Commit bc6ef07

benny-dou <60535774+benny-dou@users.noreply.github.com>
2025-05-21 07:06:40
feat(neocities): add `neocities.org` publishing support
1 parent b918cf5
src/asr/voice_recognition.py
@@ -149,7 +149,7 @@ async def voice_to_text(
             if to_telegraph:
                 html = "\n".join([f"<p>{s}</p>" for s in texts.split("\n")])
                 if telegraph_url := await publish_telegraph(title=trigger_info["text"] or "语音识别结果", html=html, author=trigger_info["full_name"], url=trigger_info["message_url"]):
-                    caption += f"\n<a href={telegraph_url}>⚡️Telegraph即时预览</a>"
+                    caption += f"\n<a href={telegraph_url}>⚡️即时预览</a>"
             with io.BytesIO(texts.encode("utf-8")) as f:
                 await client.send_document(
                     to_int(target_chat),
src/others/download_external.py
@@ -81,7 +81,7 @@ async def download_url_in_message(client: Client, message: Message, extra_prefix
                 markdown_path = path.with_suffix(".md")
                 markdown_path.write_text(markdown)
                 if telegraph_url := await publish_telegraph(title="全文内容", texts=markdown, author=info["full_name"], url=url):
-                    caption += f"\n⚡️[Telegraph即时预览]({telegraph_url})"
+                    caption += f"\n⚡️[即时预览]({telegraph_url})"
                 success = await client.send_document(target_chat, markdown_path.as_posix(), caption=caption, reply_parameters=reply_parameters)
             else:
                 success = await client.send_document(target_chat, path.as_posix(), caption=caption, reply_parameters=reply_parameters)
src/preview/wechat.py
@@ -52,7 +52,7 @@ async def preview_wechat(client: Client, message: Message, url: str = "", db_key
             texts = f"{post_info['header']}"
             telegraph_url = await publish_telegraph(title=post_info["title"], html=post_info["html"], author=post_info["author"], url=url)
             if telegraph_url:
-                texts += f"\n⚡️[Telegraph即时预览]({telegraph_url})"
+                texts += f"\n⚡️[即时预览]({telegraph_url})"
             sent_messages.extend(await send2tg(client, message, texts=texts, media=[{"document": post_info["html_path"]}], **kwargs))
     elif length < CAPTION_LENGTH - 8:  # 有图片短文
         texts = f"{post_info['header']}\n{BLOCKQUOTE_EXPANDABLE_DELIM}{post_info['markdown']}\n{BLOCKQUOTE_EXPANDABLE_END_DELIM}"
@@ -61,7 +61,7 @@ async def preview_wechat(client: Client, message: Message, url: str = "", db_key
         texts = f"{post_info['header']}"
         telegraph_url = await publish_telegraph(title=post_info["title"], html=post_info["html"], author=post_info["author"], url=url)
         if telegraph_url:
-            texts += f"\n⚡️[Telegraph即时预览]({telegraph_url})"
+            texts += f"\n⚡️[即时预览]({telegraph_url})"
         sent_messages.extend(await send2tg(client, message, texts=texts, media=[{"document": post_info["path"]}], **kwargs))
         kwargs["reply_msg_id"] = -1  # do not send as reply
         sent_messages.extend(await send2tg(client, message, texts=texts, media=post_info["media"], **kwargs))
src/preview/ytdlp.py
@@ -272,7 +272,7 @@ async def preview_ytdlp(
                 if to_telegraph:
                     html = "\n".join([f"<p>{s}</p>" for s in subtitles.split("\n")])
                     if telegraph_url := await publish_telegraph(title=info["title"], html=html, author=info["author"], url=url):
-                        caption += f"\n⚡️[Telegraph即时预览]({telegraph_url})"
+                        caption += f"\n⚡️[即时预览]({telegraph_url})"
                 with io.BytesIO(subtitles.encode("utf-8")) as f:
                     sent_messages.append(await client.send_document(to_int(target_chat), f, file_name=f"{info['title']}.txt", caption=caption))
             else:
src/subtitles/subtitle.py
@@ -123,7 +123,7 @@ async def get_subtitle(
         if to_telegraph:
             html = "\n".join([f"<p>{s}</p>" for s in subtitles.split("\n")])
             if telegraph_url := await publish_telegraph(title=vinfo["title"], html=html, author=vinfo["author"], url=url):
-                caption += f"\n⚡️[Telegraph即时预览]({telegraph_url})"
+                caption += f"\n⚡️[即时预览]({telegraph_url})"
         with BytesIO(subtitles.encode("utf-8")) as f:
             subtitle_msg = await client.send_document(to_int(target_chat), f, file_name=f"{vinfo['title']}.txt", caption=caption)
 
src/config.py
@@ -116,6 +116,8 @@ class TOKEN:
     GOOGLE_SEARCH_CX = os.getenv("GOOGLE_SEARCH_CX", "")
     CHART_IMG = os.getenv("CHART_IMG_KEY", "")
     TELEGRAPH = os.getenv("TELEGRAPH_TOKEN", "")
+    NEOCITIES = os.getenv("NEOCITIES_USERPASS", "")  # in "user,pass" format
+    IV_HASH = os.getenv("INSTANTVIEW_HASH", "")
 
 
 class PROXY:  # format: socks5://127.0.0.1:7890
src/utils.py
@@ -1,21 +1,26 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 import contextlib
+import io
 import json
 import random
 import re
 import string
+import tempfile
 from datetime import UTC, datetime
 from decimal import Decimal
 from pathlib import Path
 from typing import Any
+from urllib.parse import quote_plus
 from zoneinfo import ZoneInfo
 
+import anyio
 import markdown
 import zhconv
 from bilibili_api.utils.aid_bvid_transformer import aid2bvid, bvid2aid
 from bs4.element import PageElement
 from glom import Coalesce, PathAccessError, glom
+from httpx import AsyncClient
 from loguru import logger
 from pyrogram.client import Client
 from pyrogram.types import User
@@ -417,17 +422,20 @@ def cleanup_old_files(root: Path | str | None = None, duration: int = 7200) -> N
             path.unlink(missing_ok=True)
 
 
-async def publish_telegraph(title: str, texts: str | None = None, html: str | None = None, author: str | None = None, url: str | None = None) -> str:
+async def publish_telegraph(title: str, texts: str | None = None, html: str = "", author: str | None = None, url: str | None = None) -> str:
     """Publish to Telegraph."""
+
+    def clean_html(s: str | None) -> str:
+        # Revise Telegraph Tags
+        s = str(s).replace("<h1>", "<h3>").replace("</h1>", "</h3>")
+        return s.replace("<h2>", "<h3>").replace("</h2>", "</h3>")
+
     if not TOKEN.TELEGRAPH:
         return ""
     if not (texts or html):
         return ""
     if texts:
         html = markdown.markdown(texts)
-        # Revise Telegraph Tags
-        html = html.replace("<h1>", "<h3>").replace("</h1>", "</h3>")
-        html = html.replace("<h2>", "<h3>").replace("</h2>", "</h3>")
     telegraph = Telegraph(access_token=TOKEN.TELEGRAPH)
     account_info = {}
     if not (author and url):
@@ -444,12 +452,54 @@ async def publish_telegraph(title: str, texts: str | None = None, html: str | No
     if isinstance(url, str):
         url = url[:512]
     try:
-        page = await telegraph.create_page(title=title[:256], author_name=author, author_url=url, html_content=html)
-        logger.info(f"⚡️Telegraph即时预览: {page['url']}")
+        page = await telegraph.create_page(title=title[:256], author_name=author, author_url=url, html_content=clean_html(html))
+        logger.info(f"⚡️即时预览: {page['url']}")
         return page["url"]
     except Exception as e:
         logger.error(f"Telegraph publish error: {e}")
+        return await publish_neocities(title, texts=texts, html=html, author=author, url=url)
+
+
+async def publish_neocities(title: str, texts: str | None = None, html: str = "", author: str | None = None, url: str | None = None) -> str:
+    """Publish to neocities.org ."""
+    if not TOKEN.NEOCITIES:
+        return ""
+    if not (texts or html):
         return ""
+    if texts:
+        html = markdown.markdown(texts)
+    base_url = "https://neocities.org/api/upload"
+    username, password = TOKEN.NEOCITIES.split(",")
+    now = nowdt(TZ)
+    today = f"{now:%Y-%m-%d}"
+    server_file = f"{today}/{rand_string(12)}.html"
+    pub_url = f"https://{username}.neocities.org/{server_file.removesuffix('.html')}"
+    if not url:
+        url = pub_url
+    if not author:
+        author = "BennyBot"
+
+    html = f'<h1>{title}</h1><div class="author-name"><a href="{url}" id="author-url">{author}</a>{html}'
+    html = f'<!DOCTYPE html><html><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>{title}</title><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/water.css@2/out/water.css"></head><body><article>{html}</article></body></html>'
+
+    try:
+        with tempfile.NamedTemporaryFile("w", suffix=".html", delete=False) as tempf:
+            tempf.write(html)
+        async with await anyio.open_file(tempf.name, "rb") as f:
+            content = await f.read()
+            client = AsyncClient(http2=True, timeout=20)
+            await client.post(
+                base_url,
+                auth=(username, password),
+                files={server_file: (server_file, io.BytesIO(content), "text/html")},
+            )
+        Path(tempf.name).unlink(missing_ok=True)
+        logger.info(f"⚡️Neocities: {pub_url}")
+    except Exception as e:
+        logger.error(f"Neocities publish error: {e}")
+        return ""
+
+    return f"https://t.me/iv?url={quote_plus(pub_url)}&rhash={TOKEN.IV_HASH}" if TOKEN.IV_HASH else pub_url
 
 
 def av2bv(aid: int | str) -> str: