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

@ -81,77 +81,77 @@
], ],
"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": [
@ -195,7 +195,7 @@
"" ""
], ],
"following100": [ "following100": [
"我的朋友很多", "胜友如云",
"关注超过 100 人", "关注超过 100 人",
"" ""
], ],

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,38 +44,34 @@ 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.chat_id, message, self.tg_user.notice_topic self.tg_user.host, 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
) )
@ -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(
"Token 验证成功,绑定账号完成。\n" "当你撤销此登录时,你可以重新点击按钮授权。", quote=True
)
await finish_check(message)
else:
await message.reply("Token 验证失败,请检查 Token 是否正确", quote=True)
return return
await message.reply( await change_token(message, token_or_host)
des, return
quote=True, await message.reply(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