Support sub

This commit is contained in:
xtaodada 2022-03-20 13:02:17 +08:00
parent fe641bbe42
commit 4a39d35f15
Signed by: xtaodada
GPG Key ID: 4CBB3F4FA8C85659
13 changed files with 512 additions and 46 deletions

8
ci.py
View File

@ -1,3 +1,4 @@
import json
from configparser import RawConfigParser
from os import sep, mkdir
from os.path import exists
@ -10,13 +11,14 @@ from sqlitedict import SqliteDict
if not exists("data"):
mkdir("data")
sqlite = SqliteDict(f"data{sep}data.sqlite", autocommit=True)
sqlite = SqliteDict(f"data{sep}data.sqlite", encode=json.dumps, decode=json.loads, autocommit=True)
# data.sqlite 结构如下:
# {
# "module 名称": {
# "subscribes": [订阅id],
# "msg_link": str,
# "subscribes": List[订阅id: int],
# },
# "update_time": "",
# "update_time": str,
# }
# 读取配置文件
config = RawConfigParser()

0
defs/decorators.py Normal file
View File

View File

@ -28,25 +28,40 @@ def gen_button(data: Module) -> InlineKeyboardMarkup:
InlineKeyboardButton(
"订阅",
url=f"https://t.me/{me.username}?start={data.name.replace('.', '_')}"), ]])
else:
data_.extend([[InlineKeyboardButton("主页", url=data.homepageUrl),
InlineKeyboardButton(
"订阅",
url=f"https://t.me/{me.username}?start={data.name.replace('.', '_')}"), ]])
return InlineKeyboardMarkup(data_)
def format_text(text: str) -> str:
text = text.strip()
for i in ["/", " ", "-", "@", ]:
text = text.replace(i, "_")
for i in ["", "", "[", "]"]:
text = text.replace(i, "")
return text.strip()
def gen_tags(data: Module) -> str:
text = f"#{data.description.split()[0]} "
text += f"#{data.collaborators[0]} "
text = f"#{format_text(data.description.split()[0])} "
if data.collaborators:
text += f"#{format_text(data.collaborators[0])} "
return text
def gen_update_msg(data: Module) -> TrackMessage:
text = template.format(gen_tags(data), data.name, data.description, data.latestRelease,
data.updatedAt,
data.releases[0].description.replace(r"\r\n", "\n"))
data.releases[0].description.replace(r"\r\n", "\n")[:1000].strip())
url = None
name = data.name.replace('.', '_') + "-" + data.latestRelease
name = None
if data.releases:
if data.releases[0].releaseAssets:
url = data.releases[0].releaseAssets[0].url
mime = data.releases[0].releaseAssets[0].name.split(".")[-1:][0]
name += "." + mime
name = data.name.replace('.', '_') + "-" + data.latestRelease + "." + mime
button = gen_button(data)
return TrackMessage(text, url, name, button)

View File

