feat: support mult misskey instance

This commit is contained in:
omg-xtao 2023-07-20 22:21:37 +08:00 committed by GitHub
parent 871d3f4a01
commit 6dd58cb15d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 286 additions and 235 deletions

View File

@ -5,28 +5,27 @@ from mipac.models.lite import LiteUser
from pyrogram.errors import MediaEmpty from pyrogram.errors import MediaEmpty
from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton
from glover import misskey_host
from init import bot, request from init import bot, request
from models.services.scheduler import add_delete_file_job, delete_file from models.services.scheduler import add_delete_file_job, delete_file
def get_user_link(user: LiteUser) -> str: def get_user_link(host: str, user: LiteUser) -> str:
if user.host: if user.host:
return f"https://{user.host}/@{user.username}" return f"https://{host}/@{user.username}@{user.host}"
return f"{misskey_host}/@{user.username}" return f"https://{host}/@{user.username}"
def get_source_link(message: ChatMessage) -> str: def get_source_link(host: str, message: ChatMessage) -> str:
return ( return (
f"{misskey_host}/my/messaging/{message.user.username}?cid={message.user.id}" f"https://{host}/my/messaging/{message.user.username}?cid={message.user.id}"
if not message.group and message.user if not message.group and message.user
else f"{misskey_host}/my/messaging/group/{message.group.id}" else f"https://{host}/my/messaging/group/{message.group.id}"
) )
def gen_button(message: ChatMessage): def gen_button(host: str, message: ChatMessage):
author = get_user_link(message.user) author = get_user_link(host, message.user)
source = get_source_link(message) source = get_source_link(host, message)
first_line = [ first_line = [
InlineKeyboardButton(text="Chat", url=source), InlineKeyboardButton(text="Chat", url=source),
InlineKeyboardButton(text="Author", url=author), InlineKeyboardButton(text="Author", url=author),
@ -34,24 +33,26 @@ def gen_button(message: ChatMessage):
return InlineKeyboardMarkup([first_line]) return InlineKeyboardMarkup([first_line])
def get_content(message: ChatMessage) -> str: def get_content(host: str, message: ChatMessage) -> str:
content = message.text or "" content = message.text or ""
content = content[:768] content = content[:768]
user = f'<a href="{get_user_link(message.user)}">{message.user.nickname}</a>' user = f'<a href="{get_user_link(host, message.user)}">{message.user.nickname}</a>'
if message.group: if message.group:
group = f'<a href="{get_source_link(message)}">{message.group.name}</a>' group = f'<a href="{get_source_link(host, message)}">{message.group.name}</a>'
user += f" ( {group} )" user += f" ( {group} )"
return f"""<b>Misskey Message</b> return f"""<b>Misskey Message</b>
{user} <code>{content}</code>""" {user} <code>{content}</code>"""
async def send_text(cid: int, message: ChatMessage, reply_to_message_id: int): async def send_text(
host: str, cid: int, message: ChatMessage, reply_to_message_id: int
):
await bot.send_message( await bot.send_message(
cid, cid,
get_content(message), get_content(host, message),
reply_to_message_id=reply_to_message_id, reply_to_message_id=reply_to_message_id,
reply_markup=gen_button(message), reply_markup=gen_button(host, message),
disable_web_page_preview=True, disable_web_page_preview=True,
) )
@ -61,53 +62,53 @@ def deprecated_to_text(func):
try: try:
return await func(*args, **kwargs) return await func(*args, **kwargs)
except MediaEmpty: except MediaEmpty:
return await send_text(args[0], args[2], args[3]) return await send_text(args[0], args[1], args[3], args[4])
return wrapper return wrapper
@deprecated_to_text @deprecated_to_text
async def send_photo( async def send_photo(
cid: int, url: str, message: ChatMessage, reply_to_message_id: int host: str, cid: int, url: str, message: ChatMessage, reply_to_message_id: int
): ):
if not url: if not url:
return await send_text(cid, message, reply_to_message_id) return await send_text(host, cid, message, reply_to_message_id)
await bot.send_photo( await bot.send_photo(
cid, cid,
url, url,
reply_to_message_id=reply_to_message_id, reply_to_message_id=reply_to_message_id,
caption=get_content(message), caption=get_content(host, message),
reply_markup=gen_button(message), reply_markup=gen_button(host, message),
) )
@deprecated_to_text @deprecated_to_text
async def send_video( async def send_video(
cid: int, url: str, message: ChatMessage, reply_to_message_id: int host: str, cid: int, url: str, message: ChatMessage, reply_to_message_id: int
): ):
if not url: if not url:
return await send_text(cid, message, reply_to_message_id) return await send_text(host, cid, message, reply_to_message_id)
await bot.send_video( await bot.send_video(
cid, cid,
url, url,
reply_to_message_id=reply_to_message_id, reply_to_message_id=reply_to_message_id,
caption=get_content(message), caption=get_content(host, message),
reply_markup=gen_button(message), reply_markup=gen_button(host, message),
) )
@deprecated_to_text @deprecated_to_text
async def send_audio( async def send_audio(
cid: int, url: str, message: ChatMessage, reply_to_message_id: int host: str, cid: int, url: str, message: ChatMessage, reply_to_message_id: int
): ):
if not url: if not url:
return await send_text(cid, message, reply_to_message_id) return await send_text(host, cid, message, reply_to_message_id)
await bot.send_audio( await bot.send_audio(
cid, cid,
url, url,
reply_to_message_id=reply_to_message_id, reply_to_message_id=reply_to_message_id,
caption=get_content(message), caption=get_content(host, message),
reply_markup=gen_button(message), reply_markup=gen_button(host, message),
) )
@ -129,31 +130,31 @@ async def fetch_document(file: File) -> Optional[str]:
@deprecated_to_text @deprecated_to_text
async def send_document( async def send_document(
cid: int, file: File, message: ChatMessage, reply_to_message_id: int host: str, cid: int, file: File, message: ChatMessage, reply_to_message_id: int
): ):
file = await fetch_document(file) file = await fetch_document(file)
if not file: if not file:
return await send_text(cid, message, reply_to_message_id) return await send_text(host, cid, message, reply_to_message_id)
await bot.send_document( await bot.send_document(
cid, cid,
file, file,
reply_to_message_id=reply_to_message_id, reply_to_message_id=reply_to_message_id,
caption=get_content(message), caption=get_content(host, message),
reply_markup=gen_button(message), reply_markup=gen_button(host, message),
) )
await delete_file(file) await delete_file(file)
async def send_chat_message(cid: int, message: ChatMessage, topic_id: int): async def send_chat_message(host: str, cid: int, message: ChatMessage, topic_id: int):
if not message.file: if not message.file:
return await send_text(cid, message, topic_id) return await send_text(host, cid, message, topic_id)
file_url = message.file.url file_url = message.file.url
file_type = message.file.type file_type = message.file.type
if file_type.startswith("image"): if file_type.startswith("image"):
await send_photo(cid, file_url, message, topic_id) await send_photo(host, cid, file_url, message, topic_id)
elif file_type.startswith("video"): elif file_type.startswith("video"):
await send_video(cid, file_url, message, topic_id) await send_video(host, cid, file_url, message, topic_id)
elif file_type.startswith("audio"): elif file_type.startswith("audio"):
await send_audio(cid, file_url, message, topic_id) await send_audio(host, cid, file_url, message, topic_id)
else: else:
await send_document(cid, message.file, message, topic_id) await send_document(host, cid, message.file, message, topic_id)

30
defs/check_node.py Normal file
View File

@ -0,0 +1,30 @@
from httpx import URL, InvalidURL
from init import request
def get_host(url: str) -> str:
try:
url = URL(url)
except InvalidURL:
return ""
return url.host
async def check_host(host: str) -> bool:
if not host:
return False
try:
req = await request.get(f"https://{host}/.well-known/nodeinfo")
req.raise_for_status()
node_url = req.json()["links"][0]["href"]
req = await request.get(node_url)
req.raise_for_status()
data = req.json()
if data["software"]["name"] != "misskey":
raise ValueError
if not data["software"]["version"].startswith("13."):
raise ValueError
return True
except Exception:
return False

View File

@ -6,7 +6,6 @@ from mipac.models.lite import LiteUser
from mipac.types import IDriveFile from mipac.types import IDriveFile
from pyrogram.enums import ParseMode from pyrogram.enums import ParseMode
from pyrogram.errors import MediaEmpty from pyrogram.errors import MediaEmpty
from pyrogram.types import ( from pyrogram.types import (
InlineKeyboardMarkup, InlineKeyboardMarkup,
InlineKeyboardButton, InlineKeyboardButton,
@ -16,18 +15,17 @@ from pyrogram.types import (
InputMediaAudio, InputMediaAudio,
) )
from glover import misskey_host
from init import bot, request from init import bot, request
from models.services.scheduler import add_delete_file_job, delete_file from models.services.scheduler import add_delete_file_job, delete_file
def get_note_url(note: Note) -> str: def get_note_url(host: str, note: Note) -> str:
return f"{misskey_host}/notes/{note.id}" return f"https://{host}/notes/{note.id}"
def gen_button(note: Note, author: str): def gen_button(host: str, note: Note, author: str):
source = get_note_url(note) source = get_note_url(host, note)
reply_source = get_note_url(note.reply) if note.reply else None reply_source = get_note_url(host, note.reply) if note.reply else None
renote_id = note.renote_id if note.reply else note.id renote_id = note.renote_id if note.reply else note.id
if reply_source: if reply_source:
first_line = [ first_line = [
@ -48,14 +46,16 @@ def gen_button(note: Note, author: str):
return InlineKeyboardMarkup([first_line, second_line]) return InlineKeyboardMarkup([first_line, second_line])
def get_user_link(user: LiteUser) -> str: def get_user_link(host: str, user: LiteUser) -> str:
if user.host: if user.host:
return f"https://{user.host}/@{user.username}" return f"https://{host}/@{user.username}@{user.host}"
return f"{misskey_host}/@{user.username}" return f"https://{host}/@{user.username}"
def get_user_alink(user: LiteUser) -> str: def get_user_alink(host: str, user: LiteUser) -> str:
return "<a href=\"{}\">{}</a>".format(get_user_link(user), user.nickname or f"@{user.username}") return '<a href="{}">{}</a>'.format(
get_user_link(host, user), user.nickname or f"@{user.username}"
)
def get_post_time(date: datetime) -> str: def get_post_time(date: datetime) -> str:
@ -66,7 +66,7 @@ def get_post_time(date: datetime) -> str:
return datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S") return datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S")
def get_content(note: Note) -> str: def get_content(host: str, note: Note) -> str:
content = note.content or "" content = note.content or ""
action = "发表" action = "发表"
origin = "" origin = ""
@ -76,7 +76,7 @@ def get_content(note: Note) -> str:
action = "转推" action = "转推"
content = note.renote.content or content content = note.renote.content or content
origin = ( origin = (
f'\n{get_user_alink(note.renote.author)} ' f"\n{get_user_alink(host, note.renote.author)} "
f"发表于 {get_post_time(note.renote.created_at)}" f"发表于 {get_post_time(note.renote.created_at)}"
) )
content = content[:768] content = content[:768]
@ -84,16 +84,16 @@ def get_content(note: Note) -> str:
<code>{content}</code> <code>{content}</code>
{get_user_alink(note.author)} {action} {get_post_time(note.created_at)}{origin} {get_user_alink(host, note.author)} {action} {get_post_time(note.created_at)}{origin}
点赞: {sum(show_note.reactions.values())} | 回复: {show_note.replies_count} | 转发: {show_note.renote_count}""" 点赞: {sum(show_note.reactions.values())} | 回复: {show_note.replies_count} | 转发: {show_note.renote_count}"""
async def send_text(cid: int, note: Note, reply_to_message_id: int): async def send_text(host: str, cid: int, note: Note, reply_to_message_id: int):
await bot.send_message( await bot.send_message(
cid, cid,
get_content(note), get_content(host, note),
reply_to_message_id=reply_to_message_id, reply_to_message_id=reply_to_message_id,
reply_markup=gen_button(note, get_user_link(note.author)), reply_markup=gen_button(host, note, get_user_link(host, note.author)),
disable_web_page_preview=True, disable_web_page_preview=True,
) )
@ -103,47 +103,53 @@ def deprecated_to_text(func):
try: try:
return await func(*args, **kwargs) return await func(*args, **kwargs)
except MediaEmpty: except MediaEmpty:
return await send_text(args[0], args[2], args[3]) return await send_text(args[0], args[1], args[3], args[4])
return wrapper return wrapper
@deprecated_to_text @deprecated_to_text
async def send_photo(cid: int, url: str, note: Note, reply_to_message_id: int): async def send_photo(
host: str, cid: int, url: str, note: Note, reply_to_message_id: int
):
if not url: if not url:
return await send_text(cid, note, reply_to_message_id) return await send_text(host, cid, note, reply_to_message_id)
await bot.send_photo( await bot.send_photo(
cid, cid,
url, url,
reply_to_message_id=reply_to_message_id, reply_to_message_id=reply_to_message_id,
caption=get_content(note), caption=get_content(host, note),
reply_markup=gen_button(note, get_user_link(note.author)), reply_markup=gen_button(host, note, get_user_link(host, note.author)),
) )
@deprecated_to_text @deprecated_to_text
async def send_video(cid: int, url: str, note: Note, reply_to_message_id: int): async def send_video(
host: str, cid: int, url: str, note: Note, reply_to_message_id: int
):
if not url: if not url:
return await send_text(cid, note, reply_to_message_id) return await send_text(host, cid, note, reply_to_message_id)
await bot.send_video( await bot.send_video(
cid, cid,
url, url,
reply_to_message_id=reply_to_message_id, reply_to_message_id=reply_to_message_id,
caption=get_content(note), caption=get_content(host, note),
reply_markup=gen_button(note, get_user_link(note.author)), reply_markup=gen_button(host, note, get_user_link(host, note.author)),
) )
@deprecated_to_text @deprecated_to_text
async def send_audio(cid: int, url: str, note: Note, reply_to_message_id: int): async def send_audio(
host: str, cid: int, url: str, note: Note, reply_to_message_id: int
):
if not url: if not url:
return await send_text(cid, note, reply_to_message_id) return await send_text(host, cid, note, reply_to_message_id)
await bot.send_audio( await bot.send_audio(
cid, cid,
url, url,
reply_to_message_id=reply_to_message_id, reply_to_message_id=reply_to_message_id,
caption=get_content(note), caption=get_content(host, note),
reply_markup=gen_button(note, get_user_link(note.author)), reply_markup=gen_button(host, note, get_user_link(host, note.author)),
) )
@ -165,17 +171,17 @@ async def fetch_document(file: IDriveFile) -> Optional[str]:
@deprecated_to_text @deprecated_to_text
async def send_document( async def send_document(
cid: int, file: IDriveFile, note: Note, reply_to_message_id: int host: str, cid: int, file: IDriveFile, note: Note, reply_to_message_id: int
): ):
file = await fetch_document(file) file = await fetch_document(file)
if not file: if not file:
return await send_text(cid, note, reply_to_message_id) return await send_text(host, cid, note, reply_to_message_id)
await bot.send_document( await bot.send_document(
cid, cid,
file, file,
reply_to_message_id=reply_to_message_id, reply_to_message_id=reply_to_message_id,
caption=get_content(note), caption=get_content(host, note),
reply_markup=gen_button(note, get_user_link(note.author)), reply_markup=gen_button(host, note, get_user_link(host, note.author)),
) )
await delete_file(file) await delete_file(file)
@ -219,11 +225,11 @@ async def get_media_group(files: list[IDriveFile]) -> list:
async def send_group( async def send_group(
cid: int, files: list[IDriveFile], note: Note, reply_to_message_id: int host: str, cid: int, files: list[IDriveFile], note: Note, reply_to_message_id: int
): ):
groups = await get_media_group(files) groups = await get_media_group(files)
if len(groups) == 0: if len(groups) == 0:
return await send_text(cid, note, reply_to_message_id) return await send_text(host, cid, note, reply_to_message_id)
photo, video, audio, document, msg = [], [], [], [], None photo, video, audio, document, msg = [], [], [], [], None
for i in groups: for i in groups:
if isinstance(i, InputMediaPhoto): if isinstance(i, InputMediaPhoto):
@ -278,10 +284,10 @@ async def send_group(
) )
if msg and isinstance(msg, list): if msg and isinstance(msg, list):
msg = msg[0] msg = msg[0]
await send_text(cid, note, msg.id if msg else None) await send_text(host, cid, note, msg.id if msg else None)
async def send_update(cid: int, note: Note, topic_id: int): async def send_update(host: str, cid: int, note: Note, topic_id: int):
files = list(note.files) files = list(note.files)
if note.reply: if note.reply:
files.extend(iter(note.reply.files)) files.extend(iter(note.reply.files))
@ -290,18 +296,18 @@ async def send_update(cid: int, note: Note, topic_id: int):
files = list({f.get("id"): f for f in files}.values()) files = list({f.get("id"): f for f in files}.values())
match len(files): match len(files):
case 0: case 0:
await send_text(cid, note, topic_id) await send_text(host, cid, note, topic_id)
case 1: case 1:
file = files[0] file = files[0]
file_url = file.get("url", None) file_url = file.get("url", None)
file_type = file.get("type", "") file_type = file.get("type", "")
if file_type.startswith("image"): if file_type.startswith("image"):
await send_photo(cid, file_url, note, topic_id) await send_photo(host, cid, file_url, note, topic_id)
elif file_type.startswith("video"): elif file_type.startswith("video"):
await send_video(cid, file_url, note, topic_id) await send_video(host, cid, file_url, note, topic_id)
elif file_type.startswith("audio"): elif file_type.startswith("audio"):
await send_audio(cid, file_url, note, topic_id) await send_audio(host, cid, file_url, note, topic_id)
else: else:
await send_document(cid, file, note, topic_id) await send_document(host, cid, file, note, topic_id)
case _: case _:
await send_group(cid, files, note, topic_id) await send_group(host, cid, files, note, topic_id)

View File

@ -1,10 +1,11 @@
from json import load from json import load
from mipac.models.lite.user import LiteUser
from mipac.models.notification import ( from mipac.models.notification import (
NotificationFollow, NotificationFollow,
NotificationFollowRequest, NotificationFollowRequest,
NotificationAchievement, NotificationAchievement,
) )
from mipac.models.lite.user import LiteUser
from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton
from init import bot from init import bot

View File

@ -1,6 +1,6 @@
from datetime import datetime, timedelta
from typing import Optional from typing import Optional
from datetime import datetime, timedelta
from mipac import UserDetailed from mipac import UserDetailed
from mipac.errors import FailedToResolveRemoteUserError from mipac.errors import FailedToResolveRemoteUserError
from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup

View File

@ -2,157 +2,157 @@
"notes1": [ "notes1": [
"初来乍到", "初来乍到",
"第一次发帖", "第一次发帖",
"祝您在Misskey玩的愉快" "祝您在 Misskey 玩的愉快~"
], ],
"notes10": [ "notes10": [
"一些帖子", "一些帖子",
"发布了10篇帖子", "发布了 10 篇帖子",
"" ""
], ],
"notes100": [ "notes100": [
"很多帖子", "很多帖子",
"发布了100篇帖子", "发布了 100 篇帖子",
"" ""
], ],
"notes500": [ "notes500": [
"满是帖子", "满是帖子",
"发布了500篇帖子", "发布了 500 篇帖子",
"" ""
], ],
"notes1000": [ "notes1000": [
"积帖成山", "积帖成山",
"发布了1,000篇帖子", "发布了 1,000 篇帖子",
"" ""
], ],
"notes5000": [ "notes5000": [
"帖如泉涌", "帖如泉涌",
"发布了5,000篇帖子", "发布了 5,000 篇帖子",
"" ""
], ],
"notes10000": [ "notes10000": [
"超级帖", "超级帖",
"发布了10,000篇帖子", "发布了 10,000 篇帖子",
"" ""
], ],
"notes20000": [ "notes20000": [
"还想要更多帖子", "还想要更多帖子",
"发布了20,000篇帖子", "发布了 20,000 篇帖子",
"" ""
], ],
"notes30000": [ "notes30000": [
"帖子帖子帖子", "帖子帖子帖子",
"发布了30,000篇帖子", "发布了 30,000 篇帖子",
"" ""
], ],
"notes40000": [ "notes40000": [
"帖子工厂", "帖子工厂",
"发布了40,000篇帖子", "发布了 40,000 篇帖子",
"" ""
], ],
"notes50000": [ "notes50000": [
"帖子星球", "帖子星球",
"发布了50,000篇帖子", "发布了 50,000 篇帖子",
"" ""
], ],
"notes60000": [ "notes60000": [
"帖子类星体", "帖子类星体",
"发布了60,000篇帖子", "发布了 60,000 篇帖子",
"" ""
], ],
"notes70000": [ "notes70000": [
"帖子黑洞", "帖子黑洞",
"发布了70,000篇帖子", "发布了 70,000 篇帖子",
"" ""
], ],
"notes80000": [ "notes80000": [
"帖子星系", "帖子星系",
"发布了80,000篇帖子", "发布了 80,000 篇帖子",
"" ""
], ],
"notes90000": [ "notes90000": [
"帖子起源", "帖子起源",
"发布了90,000篇帖子", "发布了 90,000 篇帖子",
"" ""
], ],
"notes100000": [ "notes100000": [
"ALL YOUR NOTE ARE BELONG TO US", "ALL YOUR NOTE ARE BELONG TO US",
"发布了100,000篇帖子", "发布了 100,000 篇帖子",
"真的有那么多可以写的东西吗?" "真的有那么多可以写的东西吗?"
], ],
"login3": [ "login3": [
"初学者 I", "初学者 I",
"连续登录3天", "累计登录 3 天",
"今天开始我就是Misskist" "今天开始我就是 Misskist"
], ],
"login7": [ "login7": [
"初学者 II", "初学者 II",
"连续登录7天", "累计登录 7 天",
"您开始习惯了吗?" "您开始习惯了吗?"
], ],
"login15": [ "login15": [
"初学者 III", "初学者 III",
"连续登录15天", "累计登录 15 天",
"" ""
], ],
"login30": [ "login30": [
"Misskist ", "Misskist ",
"连续登录30天", "累计登录 30 天",
"" ""
], ],
"login60": [ "login60": [
"Misskist Ⅱ", "Misskist Ⅱ",
"连续登录60天", "累计登录 60 天",
"" ""
], ],
"login100": [ "login100": [
"Misskist Ⅲ", "Misskist Ⅲ",
"总登入100天", "累计登入 100 天",
"那个用户,是Misskist喔" "那个用户,是 Misskist 喔"
], ],
"login200": [ "login200": [
"定期联系Ⅰ", "定期联系Ⅰ",
"总登录天数200天", "累计登录 200 天",
"" ""
], ],
"login300": [ "login300": [
"定期联系Ⅱ", "定期联系Ⅱ",
"总登录天数300天", "累计登录 300 天",
"" ""
], ],
"login400": [ "login400": [
"定期联系Ⅲ", "定期联系Ⅲ",
"总登录天数400天", "累计登录 400 天",
"" ""
], ],
"login500": [ "login500": [
"老熟人Ⅰ", "老熟人Ⅰ",
"总登录天数500天", "累计登录 500 天",
"诸君,我喜欢贴文" "诸君,我喜欢贴文"
], ],
"login600": [ "login600": [
"老熟人Ⅱ", "老熟人Ⅱ",
"总登录天数600天", "累计登录 600 天",
"" ""
], ],
"login700": [ "login700": [
"老熟人Ⅲ", "老熟人Ⅲ",
"总登录天数700天", "累计登录 700 天",
"" ""
], ],
"login800": [ "login800": [
"帖子大师", "帖子大师 ",
"总登录天数800天", "累计登录 800 天",
"" ""
], ],
"login900": [ "login900": [
"帖子大师Ⅱ", "帖子大师 Ⅱ",
"总登录天数900天", "累计登录 900 天",
"" ""
], ],
"login1000": [ "login1000": [
"帖子大师Ⅲ", "帖子大师 Ⅲ",
"总登录天数1000天", "累计登录 1000 天",
"感谢您使用Misskey" "感谢您使用 Misskey"
], ],
"noteClipped1": [ "noteClipped1": [
"忍不住要收藏到便签", "忍不住要收藏到便签",
@ -186,22 +186,22 @@
], ],
"following10": [ "following10": [
"关注,跟随", "关注,跟随",
"关注超过10人", "关注超过 10 人",
"" ""
], ],
"following50": [ "following50": [
"我的朋友很多", "我的朋友很多",
"关注超过50人", "关注超过 50 人",
"" ""
], ],
"following100": [ "following100": [
"我的朋友很多", "胜友如云",
"关注超过100人", "关注超过 100 人",
"" ""
], ],
"following300": [ "following300": [
"朋友成群", "朋友成群",
"关注数超过300", "关注数超过 300",
"" ""
], ],
"followers1": [ "followers1": [
@ -211,37 +211,37 @@
], ],
"followers10": [ "followers10": [
"关注我吧!", "关注我吧!",
"拥有超过10名关注者", "拥有超过 10 名关注者",
"" ""
], ],
"followers50": [ "followers50": [
"三五成群", "三五成群",
"拥有超过50名关注者", "拥有超过 50 名关注者",
"" ""
], ],
"followers100": [ "followers100": [
"胜友如云", "胜友如云",
"拥有超过100名关注者", "拥有超过 100 名关注者",
"" ""
], ],
"followers300": [ "followers300": [
"排列成行", "排列成行",
"拥有超过300名关注者", "拥有超过 300 名关注者",
"" ""
], ],
"followers500": [ "followers500": [
"信号塔", "信号塔",
"拥有超过500名关注者", "拥有超过 500 名关注者",
"" ""
], ],
"followers1000": [ "followers1000": [
"大影响家", "大影响家",
"拥有超过1000名关注者", "拥有超过 1000 名关注者",
"" ""
], ],
"collectAchievements30": [ "collectAchievements30": [
"成就收藏家", "成就收藏家",
"获得超过30个成就", "获得超过 30 个成就",
"" ""
], ],
"viewAchievements3min": [ "viewAchievements3min": [
@ -251,7 +251,7 @@
], ],
"iLoveMisskey": [ "iLoveMisskey": [
"I Love Misskey", "I Love Misskey",
"发布\"I ❤ #Misskey\"帖子", "发布 \"I ❤ #Misskey\" 帖子",
"感谢您使用 Misskey by 开发团队" "感谢您使用 Misskey by 开发团队"
], ],
"foundTreasure": [ "foundTreasure": [
@ -261,12 +261,12 @@
], ],
"client30min": [ "client30min": [
"休息一下!", "休息一下!",
"启动客户端超过30分钟", "启动客户端超过 30 分钟",
"" ""
], ],
"client60min": [ "client60min": [
"Misskey重度依赖", "Misskey 重度依赖",
"启动客户端超过60分钟", "启动客户端超过 60 分钟",
"" ""
], ],
"noteDeletedWithin1min": [ "noteDeletedWithin1min": [
@ -281,7 +281,7 @@
], ],
"postedAt0min0sec": [ "postedAt0min0sec": [
"报时", "报时",
"在0点发布一篇帖子", "在 0 点发布一篇帖子",
"嘣 嘣 嘣 Biu——" "嘣 嘣 嘣 Biu——"
], ],
"selfQuote": [ "selfQuote": [
@ -291,7 +291,7 @@
], ],
"htl20npm": [ "htl20npm": [
"流动的时间线", "流动的时间线",
"在首页时间线的流速超过20npm", "在首页时间线的流速超过 20npm",
"" ""
], ],
"viewInstanceChart": [ "viewInstanceChart": [
@ -301,7 +301,7 @@
], ],
"outputHelloWorldOnScratchpad": [ "outputHelloWorldOnScratchpad": [
"Hello, world!", "Hello, world!",
"在AiScript控制台中输出 hello world", "在 AiScript 控制台中输出 hello world",
"" ""
], ],
"open3windows": [ "open3windows": [
@ -316,7 +316,7 @@
], ],
"reactWithoutRead": [ "reactWithoutRead": [
"有好好读过吗?", "有好好读过吗?",
"在含有100字以上的帖子被发出三秒内做出回应", "在含有 100 字以上的帖子被发出三秒内做出回应",
"" ""
], ],
"clickedClickHere": [ "clickedClickHere": [
@ -326,27 +326,27 @@
], ],
"justPlainLucky": [ "justPlainLucky": [
"超高校级的幸运", "超高校级的幸运",
"每10秒有0.01的概率自动获得", "每 10 秒有 0.01 的概率自动获得",
"" ""
], ],
"setNameToSyuilo": [ "setNameToSyuilo": [
"像神一样呐", "像神一样呐",
"将名称设定为syuilo", "将名称设定为 syuilo",
"" ""
], ],
"passedSinceAccountCreated1": [ "passedSinceAccountCreated1": [
"一周年", "一周年",
"账户创建时间超过1年", "账户创建时间超过 1 年",
"" ""
], ],
"passedSinceAccountCreated2": [ "passedSinceAccountCreated2": [
"二周年", "二周年",
"账户创建时间超过2年", "账户创建时间超过 2 年",
"" ""
], ],
"passedSinceAccountCreated3": [ "passedSinceAccountCreated3": [
"三周年", "三周年",
"账户创建时间超过3年", "账户创建时间超过 3 年",
"" ""
], ],
"loggedInOnBirthday": [ "loggedInOnBirthday": [
@ -366,7 +366,7 @@
], ],
"brainDiver": [ "brainDiver": [
"Brain Diver", "Brain Diver",
"发布了包含Brain Diver链接的帖子", "发布了包含 Brain Diver 链接的帖子",
"Misskey-Misskey La-Tu-Ma" "Misskey-Misskey La-Tu-Ma"
] ]
} }

View File

@ -1,7 +1,8 @@
import yaml
from pathlib import Path
from httpx import get
from json import dump from json import dump
from pathlib import Path
import yaml
from httpx import get
json_path = Path(__file__).parent / "achievement.json" json_path = Path(__file__).parent / "achievement.json"

View File

@ -1,4 +1,3 @@
import re
from configparser import RawConfigParser from configparser import RawConfigParser
from typing import Union from typing import Union
from distutils.util import strtobool from distutils.util import strtobool
@ -9,9 +8,6 @@ api_hash: str = ""
# [Basic] # [Basic]
ipv6: Union[bool, str] = "False" ipv6: Union[bool, str] = "False"
# [misskey] # [misskey]
misskey_url: str = ""
misskey_host: str = ""
misskey_domain: str = ""
web_domain: str = "" web_domain: str = ""
admin: int = 0 admin: int = 0
@ -20,20 +16,9 @@ config.read("config.ini")
api_id = config.getint("pyrogram", "api_id", fallback=api_id) api_id = config.getint("pyrogram", "api_id", fallback=api_id)
api_hash = config.get("pyrogram", "api_hash", fallback=api_hash) api_hash = config.get("pyrogram", "api_hash", fallback=api_hash)
ipv6 = config.get("basic", "ipv6", fallback=ipv6) ipv6 = config.get("basic", "ipv6", fallback=ipv6)
misskey_url = config.get("misskey", "url", fallback=misskey_url)
if origin_url := re.search(r"wss?://(.*)/streaming", misskey_url):
misskey_host = (
origin_url[0]
.replace("wss", "https")
.replace("ws", "http")
.replace("/streaming", "")
)
else:
misskey_host = misskey_url
misskey_domain = re.search(r"https?://(.*)", misskey_host)[1]
web_domain = config.get("misskey", "web_domain", fallback=web_domain) web_domain = config.get("misskey", "web_domain", fallback=web_domain)
admin = config.getint("misskey", "admin", fallback=admin) admin = config.getint("misskey", "admin", fallback=admin)
try: try:
ipv6 = strtobool(ipv6) ipv6 = bool(strtobool(ipv6))
except ValueError: except ValueError:
ipv6 = False ipv6 = False

View File

@ -23,7 +23,6 @@ from defs.notice import (
send_follow_request_accept, send_follow_request_accept,
send_achievement_earned, send_achievement_earned,
) )
from glover import misskey_url, misskey_host
from models.models.user import User, TokenStatusEnum from models.models.user import User, TokenStatusEnum
from models.services.user import UserAction from models.services.user import UserAction
@ -45,41 +44,37 @@ class MisskeyBot(commands.Bot):
await Router(ws).connect_channel(["main", "home"]) await Router(ws).connect_channel(["main", "home"])
async def on_note(self, note: Note): async def on_note(self, note: Note):
if self.tg_user: await send_update(
await send_update(self.tg_user.chat_id, note, self.tg_user.timeline_topic) self.tg_user.host, self.tg_user.chat_id, note, self.tg_user.timeline_topic
)
async def on_user_followed(self, notice: NotificationFollow): async def on_user_followed(self, notice: NotificationFollow):
if self.tg_user: await send_user_followed(
await send_user_followed( self.tg_user.chat_id, notice, self.tg_user.notice_topic
self.tg_user.chat_id, notice, self.tg_user.notice_topic )
)
async def on_follow_request(self, notice: NotificationFollowRequest): async def on_follow_request(self, notice: NotificationFollowRequest):
if self.tg_user: await send_follow_request(
await send_follow_request( self.tg_user.chat_id, notice, self.tg_user.notice_topic
self.tg_user.chat_id, notice, self.tg_user.notice_topic )
)
async def on_follow_request_accept(self, notice: NotificationFollowRequest): async def on_follow_request_accept(self, notice: NotificationFollowRequest):
if self.tg_user: await send_follow_request_accept(
await send_follow_request_accept( self.tg_user.chat_id, notice, self.tg_user.notice_topic
self.tg_user.chat_id, notice, self.tg_user.notice_topic )
)
async def on_chat(self, message: ChatMessage): async def on_chat(self, message: ChatMessage):
if self.tg_user: await send_chat_message(
await send_chat_message( self.tg_user.host, self.tg_user.chat_id, message, self.tg_user.notice_topic
self.tg_user.chat_id, message, self.tg_user.notice_topic )
)
async def on_chat_unread_message(self, message: ChatMessage): async def on_chat_unread_message(self, message: ChatMessage):
await message.api.read() await message.api.read()
async def on_achievement_earned(self, notice: NotificationAchievement): async def on_achievement_earned(self, notice: NotificationAchievement):
if self.tg_user: await send_achievement_earned(
await send_achievement_earned( self.tg_user.chat_id, notice, self.tg_user.notice_topic
self.tg_user.chat_id, notice, self.tg_user.notice_topic )
)
misskey_bot_map: dict[int, MisskeyBot] = {} misskey_bot_map: dict[int, MisskeyBot] = {}
@ -99,16 +94,16 @@ async def run(user: User):
misskey = await create_or_get_misskey_bot(user) misskey = await create_or_get_misskey_bot(user)
try: try:
logs.info(f"尝试启动 Misskey Bot WS 任务 {user.user_id}") logs.info(f"尝试启动 Misskey Bot WS 任务 {user.user_id}")
await misskey.start(misskey_url, user.token) await misskey.start(f"wss://{user.host}", user.token)
except ClientConnectorError: except ClientConnectorError:
await sleep(3) await sleep(3)
await run(user) await run(user)
async def test_token(token: str) -> bool: async def test_token(host: str, token: str) -> bool:
try: try:
logs.info(f"验证 Token {token}") logs.info(f"验证 Token {host} {token}")
client = MisskeyClient(misskey_host, token) client = MisskeyClient(f"https://{host}", token)
await client.http.login() await client.http.login()
await client.http.close_session() await client.http.close_session()
return True return True
@ -124,7 +119,7 @@ async def rerun_misskey_bot(user_id: int) -> bool:
user = await UserAction.get_user_if_ok(user_id) user = await UserAction.get_user_if_ok(user_id)
if not user: if not user:
return False return False
if not await test_token(user.token): if not await test_token(user.host, user.token):
await UserAction.set_user_status(user_id, TokenStatusEnum.INVALID_TOKEN) await UserAction.set_user_status(user_id, TokenStatusEnum.INVALID_TOKEN)
return False return False
bot.loop.create_task(run(user)) bot.loop.create_task(run(user))
@ -135,7 +130,7 @@ async def init_misskey_bot():
await sqlite.create_db_and_tables() await sqlite.create_db_and_tables()
count = 0 count = 0
for user in await UserAction.get_all_token_ok_users(): for user in await UserAction.get_all_token_ok_users():
if not await test_token(user.token): if not await test_token(user.host, user.token):
user.status = TokenStatusEnum.INVALID_TOKEN user.status = TokenStatusEnum.INVALID_TOKEN
await UserAction.update_user(user) await UserAction.update_user(user)
continue continue

