Begin a project

This commit is contained in:
xtaodada 2022-03-20 01:44:35 +08:00
parent 201196733a
commit bd2d03f9e7
Signed by: xtaodada
GPG Key ID: 4CBB3F4FA8C85659
12 changed files with 366 additions and 0 deletions

6
.gitignore vendored
View File

@ -95,6 +95,9 @@ venv/
ENV/ ENV/
env.bak/ env.bak/
venv.bak/ venv.bak/
config.ini
*session*
data/
# Spyder project settings # Spyder project settings
.spyderproject .spyderproject
@ -113,3 +116,6 @@ dmypy.json
# Pyre type checker # Pyre type checker
.pyre/ .pyre/
# pycharm
.idea/

52
ci.py Normal file
View File

@ -0,0 +1,52 @@
from configparser import RawConfigParser
from os import sep, mkdir
from os.path import exists
import pyromod.listen
from pyrogram import Client
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from httpx import AsyncClient, get
from sqlitedict import SqliteDict
if not exists("data"):
mkdir("data")
sqlite = SqliteDict(f"data{sep}data.sqlite", autocommit=True)
# data.sqlite 结构如下:
# {
# "module 名称": {
# "subscribes": [订阅id],
# },
# "update_time": "",
# }
# 读取配置文件
config = RawConfigParser()
config.read("config.ini")
bot_token: str = ""
admin_id: int = 0
channel_id: int = 0
bot_token = config.get("basic", "bot_token", fallback=bot_token)
admin_id = config.getint("basic", "admin", fallback=admin_id)
channel_id = config.getint("basic", "channel_id", fallback=channel_id)
""" Init httpx client """
# 使用自定义 UA
headers = {
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36"
}
client = AsyncClient(timeout=10.0, headers=headers)
# 自定义类型
class Bot:
def __init__(self, data: dict):
self.uid = data["id"]
self.username = data["username"]
self.name = data["first_name"]
me = Bot(get(f"https://api.telegram.org/bot{bot_token}/getme").json()["result"])
# 初始化客户端
scheduler = AsyncIOScheduler()
if not scheduler.running:
scheduler.configure(timezone="Asia/ShangHai")
scheduler.start()
app = Client("bot", bot_token=bot_token)

16
config.ini.example Normal file
View File

@ -0,0 +1,16 @@
[pyrogram]
api_id = 12345
api_hash = 0123456789abc0123456789abc
[basic]
bot_token = 111:abc
admin = 777000
channel_id = 0
[plugins]
root = plugins
[proxy]
enabled = False
hostname = 127.0.0.1
port = 1080

19
defs/format_time.py Normal file
View File

@ -0,0 +1,19 @@
from datetime import datetime, timedelta
date_format = "%Y-%m-%dT%H:%M:%SZ"
def strf_time(data: str) -> str:
# data = "2021-07-17T09:14:05Z"
ts = datetime.strptime(data, date_format)
# UTC+8
ts = ts + timedelta(hours=8)
return ts.strftime("%Y/%m/%d %H:%M:%S")
def now_time() -> str:
# UTC
ts = datetime.utcnow()
# UTC+8
ts = ts + timedelta(hours=8)
return ts.strftime("%Y/%m/%d %H:%M:%S")

50
defs/msg.py Normal file
View File