@ -1,7 +1,7 @@
from os import sep
from os.path import exists
from shutil import copyfile
from typing import List
from typing import List, Optional
from ci import client, sqlite
from json import load
@ -9,17 +9,25 @@ from defs.format_time import now_time
from defs.utils import Module
new_modules: List[Module] = []
new_modules_index: dict = {}
old_modules: List[Module] = []
old_modules_index: dict = {}
if exists(f"data{sep}modules.json"):
with open(f"data{sep}modules.json", "r", encoding="utf-8") as file:
new_modules = load(file)
temp_data = load(file)
for temp in temp_data:
new_modules.append(Module(temp))
if exists(f"data{sep}old_modules.json"):
with open(f"data{sep}old_modules.json", "r", encoding="utf-8") as file:
old_modules = load(file)
temp_data = load(file)
for temp in temp_data:
old_modules.append(Module(temp))
new_modules_index = {i.name: i for i in new_modules}
old_modules_index = {i.name: i.latestRelease for i in old_modules}
async def update_data() -> None:
global new_modules, old_modules
global new_modules, old_modules, new_modules_index
if exists(f"data{sep}modules.json"):
copyfile(f"data{sep}modules.json", f"data{sep}old_modules.json")
data = await client.get("https://modules.lsposed.org/modules.json")
@ -30,14 +38,16 @@ async def update_data() -> None:
new_modules = []
for i in data:
new_modules.append(Module(i))
new_modules_index = {i.name: i for i in new_modules}
sqlite["update_time"] = now_time()
def compare() -> List[Module]:
global old_modules_index
data = []
old_data = {i.name: i.latestRelease for i in old_modules}
old_modules_index = {i.name: i.latestRelease for i in old_modules}
for i in new_modules:
if i.latestRelease != old_data.get(i.name, ""):
if i.latestRelease != old_modules_index.get(i.name, ""):
data.append(i)
return data
@ -48,3 +58,28 @@ async def download(url: str, name: str) -> str:
with open(f"data{sep}{name}", 'wb') as f:
f.write(content)
return f"data{sep}{name}"
def from_name_to_module(name: str) -> Optional[Module]:
return new_modules_index.get(name, None)
def from_list_to_name(data: List) -> str:
data_ = ""
for i in data:
name = new_modules_index.get(i, None)
if isinstance(name, Module):
data_ += f"\n{name.name}{name.description}"
return data_
def from_keyword_to_module(keyword: str) -> Optional[Module]:
for value in new_modules:
data = value.name + value.description + value.url + value.homepageUrl + value.summary + \
value.sourceUrl
if value.scope:
for i in value.scope:
data += i
if keyword in data:
return value
return None

97
defs/subs.py Normal file
View File

@ -0,0 +1,97 @@
import traceback
from asyncio import sleep
from random import uniform
from pyrogram.errors import FloodWait, ButtonUrlInvalid, BadRequest
from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton
from ci import app, sqlite, me
from defs.source import from_list_to_name
from defs.utils import Module
subs_msg = """
<code>{}</code> 有新的更新
<b>版本</b><code>{}</code>
<b>更新时间</b><code>{}</code>
"""
subs_list_msg = """
<b>您订阅了</b>{}
"""
subs_list_no_msg = """
<b>您订阅了个寂寞</b>
"""
def gen_subs_button(data: Module, link: str) -> InlineKeyboardMarkup:
data_ = [[InlineKeyboardButton("详情", url=link),
InlineKeyboardButton("退订",
url=f"https://t.me/{me.username}?start=un-{data.name.replace('.', '_')}"), ]]
return InlineKeyboardMarkup(data_)
def gen_back_button() -> InlineKeyboardMarkup:
return InlineKeyboardMarkup([[InlineKeyboardButton("返回", callback_data="help"), ]])
def gen_subs_msg(cid: int) -> str:
data_ = []
for key, value in sqlite.items():
if key == "update_time":
continue
data = value.get("subscribes", [])
if cid in data:
data_.append(key)
if data_:
text = subs_list_msg.format(from_list_to_name(data_))
else:
text = subs_list_no_msg
return text
async def send_subs_msg(cid: int, data: Module, link: str):
return await app.send_message(cid,
subs_msg.format(data.name, data.latestRelease, data.updatedAt),
reply_markup=gen_subs_button(data, link))
async def send_to_subscribes(data: Module):
users = sqlite.get(data.name, {}).get("subscribes", [])
link = sqlite.get(data.name, {}).get("msg_link", "https://t.me/lsposed_Modules_Updates_Tracker")
for i in users:
try:
await send_subs_msg(i, data, link)
except FloodWait as e:
print(f"Send subscribes msg flood - Sleep for {e.x} second(s)")
await sleep(uniform(0.5, 1.0))
await send_subs_msg(i, data, link)
except ButtonUrlInvalid:
print(f"Send button error")
await app.send_message(i, subs_msg.format(data.name, data.latestRelease, data.updatedAt), )
except BadRequest:
users.remove(i)
except Exception as e:
traceback.print_exc()
sqlite[data.name]["subscribes"] = users
def add_to_subs(cid: int, data: Module) -> bool:
users = sqlite.get(data.name, {}).get("subscribes", [])
if cid not in users:
users.append(cid)
data_ = sqlite.get(data.name, {"subscribes": []})
data_["subscribes"] = users
sqlite[data.name] = data_
return True
return False
def remove_from_subs(cid: int, data: Module) -> bool:
users = sqlite.get(data.name, {}).get("subscribes", [])
if cid in users:
users.remove(cid)
data_ = sqlite.get(data.name, {"subscribes": []})
data_["subscribes"] = users
sqlite[data.name] = data_
return True
return False