View File

@ -12,8 +12,9 @@ class User(SQLModel, table=True):
__table_args__ = dict(mysql_charset="utf8mb4", mysql_collate="utf8mb4_general_ci") __table_args__ = dict(mysql_charset="utf8mb4", mysql_collate="utf8mb4_general_ci")
user_id: int = Field(primary_key=True) user_id: int = Field(primary_key=True)
host: str = Field(default="")
token: str = Field(default="") token: str = Field(default="")
status: TokenStatusEnum = Field(sa_column=Column(Enum(TokenStatusEnum))) status: TokenStatusEnum = Field(sa_column=Column(Enum(TokenStatusEnum)))
chat_id: int = Field(default=0) chat_id: int = Field(default=0, primary_key=True)
timeline_topic: int = Field(default=0) timeline_topic: int = Field(default=0)
notice_topic: int = Field(default=0) notice_topic: int = Field(default=0)

View File

@ -35,6 +35,7 @@ class UserAction:
.where(User.timeline_topic != 0) .where(User.timeline_topic != 0)
.where(User.notice_topic != 0) .where(User.notice_topic != 0)
.where(User.token != "") .where(User.token != "")
.where(User.host != "")
) )
results = await session.exec(statement) results = await session.exec(statement)
return user[0] if (user := results.first()) else None return user[0] if (user := results.first()) else None
@ -80,12 +81,22 @@ class UserAction:
return True return True
@staticmethod @staticmethod
async def change_user_token(user_id: int, token: str) -> bool: async def change_user_host(user_id: int, host: str) -> bool:
user = await UserAction.get_user_by_id(user_id) user = await UserAction.get_user_by_id(user_id)
if not user: if not user:
user = User( user = User(
user_id=user_id, token=token, status=TokenStatusEnum.STATUS_SUCCESS user_id=user_id, host=host, status=TokenStatusEnum.INVALID_TOKEN
) )
user.host = host
user.status = TokenStatusEnum.INVALID_TOKEN
await UserAction.update_user(user)
return True
@staticmethod
async def change_user_token(user_id: int, token: str) -> bool:
user = await UserAction.get_user_by_id(user_id)
if not user:
return False
user.token = token user.token = token
user.status = TokenStatusEnum.STATUS_SUCCESS user.status = TokenStatusEnum.STATUS_SUCCESS
await UserAction.update_user(user) await UserAction.update_user(user)

