main
  1#!/usr/bin/env python
  2# -*- coding: utf-8 -*-
  3from datetime import UTC, datetime
  4from zoneinfo import ZoneInfo
  5
  6from glom import glom
  7from pyrogram.client import Client
  8from pyrogram.types import Message
  9
 10from config import PROXY, TOKEN, TZ
 11from messages.progress import modify_progress
 12from messages.sender import send2tg
 13from messages.utils import remove_img_tag
 14from networking import download_file, download_media, hx_req
 15from utils import nowdt
 16
 17HEADERS = {"Accept": "application/vnd.github+json", "X-GitHub-Api-Version": "2022-11-28"}
 18if TOKEN.GITHUB:
 19    HEADERS["Authorization"] = f"Bearer {TOKEN.GITHUB}"
 20
 21
 22async def preview_github(client: Client, message: Message, url: str, gh_user: str = "", gh_repo: str = "", query: str = "", **kwargs):
 23    """Preview github info in the message."""
 24    if kwargs.get("show_progress") and "progress" not in kwargs:
 25        res = await send2tg(client, message, texts=f"🔗正在解析GitHub链接\n{url}", **kwargs)
 26        kwargs["progress"] = res[0]
 27    kwargs["send_from_user"] = ""  # disable @send_user
 28    if not query:
 29        resp = await preview_readme(gh_user, gh_repo)
 30    elif query.startswith(("issues", "pull")):
 31        resp = await preview_issue(gh_user, gh_repo, query)
 32    else:
 33        return
 34
 35    if error := resp.get("error"):
 36        await modify_progress(text=f"❌GitHub解析失败: {error}", force_update=True, **kwargs)
 37    await send2tg(client, message, **resp, **kwargs)
 38    await modify_progress(del_status=True, **kwargs)
 39
 40
 41async def preview_readme(gh_user: str, gh_repo: str) -> dict:
 42    """Preview github readme.
 43
 44    https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#get-a-repository
 45
 46    Returns:
 47        {"texts": str, "media": list[dict]}
 48    """
 49    api = f"https://api.github.com/repos/{gh_user}/{gh_repo}"
 50    resp = await hx_req(api, headers=HEADERS, proxy=PROXY.GITHUB, check_keys=["full_name"])
 51    if error := resp.get("hx_error"):
 52        return {"error": error}
 53    full_repo = resp["full_name"]
 54    gh_user, gh_repo = full_repo.split("/")  # correct uppercase / lowercase
 55    msg = ""
 56    if desc := resp["description"]:
 57        msg += f"**{desc}**\n"
 58    msg += f"📦[{full_repo}](https://github.com/{full_repo})\n"
 59    if upstream := glom(resp, "parent.full_name", default=""):
 60        msg += f"⬆️上游: [{upstream}](https://github.com/{upstream})\n"
 61
 62    msg += f"⭐️Star: {resp['stargazers_count']}\n"
 63    msg += f"👁Watch: {resp['subscribers_count']}\n"
 64    msg += f"🔀[Fork](https://github.com/{full_repo}/forks): {resp['forks_count']}\n"
 65    msg += f"❔[Issues](https://github.com/{full_repo}/issues): {resp['open_issues_count']}\n"
 66
 67    if lcen := glom(resp, "license.spdx_id", default=""):
 68        msg += f"📄License: {lcen}\n"
 69    msg += f"🕒创建日期: {convert_dt(resp['created_at']):%Y-%m-%d %H:%M:%S}\n"
 70    default_branch = resp.get("default_branch", "main")
 71    msg += f"🔄[最新推送](https://github.com/{full_repo}/commits/{default_branch}): {delta_time(resp['pushed_at'])}\n"
 72    if tags := glom(resp, "topics", default=[]):
 73        msg += "🏷️Tags:"
 74        for tag in tags:
 75            msg += f" #{tag}"
 76    media = []
 77    if readme := await download_readme(gh_user, gh_repo):
 78        media = [{"document": readme}]
 79    return {"texts": msg.strip(), "media": media}
 80
 81
 82async def preview_issue(gh_user: str, gh_repo: str, query: str) -> dict:
 83    """Preview github issue.
 84
 85    https://docs.github.com/en/rest/issues/issues?apiVersion=2022-11-28#get-an-issue
 86
 87    Args:
 88        query:
 89            issues/123
 90            issues/123#issuecomment-4567
 91            pull/123
 92            pull/123#issuecomment-4567
 93
 94    Returns:
 95        {"texts": str, "media": list[dict]}
 96    """
 97    issue_number = query.split("#")[0].split("/")[1]
 98    api = f"https://api.github.com/repos/{gh_user}/{gh_repo}/issues/{issue_number}"
 99    resp = await hx_req(api, headers=HEADERS, proxy=PROXY.GITHUB, check_kv={"number": issue_number})
100    if error := resp.get("hx_error"):
101        return {"error": error}
102    msg = f"📦[{gh_user}/{gh_repo}/{query.split('#')[0]}](https://github.com/{gh_user}/{gh_repo}/{query})\n"
103    emoji = "🟢" if resp["state"] == "open" else "🟣"
104    msg += f"{emoji}**{resp['title']}**\n"
105    if "issuecomment" in query:
106        comment_id = query.split("#issuecomment-")[-1]
107        api = f"https://api.github.com/repos/{gh_user}/{gh_repo}/issues/comments/{comment_id}"
108        resp = await hx_req(api, headers=HEADERS, proxy=PROXY.GITHUB, check_kv={"id": comment_id})
109    issue_user = glom(resp, "user.login", default="user")
110    msg += f"👤[{issue_user}](https://github.com/{issue_user})\n"
111    msg += f"🕒{convert_dt(resp['created_at']):%Y-%m-%d %H:%M:%S}创建\n"
112    if resp.get("closed_at"):
113        msg += f"🕒{convert_dt(resp['closed_at']):%Y-%m-%d %H:%M:%S}关闭\n"
114
115    media = []
116    if desc := resp["body"]:
117        cleaned, urls = remove_img_tag(desc)
118        media = [{"photo": download_file(url, proxy=PROXY.GITHUB)} for url in urls]
119        media = await download_media(media)
120        msg += f"{cleaned}\n"
121
122    return {"texts": msg.strip(), "media": media}
123
124
125async def download_readme(user: str, repo: str) -> str:
126    """Returns downloaded README path."""
127    api = f"https://api.github.com/repos/{user}/{repo}/readme"
128    resp = await hx_req(api, headers=HEADERS, proxy=PROXY.GITHUB, check_kv={"type": "file"})
129    if not resp.get("download_url"):
130        return ""
131    return await download_file(resp["download_url"], headers=HEADERS, skip_exist=False, proxy=PROXY.GITHUB)
132
133
134def convert_dt(dt: str) -> datetime:
135    """Convert 2017-04-18T22:02:38Z to datetime object."""
136    return datetime.strptime(dt[:-1], "%Y-%m-%dT%H:%M:%S").replace(tzinfo=UTC).astimezone(ZoneInfo(TZ))
137
138
139def delta_time(dt: str) -> str:
140    """Time difference between the current time and the specified time.
141
142    dt format: 2017-04-18T22:02:38Z
143    Returns: 2d12h
144    """
145    time = convert_dt(dt)
146    now = nowdt(TZ)
147    delta = now - time
148    res = ""
149    if delta.days:
150        res += f"{delta.days}"
151    minutes, _ = divmod(delta.seconds, 60)
152    hours, minutes = divmod(minutes, 60)
153    if hours:
154        res += f"{hours}小时"
155    if minutes:
156        res += f"{minutes}分钟"
157    return res + "" if res else "刚刚"