View File

@ -27,10 +27,10 @@ class Release:
class Module:
def __init__(self, data: dict):
self.name: str = data["name"]
self.description: str = data["description"]
self.url: str = data["url"]
self.description: str = data["description"] if data["description"] else ""
self.url: str = data["url"] if data["url"] else ""
self.homepageUrl: str = data["homepageUrl"] if data["homepageUrl"] else data["url"]
self.sourceUrl: str = data["sourceUrl"]
self.sourceUrl: str = data["sourceUrl"] if data["sourceUrl"] else ""
self.hide: bool = data["hide"]
self.createdAt: str = strf_time(data["createdAt"])
self.updatedAt: str = strf_time(data["updatedAt"])
@ -47,7 +47,8 @@ class Module:
for i in data["releases"]:
releases.append(Release(i))
self.releases: List[Release] = releases
self.summary = data["summary"]
self.summary: str = data["summary"] if data["summary"] else ""
self.scope: List[str] = data["scope"] if data["scope"] else []
class TrackMessage:

26
plugins/callback.py Normal file
View File

@ -0,0 +1,26 @@
from pyrogram import Client, filters
from pyrogram.types import CallbackQuery, InlineKeyboardMarkup, InlineKeyboardButton
from defs.subs import gen_subs_msg, gen_back_button
from plugins.help import help_msg
@Client.on_callback_query(filters.regex("help"))
async def help_set(_, query: CallbackQuery):
await query.edit_message_text(
help_msg,
reply_markup=InlineKeyboardMarkup(
[[InlineKeyboardButton("订阅", callback_data="subs")]]
),
disable_web_page_preview=True,
)
@Client.on_callback_query(filters.regex("subs"))
async def subs_set(_, query: CallbackQuery):
text = gen_subs_msg(query.from_user.id)
await query.edit_message_text(
text,
reply_markup=gen_back_button(),
disable_web_page_preview=True,
)

32
plugins/help.py Normal file
View File

@ -0,0 +1,32 @@
from pyrogram import Client, filters
from pyrogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton
help_msg = """
下面是我学会了的指令列表
👩🏻💼 » /subscribe <code>包名|关键词|作用域|开源地址|模块地址</code> - 订阅更新
<code>/subscribe nil.nadph.qnotified</code>
<code>/subscribe QNotified</code>
<code>/subscribe com.tencent.mobileqq</code>
<code>/subscribe https://github.com/ferredoxin/QNotified</code>
<code>/subscribe https://modules.lsposed.org/module/nil.nadph.qnotified</code>
👩🏻💼 » /unsubscribe <code>包名|关键词|作用域|开源地址|模块地址</code> - 取消订阅更新
👩🏻💼 » /subscription - 列出您当前的订阅
👩🏻💼 » /info <code>包名|关键词|作用域|开源地址|模块地址</code> - 查询模块信息
"""
@Client.on_message(filters.incoming & filters.private &
filters.command(["help"]))
async def help_command(_: Client, message: Message):
await message.reply(
help_msg,
reply_markup=InlineKeyboardMarkup(
[[InlineKeyboardButton("订阅", callback_data="subs")]]
),
disable_web_page_preview=True,
quote=True,
)

64
plugins/info.py Normal file
View File