View File

@ -1,7 +1,8 @@
from pathlib import Path
from sqlmodel import SQLModel from sqlmodel import SQLModel
from models.models.user import User from models.models.user import User
from pathlib import Path
__all__ = ["User", "Sqlite"] __all__ = ["User", "Sqlite"]

View File

@ -1,7 +1,6 @@
from models.services.scheduler import scheduler
from defs.announcement import get_unread_announcements from defs.announcement import get_unread_announcements
from misskey_init import misskey_bot_map from misskey_init import misskey_bot_map
from models.services.scheduler import scheduler
@scheduler.scheduled_job("interval", minutes=15, id="check_announcement") @scheduler.scheduled_job("interval", minutes=15, id="check_announcement")

View File

@ -1,9 +1,8 @@
from pyrogram import Client, filters from pyrogram import Client, filters
from pyrogram.types import Message from pyrogram.types import Message
from models.services.user import UserAction
from misskey_init import rerun_misskey_bot from misskey_init import rerun_misskey_bot
from models.services.user import UserAction
async def pre_check(message: Message): async def pre_check(message: Message):

View File

@ -1,4 +1,5 @@
import contextlib import contextlib
from mipac.errors import ( from mipac.errors import (
InternalErrorError, InternalErrorError,
AlreadyFollowingError, AlreadyFollowingError,

View File

@ -1,22 +1,25 @@
from pyrogram import Client, filters from pyrogram import Client, filters
from pyrogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton from pyrogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton
from glover import misskey_host, web_domain, misskey_domain from defs.check_node import get_host, check_host
from glover import web_domain
from init import bot from init import bot
from misskey_init import test_token, rerun_misskey_bot from misskey_init import test_token, rerun_misskey_bot
from models.services.user import UserAction from models.services.user import UserAction
des = f"""欢迎使用 {bot.me.first_name},这是一个用于在 Telegram 上使用 Misskey 的机器人。按下方教程开始使用: des = f"""欢迎使用 {bot.me.first_name},这是一个用于在 Telegram 上使用 Misskey 的机器人。按下方教程开始使用:
1. 点击下方按钮绑定 Misskey 账号 1. 使用 `/start https://[misskey_domain]` 设置账号所在 Misskey 实例地址仅支持 https 链接
2. 在论坛群组中使用 /bind_timeline 绑定 Timeline 话题接收时间线更新 2. 点击 start 之后回复你的按钮绑定所在 Misskey 实例的账号
3. 在论坛群组中使用 /bind_notice 绑定 Notice 话题接收通知 3. 在论坛群组中使用 /bind_timeline 绑定 Timeline 话题接收时间线更新
4. 在论坛群组中使用 /bind_notice 绑定 Notice 话题接收通知
至此你便可以在 Telegram 接收 Misskey 消息同时你可以私聊我使用 /status 查看 Bot 运行状态 至此你便可以在 Telegram 接收 Misskey 消息同时你可以私聊我使用 /status 查看 Bot 运行状态
Bot 仅支持绑定 {misskey_host} Misskey 账号""" Bot 仅支持 Misskey V13 实例的账号"""
async def finish_check(message: Message): async def finish_check(message: Message):
@ -26,8 +29,40 @@ async def finish_check(message: Message):
await message.reply("Token 设置完成,请绑定群组。", quote=True) await message.reply("Token 设置完成,请绑定群组。", quote=True)
def gen_url(): def gen_url(domain: str):
return f"https://{web_domain}/gen?host={misskey_domain}&back_host={web_domain}&username={bot.me.username}" return f"https://{web_domain}/gen?host={domain}&back_host={web_domain}&username={bot.me.username}"
async def change_host(message: Message, token_or_host: str):
host = get_host(token_or_host)
if await check_host(host):
await UserAction.change_user_host(message.from_user.id, host)
await message.reply(
"Host 验证成功,请点击下方按钮绑定账号。",
quote=True,
reply_markup=InlineKeyboardMarkup(
[
[
InlineKeyboardButton(text="绑定 Misskey 账号", url=gen_url(host)),
]
]
),
)
else:
await message.reply("Host 验证失败,请检查 Host 是否正在运行 Misskey V13", quote=True)
async def change_token(message: Message, token_or_host: str):
if user := await UserAction.get_user_by_id(message.from_user.id):
if user.host:
if await test_token(user.host, token_or_host):
await UserAction.change_user_token(message.from_user.id, token_or_host)
await message.reply(
"Token 验证成功,绑定账号完成。\n当你撤销此登录时,你可以重新点击按钮授权。", quote=True
)
await finish_check(message)
else:
await message.reply("Token 验证失败,请检查 Token 是否正确", quote=True)
@Client.on_message(filters.incoming & filters.private & filters.command(["start"])) @Client.on_message(filters.incoming & filters.private & filters.command(["start"]))
@ -36,27 +71,13 @@ async def start_command(_: Client, message: Message):
回应 start 回应 start
""" """
if len(message.command) == 2: if len(message.command) == 2:
token = message.command[1] token_or_host = message.command[1]
if not token: if not token_or_host:
await message.reply(des, quote=True) await message.reply(des, quote=True)
return return
if await test_token(token): if token_or_host.startswith("https://"):
await UserAction.change_user_token(message.from_user.id, token) await change_host(message, token_or_host)
await message.reply( return
"Token 验证成功,绑定账号完成。\n" "当你撤销此登录时,你可以重新点击按钮授权。", quote=True await change_token(message, token_or_host)
)
await finish_check(message)
else:
await message.reply("Token 验证失败,请检查 Token 是否正确", quote=True)
return return
await message.reply( await message.reply(des, quote=True)
des,
quote=True,
reply_markup=InlineKeyboardMarkup(
[
[
InlineKeyboardButton(text="绑定 Misskey 账号", url=gen_url()),
]
]
),
)

View File

@ -2,7 +2,6 @@ from pyrogram import filters, Client
from pyrogram.types import Message from pyrogram.types import Message
from glover import admin from glover import admin
from misskey_init import get_misskey_bot, rerun_misskey_bot, misskey_bot_map from misskey_init import get_misskey_bot, rerun_misskey_bot, misskey_bot_map
from models.models.user import TokenStatusEnum from models.models.user import TokenStatusEnum
from models.services.user import UserAction from models.services.user import UserAction

View File

@ -5,4 +5,4 @@ apscheduler==3.10.1
sqlalchemy==1.4.41 sqlalchemy==1.4.41
sqlmodel==0.0.8 sqlmodel==0.0.8
aiosqlite==0.19.0 aiosqlite==0.19.0
PyYAML==6.0 PyYAML==6.0.1