Commit 1201a02

benny-dou <60535774+benny-dou@users.noreply.github.com>
2026-05-07 11:50:44
feat(instagram): support instagram story preview
1 parent 757848e
Changed files (3)
src/preview/instagram.py
@@ -1,17 +1,15 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
-
-from datetime import datetime
-from zoneinfo import ZoneInfo
+from typing import Literal
 
 from bs4 import BeautifulSoup
-from glom import glom
+from glom import flatten, glom
 from loguru import logger
 from pyrogram.client import Client
 from pyrogram.types import Message
 
 from bridge.social import send_to_social_media_bridge
-from config import API, DB, DOWNLOAD_DIR, PROVIDER, PROXY, TELEGRAM_UA, TOKEN, TZ
+from config import API, DB, DOWNLOAD_DIR, PROVIDER, PROXY, TELEGRAM_UA, TOKEN
 from database.database import get_db
 from messages.database import copy_messages_from_db, save_messages
 from messages.progress import modify_progress
@@ -19,7 +17,7 @@ from messages.sender import send2tg
 from messages.utils import blockquote, summay_media
 from multimedia import is_valid_video_or_audio, validate_img
 from networking import download_file, download_media, hx_req
-from utils import readable_count, true
+from utils import readable_count, true, ts_to_dt
 
 
 async def preview_instagram(
@@ -28,6 +26,9 @@ async def preview_instagram(
     url: str = "",
     db_key: str = "",
     *,
+    post_type: Literal["p", "story", "reel"] = "p",
+    post_id: str = "",
+    username: str = "",
     instagram_provider: str = PROVIDER.INSTAGRAM,
     instagram_comments: bool = True,
     show_author: bool = True,
@@ -58,7 +59,7 @@ async def preview_instagram(
     succ = False
     resp = {}
     if "tikhub" in instagram_provider:  # try tikhub
-        api_url = API.TIKHUB_INSTAGRAM + url
+        api_url = f"{API.TIKHUB_INSTAGRAM_STORY}{username}" if post_type == "story" else f"{API.TIKHUB_INSTAGRAM}{url}"
         logger.info(f"Preview Instagram TikHub for {api_url}")
         headers = {"authorization": f"Bearer {TOKEN.TIKHUB}", "accept": "application/json"}
         resp = await hx_req(api_url, headers=headers, check_keys=["data"], check_kv={"code": 200})
@@ -70,6 +71,9 @@ async def preview_instagram(
         return
 
     data = resp["data"]
+    if post_type == "story":
+        await preview_story(client, message, data, username, post_id, db_key=db_key, **kwargs)
+        return
     # parse media
     media = []
     if data.get("video_url"):  # reel
@@ -94,8 +98,7 @@ async def preview_instagram(
 
     if metadata_node := glom(data, "edge_media_to_caption.edges.0", default=None):
         if true(show_pubdate) and (ts := glom(metadata_node, "node.created_at", default=0)):
-            dt = datetime.fromtimestamp(float(ts)).astimezone(ZoneInfo(TZ))
-            create_time = f"{dt:%Y-%m-%d %H:%M:%S}"
+            create_time = f"{ts_to_dt(ts):%Y-%m-%d %H:%M:%S}"
             texts += f"🕒{create_time}\n"
         if true(show_statistics) and statistics:
             texts += f"{statistics}\n"
@@ -121,6 +124,36 @@ async def preview_instagram(
     await save_messages(messages=sent_messages, key=db_key)
 
 
+async def preview_story(client: Client, message: Message, data: dict, username: str, post_id: str, db_key: str, **kwargs):
+    items = flatten(glom(data, "reels_media.*.items", default=[]))
+    item = next((x for x in items if glom(x, "pk", default="") == post_id), None)
+    url = f"https://www.instagram.com/stories/{username}/{post_id}"
+    if not item:
+        await modify_progress(text=f"❌Instagram解析失败, 请访问{url}查看原始内容", force_update=True, **kwargs)
+        return
+
+    create_ts = glom(item, "taken_at", default=0)
+    expiring_ts = glom(item, "expiring_at", default=0)
+    texts = ""
+    fullname = glom(item, "story_music_stickers.0.display_artist", default=username)
+    media = []
+    if img_url := glom(item, "image_versions2.candidates.0.url", default=""):
+        media.append({"photo": download_file(img_url, proxy=PROXY.INSTAGRAM, **kwargs)})
+    if video_url := glom(item, "video_versions.0.url", default=""):
+        media.append({"video": download_file(video_url, proxy=PROXY.INSTAGRAM, **kwargs)})
+
+    texts += f"🏞**[{fullname}]({url})**"
+    if create_ts:
+        texts += f"\n🕒{ts_to_dt(create_ts):%Y-%m-%d %H:%M:%S}"
+    if expiring_ts:
+        texts += f"\n🔥{ts_to_dt(expiring_ts):%Y-%m-%d %H:%M:%S}"
+    await modify_progress(text=f"⏬正在下载:\n{summay_media(media)}", force_update=True, **kwargs)
+    media = await download_media(media, **kwargs)
+    sent_messages = await send2tg(client, message, texts=texts.strip(), media=media, **kwargs)
+    await modify_progress(del_status=True, **kwargs)
+    await save_messages(messages=sent_messages, key=db_key)
+
+
 async def preview_ddinstagram(client: Client, message: Message, url: str, post_type: str, post_id: str, *, instagram_provider: str, **kwargs):
     """Preview instagram link in the message via DDInstagram.
 
src/config.py
@@ -124,6 +124,7 @@ class API:
     TIKHUB = os.getenv("TIKHUB", "https://api.tikhub.io")
     TIKHUB_FREE = os.getenv("TIKHUB_FREE", "https://api.douyin.wtf")
     TIKHUB_INSTAGRAM = os.getenv("TIKHUB_INSTAGRAM_API", "https://api.tikhub.io/api/v1/instagram/v1/fetch_post_by_url?post_url=")
+    TIKHUB_INSTAGRAM_STORY = os.getenv("TIKHUB_INSTAGRAM_STORY_API", "https://api.tikhub.io/api/v1/instagram/v3/get_user_stories?username=")
     TIKHUB_TWITTER = os.getenv("TIKHUB_TWITTER_API", "https://api.tikhub.io/api/v1/twitter/web/fetch_post_comments?tweet_id=")
     TIKHUB_WEIBO_VIDEO = os.getenv("TIKHUB_WEIBO_VIDEO_API", "https://api.tikhub.io/api/v1/weibo/web/fetch_short_video_data?share_text=")
     TIKHUB_WECHAT = os.getenv("TIKHUB_WECHAT", "https://api.tikhub.io/api/v1/wechat_mp/web/fetch_mp_article_detail_json?url=")
src/networking.py
@@ -315,6 +315,9 @@ async def match_social_media_link(text: str, *, flatten_first: bool = True) -> d
     # https://www.instagram.com/yifaer_chen/p/DEzv9x-vzOn/
     if matched := re.search(r"(https?://)?(www\.)?instagram\.com/[a-zA-Z0-9_.]+/(:?|p|reel)/([^.。,,/\s]+)", text):
         return {"post_type": matched.group(3), "post_id": matched.group(4), "url": https_url(matched.group(0)), "db_key": bare_url(matched.group(0)), "platform": "instagram"}
+    # https://www.instagram.com/stories/laufey/3891120377355460527
+    if matched := re.search(r"(https?://)?(www\.)?instagram\.com/stories/([a-zA-Z0-9_.]+)/(\d+)", text):
+        return {"post_type": "story", "post_id": matched.group(4), "username": matched.group(3), "url": https_url(matched.group(0)), "db_key": bare_url(matched.group(0)), "platform": "instagram"}
 
     # https://x.com/taylorswift13/status/1794805688696275131
     # https://twitter.com/taylorswift13/status/1794805688696275131