@ -0,0 +1,64 @@
from pyrogram import Client, filters
from pyrogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton
from ci import sqlite, me
from defs.source import from_keyword_to_module
from defs.utils import Module
from plugins.start import not_found_msg
info_help_msg = """
👩🏻💼 » /info <code>包名|关键词|作用域|开源地址|模块地址</code> - 查询模块信息
<code>/info nil.nadph.qnotified</code>
<code>/info QNotified</code>
<code>/info com.tencent.mobileqq</code>
<code>/info https://github.com/ferredoxin/QNotified</code>
<code>/info https://modules.lsposed.org/module/nil.nadph.qnotified</code>
"""
module_msg = """
<b>{}</b>
<b>简介</b><code>{}</code>
<b>概要</b><code>{}</code>
<b>版本</b><code>{}</code>
<b>创建时间</b><code>{}</code>
<b>更新时间</b><code>{}</code>
<b>作用域</b>
<code>
{}
</code>
@lsposed_Modules_Updates_Tracker | @lsposed_Geeks_Bot
"""
def gen_info_button(data: Module) -> InlineKeyboardMarkup:
msg_link = sqlite.get(data.name, {}).get("msg_link", "https://t.me/lsposed_Modules_Updates_Tracker")
data_ = [[InlineKeyboardButton("详情", url=msg_link),
InlineKeyboardButton("订阅",
url=f"https://t.me/{me.username}?start={data.name.replace('.', '_')}"), ]]
return InlineKeyboardMarkup(data_)
@Client.on_message(filters.incoming & filters.private &
filters.command(["info"]))
async def info_command(_: Client, message: Message):
if len(message.command) == 1:
await message.reply(info_help_msg, quote=True)
else:
data = " ".join(message.command[1:])
module = from_keyword_to_module(data)
if module:
await message.reply(
module_msg.format(
module.name,
module.description,
module.summary,
module.latestRelease,
module.createdAt,
module.updatedAt,
"\n ".join(module.scope),
),
reply_markup=gen_info_button(module),
quote=True,
)
else:
await message.reply(not_found_msg.format(data), quote=True)

67
plugins/inline.py Normal file
View File

@ -0,0 +1,67 @@
from pyrogram import Client, emoji
from pyrogram.types import InlineQuery, InputTextMessageContent, InlineQueryResultArticle
from defs.source import new_modules, new_modules_index
from plugins.info import module_msg, gen_info_button
@Client.on_inline_query()
async def inline_process(_: Client, query: InlineQuery):
data = []
text = query.query.split()
nums = 0
if not new_modules_index:
return
data_ = new_modules_index
for key, module in data_.items():
if len(text) == 0:
data.append(InlineQueryResultArticle(
(module.summary if module.summary else module.description) + " - " + module.name,
InputTextMessageContent(module_msg.format(
module.name,
module.description,
module.summary,
module.latestRelease,
module.createdAt,
module.updatedAt,
"\n ".join(module.scope),
)),
reply_markup=gen_info_button(module),
))
nums += 1
else:
name = module.name + module.description + module.url + module.homepageUrl + module.summary + \
module.sourceUrl
if module.scope:
for i in module.scope:
name += i
skip = False
for i in text:
if i not in name:
skip = True
if not skip:
data.append(InlineQueryResultArticle(
(module.summary if module.summary else module.description) + " - " + module.name,
InputTextMessageContent(module_msg.format(
module.name,
module.description,
module.summary,
module.latestRelease,
module.createdAt,
module.updatedAt,
"\n ".join(module.scope),
)),
reply_markup=gen_info_button(module),
))
nums += 1
if nums >= 50:
break
if nums == 0:
return await query.answer(
results=[],
switch_pm_text=f'{emoji.CROSS_MARK} 字符串 "{" ".join(text)}" 没有搜索到任何结果',
switch_pm_parameter="help",
)
await query.answer(data,
switch_pm_text=f'{emoji.KEY} 搜索了 {len(new_modules)} 个模块',
switch_pm_parameter="help", )

View File