@ -0,0 +1,50 @@
from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton
from defs.utils import Module, TrackMessage
from ci import me
template = """
{}
<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_button(data: Module) -> InlineKeyboardMarkup:
data_ = []
if data.releases:
if data.releases[0].releaseAssets:
data_ = [[InlineKeyboardButton("⬇️ 下载", url=data.releases[0].releaseAssets[0].url)]]
data_.extend([[InlineKeyboardButton("Release", url=data.releases[0].url),
InlineKeyboardButton("主页", url=data.homepageUrl),
InlineKeyboardButton(
"订阅",
url=f"https://t.me/{me.username}?start={data.name.replace('.', '_')}"),]])
return InlineKeyboardMarkup(data_)
def gen_tags(data: Module) -> str:
text = f"#{data.description.split()[0]} "
text += f"#{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"))
url = None
if data.releases:
if data.releases[0].releaseAssets:
url = data.releases[0].releaseAssets[0].url
name = data.name.replace('.', '_') + "-" + data.latestRelease
button = gen_button(data)
return TrackMessage(text, url, name, button)

50
defs/source.py Normal file
View File

@ -0,0 +1,50 @@
from os import sep
from os.path import exists
from shutil import copyfile
from typing import List
from ci import client, sqlite
from json import load
from defs.format_time import now_time
from defs.utils import Module
new_modules: List[Module] = []
old_modules: List[Module] = []
if exists(f"data{sep}modules.json"):
with open(f"data{sep}modules.json", "r", encoding="utf-8") as file:
new_modules = load(file)
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)
async def update_data() -> None:
global new_modules, old_modules
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")
with open(f"data{sep}modules.json", "w", encoding="utf-8") as f:
f.write(data.text)
data = data.json()
old_modules = new_modules
new_modules = []
for i in data:
new_modules.append(Module(i))
sqlite["update_time"] = now_time()
def compare() -> List[Module]:
data = []
old_data = {i.name: i.latestRelease for i in old_modules}
for i in new_modules:
if i.latestRelease != old_data.get(i.name, ""):
data.append(i)
return data
async def download(url: str, name: str) -> str:
content = await client.get(url)
content = content.content
with open(f"data{sep}{name}", 'wb') as f:
f.write(content)
return f"data{sep}{name}"

58
defs/utils.py Normal file
View File

@ -0,0 +1,58 @@
from typing import List
from defs.format_time import strf_time
class Assets:
def __init__(self, data: dict):
self.name = data["name"]
self.url = data["downloadUrl"]
class Release:
def __init__(self, data: dict):
self.name: str = data["name"]
self.url: str = data["url"]
self.description: str = data["description"]
self.publishedAt: str = strf_time(data["publishedAt"])
self.tagName: str = data["tagName"]
self.isPrerelease: bool = data["isPrerelease"]
assets = []
if data["releaseAssets"]:
for i in data["releaseAssets"]:
assets.append(Assets(i))
self.releaseAssets: List[Assets] = assets
self.releaseAssetsLen = len(assets)
class Module:
def __init__(self, data: dict):
self.name: str = data["name"]
self.description: str = data["description"]
self.url: str = data["url"]
self.homepageUrl: str = data["homepageUrl"] if data["homepageUrl"] else data["url"]
self.sourceUrl: str = data["sourceUrl"]
self.hide: bool = data["hide"]
self.createdAt: str = strf_time(data["createdAt"])
self.updatedAt: str = strf_time(data["updatedAt"])
text = []
for i in data["collaborators"]:
if i["name"]:
text.append(i["name"])
else:
text.append(i["login"])
self.collaborators: List[str] = text
self.latestRelease: str = data["latestRelease"]
releases = []
if data["releases"]:
for i in data["releases"]:
releases.append(Release(i))
self.releases: List[Release] = releases
self.summary = data["summary"]
class TrackMessage:
def __init__(self, text, url, name, button):
self.text = text
self.url = url
self.name = name
self.button = button

6
main.py Normal file
View File

@ -0,0 +1,6 @@
import logging
from ci import app
# 日志记录
logging.basicConfig(level=logging.INFO)
app.run()

8
plugins/ping.py Normal file
View File

@ -0,0 +1,8 @@
from pyrogram import Client, filters
from pyrogram.types import Message
@Client.on_message(filters.incoming & filters.private &
filters.command(["ping", ]))
async def force_update(bot: Client, message: Message):
await message.reply("poi ~", quote=True)

22
plugins/start.py Normal file
View File

@ -0,0 +1,22 @@
from pyrogram import Client, filters
from pyrogram.types import Message
from ci import me
des = """
你好{} 我是 [{}]({})一个为 lsposed 用户打造的一体化机器人
我可以帮助你获取最新的 lsposed 模块的下载链接和信息查询
点击下面的帮助按钮来查看使用方法
加入 [我的频道](https://t.me/lsposed_Modules_Updates_Tracker) 获取关于 lsposed 模块的所有更新和公告
"""
@Client.on_message(filters.incoming & filters.private &
filters.command(["start"]))
async def start_command(client: Client, message: Message):
"""
回应消息
"""
await message.reply(des.format(message.from_user.mention(),
me.name,
f"https://t.me/{me.username}"), quote=True,)

73
plugins/track.py Normal file
View File

@ -0,0 +1,73 @@
import traceback
from asyncio import sleep
from os import remove
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 pyrogram import Client, filters
from defs.msg import gen_update_msg
from defs.source import update_data, compare, download
async def send_track_msg(file, track_msg):
if file:
await app.send_document(channel_id, file,
caption=track_msg.text,
file_name=track_msg.name,
force_document=True,
parse_mode="html",
reply_markup=track_msg.button)
else:
await app.send_message(channel_id, track_msg.text,
parse_mode="html",
reply_markup=track_msg.button)
# @scheduler.scheduled_job("cron", minute="*/30", id="0")
async def run_every_30_minute():
await update_data()
need_update = compare()
for i in need_update:
track_msg = gen_update_msg(i)
if track_msg.url:
file = await download(track_msg.url, track_msg.name)
try:
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)
except ButtonUrlInvalid:
print(f"Send button error")
await app.send_document(channel_id, file,
caption=track_msg.text,
file_name=track_msg.name,
force_document=True,
parse_mode="html",)
except Exception as e:
traceback.print_exc()
try:
remove(file)
except FileNotFoundError:
pass
else:
try:
await send_track_msg(None, 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(None, track_msg)
except ButtonUrlInvalid:
print(f"Send button error")
await app.send_message(channel_id, track_msg.text, parse_mode="html",)
except Exception as e:
traceback.print_exc()
@Client.on_message(filters.incoming & filters.private & filters.chat(admin_id) &
filters.command(["force_update", ]))
async def force_update(bot: Client, message: Message):
await run_every_30_minute()

6
requirements.txt Normal file
View File

@ -0,0 +1,6 @@
Pyrogram>=1.4.8
Tgcrypto>=1.2.3
pyromod
httpx>=0.22.0
apscheduler>=3.8.1
sqlitedict>=2.0.0