@ -1,6 +1,8 @@
from pyrogram import Client, filters
from pyrogram.types import Message
from pyrogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton
from ci import me
from defs.source import from_name_to_module
from defs.subs import add_to_subs, remove_from_subs
des = """
你好{} 我是 [{}]({})一个为 lsposed 用户打造的一体化机器人
@ -9,6 +11,29 @@ des = """
点击下面的帮助按钮来查看使用方法
加入 [我的频道](https://t.me/lsposed_Modules_Updates_Tracker) 获取关于 lsposed 模块的所有更新和公告
"""
unsub_msg = """
<b>成功退订了</b> <code>{}</code> <b>的更新</b>
"""
not_sub_msg = """
<b>你好像没有订阅</b> <code>{}</code> <b>的更新</b>
"""
sub_msg = """
<b>成功订阅了</b> <code>{}</code> <b>的更新</b>
"""
already_sub_msg = """
<b>已经订阅过</b> <code>{}</code> <b>的更新</b>
"""
not_found_msg = """
<b>没有找到名为</b> <code>{}</code> <b>的模块</b>
"""
def gen_help_button() -> InlineKeyboardMarkup:
data_ = [[InlineKeyboardButton("📢 官方频道", url="https://t.me/lsposed_Modules_Updates_Tracker"),
InlineKeyboardButton("💬 官方群组", url="https://t.me/Invite_Challenge_Bot?start=1"), ],
[InlineKeyboardButton("❓ 阅读帮助", callback_data="help")],
]
return InlineKeyboardMarkup(data_)
@Client.on_message(filters.incoming & filters.private &
@ -17,6 +42,35 @@ async def start_command(client: Client, message: Message):
"""
回应消息
"""
if len(message.command) == 1:
await message.reply(des.format(message.from_user.mention(),
me.name,
f"https://t.me/{me.username}"), quote=True,)
f"https://t.me/{me.username}"),
reply_markup=gen_help_button(),
quote=True, )
else:
data = message.command[1].replace("_", ".")
if data.startswith("un-"):
# 退订
name = data[3:]
data = from_name_to_module(name)
if data:
success = remove_from_subs(message.from_user.id, data)
if success:
await message.reply(unsub_msg.format(data.name), quote=True)
else:
await message.reply(not_sub_msg.format(data.name), quote=True)
else:
await message.reply(not_found_msg.format(name), quote=True)
else:
# 订阅
name = data
data = from_name_to_module(data)
if data:
success = add_to_subs(message.from_user.id, data)
if success:
await message.reply(sub_msg.format(data.name), quote=True)
else:
await message.reply(already_sub_msg.format(data.name), quote=True)
else:
await message.reply(not_found_msg.format(name), quote=True)

66
plugins/subs.py Normal file
View File

@ -0,0 +1,66 @@
from pyrogram import Client, filters
from pyrogram.types import Message
from defs.source import from_keyword_to_module
from defs.subs import gen_subs_msg, gen_back_button, add_to_subs, remove_from_subs
from plugins.start import sub_msg, not_found_msg, already_sub_msg, unsub_msg, not_sub_msg
sub_help_msg = """
👩🏻💼 » /subscribe <code>包名|关键词|作用域|开源地址|模块地址</code> - 订阅更新
<code>/subscribe nil.nadph.qnotified</code>
<code>/subscribe QNotified</code>
<code>/subscribe com.tencent.mobileqq</code>
<code>/subscribe https://github.com/ferredoxin/QNotified</code>
<code>/subscribe https://modules.lsposed.org/module/nil.nadph.qnotified</code>
"""
unsub_help_msg = """
👩🏻💼 » /unsubscribe <code>包名|关键词|作用域|开源地址|模块地址</code> - 取消订阅更新
<code>/unsubscribe nil.nadph.qnotified</code>
<code>/unsubscribe QNotified</code>
<code>/unsubscribe com.tencent.mobileqq</code>
<code>/unsubscribe https://github.com/ferredoxin/QNotified</code>
<code>/unsubscribe https://modules.lsposed.org/module/nil.nadph.qnotified</code>
"""
@Client.on_message(filters.incoming & filters.private &
filters.command(["subscription"]))
async def subscription_command(_: Client, message: Message):
text = gen_subs_msg(message.from_user.id)
await message.reply(text, reply_markup=gen_back_button(), quote=True, )
@Client.on_message(filters.incoming & filters.private &
filters.command(["subscribe"]))
async def sub_command(_: Client, message: Message):
if len(message.command) == 1:
await message.reply(sub_help_msg, reply_markup=gen_back_button(), quote=True)
else:
data = " ".join(message.command[1:])
module = from_keyword_to_module(data)
if module:
success = add_to_subs(message.from_user.id, module)
if success:
await message.reply(sub_msg.format(module.name), quote=True)
else:
await message.reply(already_sub_msg.format(module.name), quote=True)
else:
await message.reply(not_found_msg.format(data), quote=True)
@Client.on_message(filters.incoming & filters.private &
filters.command(["unsubscribe"]))
async def un_sub_command(_: Client, message: Message):
if len(message.command) == 1:
await message.reply(unsub_help_msg, reply_markup=gen_back_button(), quote=True)
else:
data = " ".join(message.command[1:])
module = from_keyword_to_module(data)
if module:
success = remove_from_subs(message.from_user.id, module)
if success:
await message.reply(unsub_msg.format(module.name), quote=True)
else:
await message.reply(not_sub_msg.format(module.name), quote=True)
else:
await message.reply(not_found_msg.format(data), quote=True)

View File

@ -6,21 +6,22 @@ from random import uniform
from pyrogram.errors import FloodWait, ButtonUrlInvalid
from pyrogram.types import Message
from ci import app, scheduler, channel_id, admin_id
from ci import app, scheduler, channel_id, admin_id, sqlite
from pyrogram import Client, filters
from defs.msg import gen_update_msg
from defs.source import update_data, compare, download
from defs.subs import send_to_subscribes
async def send_track_msg(file, track_msg):
async def send_track_msg(file, track_msg) -> Message:
if file:
await app.send_document(channel_id, file,
caption=track_msg.text[:1000],
return await app.send_document(channel_id, file,
caption=track_msg.text,
force_document=True,
parse_mode="html",
reply_markup=track_msg.button)
else:
await app.send_message(channel_id, track_msg.text[:1000],
return await app.send_message(channel_id, track_msg.text,
parse_mode="html",
reply_markup=track_msg.button)
@ -31,17 +32,18 @@ async def run_every_30_minute():
need_update = compare()
for i in need_update:
track_msg = gen_update_msg(i)
msg = None
if track_msg.url:
file = await download(track_msg.url, track_msg.name)
try:
await send_track_msg(file, track_msg)
msg = await send_track_msg(file, track_msg)
except FloodWait as e:
print(f"Send document flood - Sleep for {e.x} second(s)")
await sleep(uniform(0.5, 1.0))
await send_track_msg(file, track_msg)
msg = await send_track_msg(file, track_msg)
except ButtonUrlInvalid:
print(f"Send button error")
await app.send_document(channel_id, file,
msg = await app.send_document(channel_id, file,
caption=track_msg.text,
force_document=True,
parse_mode="html", )
@ -53,17 +55,22 @@ async def run_every_30_minute():
pass
else:
try:
await send_track_msg(None, track_msg)
msg = await send_track_msg(None, track_msg)
except FloodWait as e:
print(f"Send document flood - Sleep for {e.x} second(s)")
await sleep(e.x + uniform(0.5, 1.0))
await send_track_msg(None, track_msg)
msg = await send_track_msg(None, track_msg)
except ButtonUrlInvalid:
print(f"Send button error")
await app.send_message(channel_id, track_msg.text, parse_mode="html",)
msg = await app.send_message(channel_id, track_msg.text, parse_mode="html", )
except Exception as e:
traceback.print_exc()
await sleep(uniform(0.5, 2.0))
data_ = sqlite.get(i.name, {"msg_link": ""})
data_["msg_link"] = msg.link
sqlite[i.name] = data_
await send_to_subscribes(i)
await sleep(uniform(0.5, 2.0))
@Client.on_message(filters.incoming & filters.private & filters.chat(admin_id) &