class style, rename

This commit is contained in:
BennyThink
2021-01-18 22:26:02 +08:00
parent 368f6b260b
commit 0dee0fcacf
9 changed files with 672 additions and 601 deletions

4
.gitignore vendored
View File

@@ -110,6 +110,6 @@ venv.bak/
/.idea/modules.xml /.idea/modules.xml
/.idea/vcs.xml /.idea/vcs.xml
.idea/ .idea/
/data/cookies.dump /yyetsbot/data/cookies.dump
/.idea/inspectionProfiles/profiles_settings.xml /.idea/inspectionProfiles/profiles_settings.xml
data/ yyetsbot/data/

View File

@@ -1 +0,0 @@
dir to storage request id and data.

View File

@@ -1,127 +0,0 @@
# coding: utf-8
# YYeTsBot - html_request.py
# 2019/8/15 18:30
__author__ = 'Benny <benny.think@gmail.com>'
import os
import logging
import requests
import feedparser
from bs4 import BeautifulSoup
from config import SEARCH_URL, GET_USER, RSS_URL, BASE_URL, SHARE_WEB, SHARE_URL, WORKERS, SHARE_API
from utils import load_cookies, cookie_file, login
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(filename)s [%(levelname)s]: %(message)s')
s = requests.Session()
ua = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
s.headers.update({"User-Agent": ua})
def get_search_html(kw: str) -> str:
if not os.path.exists(cookie_file):
logging.warning("Cookie file not found")
login()
if not is_cookie_valid():
login()
cookie = load_cookies()
logging.info("Searching for %s", kw)
r = s.get(SEARCH_URL.format(kw=kw), cookies=cookie)
r.close()
return r.text
def get_detail_page(url: str) -> dict:
logging.info("Loading detail page %s", url)
share_link, api_res = analysis_share_page(url)
cnname = api_res["data"]["info"]["cnname"]
logging.info("Loading rss...")
rss_url = RSS_URL.format(id=url.split("/")[-1])
rss_result = analyse_rss(rss_url)
# get search_content from here...
if not rss_result:
rss_result = api_res
return {"all": rss_result, "share": share_link, "cnname": cnname}
def analyse_search_html(html: str) -> dict:
logging.info('Parsing html...')
soup = BeautifulSoup(html, 'lxml')
link_list = soup.find_all("div", class_="clearfix search-item")
list_result = {}
for block in link_list:
name = block.find_all('a')[-1].text
url = BASE_URL + block.find_all('a')[-1].attrs['href']
list_result[url] = name
return list_result
def analyse_rss(feed_url: str) -> dict:
# feed parser is meaningless now
return {}
# d = feedparser.parse(feed_url)
# # data['feed']['title']
# result = {}
# for item in d['entries']:
# download = {
# "title": getattr(item, "title", ""),
# "ed2k": getattr(item, "ed2k", ""),
# "magnet": getattr(item, "magnet", ""),
# "pan": getattr(item, "pan", "")}
# result[item.guid] = download
# return result
def analysis_share_page(detail_url: str) -> (str, dict):
rid = detail_url.split('/')[-1]
res = s.post(SHARE_URL, data={"rid": rid}, cookies=load_cookies()).json()
share_code = res['data'].split('/')[-1]
share_url = SHARE_WEB.format(code=share_code)
logging.info("Share url is %s", share_url)
# get api response
api_response = s.get(SHARE_API.format(code=share_code)).json()
return share_url, api_response
def is_cookie_valid() -> bool:
cookie = load_cookies()
r = s.get(GET_USER, cookies=cookie)
return r.json()['status'] == 1
def offline_search(search_content):
# from cloudflare workers
# no redis cache for now
logging.info("Loading data from cfkv...")
index = WORKERS.format(id="index")
data: dict = requests.get(index).json()
logging.info("Loading complete, searching now...")
results = {}
for name, rid in data.items():
if search_content in name:
fake_url = f"http://www.rrys2020.com/resource/{rid}"
results[fake_url] = name.replace("\n", " ")
logging.info("Search complete")
return results
def offline_link(resource_url) -> str:
rid = resource_url.split("/")[-1]
query_url = WORKERS.format(id=rid)
# TODO: too lazy to optimize cloudflare worker page.
return query_url
if __name__ == '__main__':
a = offline_search("越狱")
print(a)

10
tests/test_fansub.py Normal file
View File

@@ -0,0 +1,10 @@
import unittest
class MyTestCase(unittest.TestCase):
def test_something(self):
self.assertEqual(True, False)
if __name__ == '__main__':
unittest.main()

View File

@@ -1,329 +1,325 @@
# coding: utf-8 # coding: utf-8
# YYeTsBot - bot.py # YYeTsBot - bot.py
# 2019/8/15 18:27 # 2019/8/15 18:27
__author__ = 'Benny <benny.think@gmail.com>' __author__ = 'Benny <benny.think@gmail.com>'
import io import io
import time import time
import re import re
import os import os
import logging import logging
import json import json
import tempfile import tempfile
from urllib.parse import quote_plus from urllib.parse import quote_plus
import telebot import telebot
from telebot import types, apihelper from telebot import types, apihelper
from tgbot_ping import get_runtime from tgbot_ping import get_runtime
from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.schedulers.background import BackgroundScheduler
from html_request import get_search_html, analyse_search_html, get_detail_page, offline_search, offline_link from fansub import YYeTs
from utils import (save_error_dump, save_to_cache, yyets_get_from_cache, get_error_dump, from utils import (save_error_dump, get_error_dump, reset_request, today_request,
reset_request, today_request, show_usage, show_usage, redis_announcement
redis_announcement )
) from config import PROXY, TOKEN, SEARCH_URL, MAINTAINER, REPORT, OFFLINE
from config import PROXY, TOKEN, SEARCH_URL, MAINTAINER, REPORT, WORKERS, OFFLINE
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(filename)s [%(levelname)s]: %(message)s')
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(filename)s [%(levelname)s]: %(message)s') if PROXY:
if PROXY: apihelper.proxy = {'https': PROXY}
apihelper.proxy = {'https': PROXY}
bot = telebot.TeleBot(os.environ.get('TOKEN') or TOKEN)
bot = telebot.TeleBot(os.environ.get('TOKEN') or TOKEN) angry_count = 0
angry_count = 0
@bot.message_handler(commands=['start'])
@bot.message_handler(commands=['start']) def send_welcome(message):
def send_welcome(message): bot.send_chat_action(message.chat.id, 'typing')
bot.send_chat_action(message.chat.id, 'typing') bot.send_message(message.chat.id, '欢迎使用,直接发送想要的剧集标题给我就可以了,不需要其他关键字,我会帮你搜索。\n\n'
bot.send_message(message.chat.id, '欢迎使用,直接发送想要的剧集标题给我就可以了,不需要其他关键字,我会帮你搜索。\n\n' '人人影视专注于欧美日韩剧集,请不要反馈“我搜不到喜羊羊与灰太狼/流浪地球”这种问题,'
'人人影视专注于欧美日韩剧集,请不要反馈“我搜不到喜羊羊与灰太狼/流浪地球”这种问题,' '我会生气的😠😡🤬😒\n\n'
'我会生气的😠😡🤬😒\n\n' '建议使用<a href="http://www.zmz2019.com/">人人影视</a> 标准译名',
'建议使用<a href="http://www.zmz2019.com/">人人影视</a> 标准译名', parse_mode='html', disable_web_page_preview=True)
parse_mode='html', disable_web_page_preview=True)
@bot.message_handler(commands=['help'])
@bot.message_handler(commands=['help']) def send_help(message):
def send_help(message): bot.send_chat_action(message.chat.id, 'typing')
bot.send_chat_action(message.chat.id, 'typing') bot.send_message(message.chat.id, '''机器人无法使用或者报错?从 /ping 里可以看到运行状态以及最新信息。
bot.send_message(message.chat.id, '''机器人无法使用或者报错?从 /ping 里可以看到运行状态以及最新信息。 同时你可以使用如下方式寻求使用帮助和报告错误\n
同时你可以使用如下方式寻求使用帮助和报告错误\n 1. @BennyThink
1. @BennyThink 2. <a href='https://github.com/BennyThink/YYeTsBot/issues'>Github issues</a>
2. <a href='https://github.com/BennyThink/YYeTsBot/issues'>Github issues</a> 3. <a href='https://t.me/mikuri520'>Telegram Channel</a>''', parse_mode='html', disable_web_page_preview=True)
3. <a href='https://t.me/mikuri520'>Telegram Channel</a>''', parse_mode='html', disable_web_page_preview=True)
@bot.message_handler(commands=['ping'])
@bot.message_handler(commands=['ping']) def send_ping(message):
def send_ping(message): logging.info("Pong!")
logging.info("Pong!") bot.send_chat_action(message.chat.id, 'typing')
bot.send_chat_action(message.chat.id, 'typing')
info = get_runtime("botsrunner_yyets_1")
info = get_runtime("botsrunner_yyets_1") redis = get_runtime("botsrunner_redis_1", "Redis")
redis = get_runtime("botsrunner_redis_1", "Redis")
usage = ""
usage = "" if str(message.chat.id) == MAINTAINER:
if str(message.chat.id) == MAINTAINER: usage = show_usage()
usage = show_usage() announcement = redis_announcement() or ""
announcement = redis_announcement() or "" if announcement:
if announcement: announcement = f"\n\n*公告:{announcement}*\n\n"
announcement = f"\n\n*公告:{announcement}*\n\n" bot.send_message(message.chat.id, f"{info}\n{redis}\n\n{usage}\n{announcement}",
bot.send_message(message.chat.id, f"{info}\n{redis}\n\n{usage}\n{announcement}", parse_mode='markdown')
parse_mode='markdown')
@bot.message_handler(commands=['settings'])
@bot.message_handler(commands=['settings']) def settings(message):
def settings(message): is_admin = str(message.chat.id) == MAINTAINER
is_admin = str(message.chat.id) == MAINTAINER # 普通用户只可以查看,不可以设置。
# 普通用户只可以查看,不可以设置 # 管理员可以查看可以设置
# 管理员可以查看可以设置 if message.text != "/settings" and not is_admin:
if message.text != "/settings" and not is_admin: bot.send_message(message.chat.id, "此功能只允许管理员使用。请使用 /ping 和 /settings 查看相关信息")
bot.send_message(message.chat.id, "此功能只允许管理员使用。请使用 /ping 和 /settings 查看相关信息") return
return
# 删除公告,设置新公告
# 删除公告,设置新公告 if message.text != "/settings":
if message.text != "/settings": date = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
date = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) text = message.text.replace("/settings", f"{date}\t")
text = message.text.replace("/settings", f"{date}\t") logging.info("New announcement %s", text)
logging.info("New announcement %s", text) redis_announcement(text, "set")
redis_announcement(text, "set") setattr(message, "text", "/settings")
setattr(message, "text", "/settings") settings(message)
settings(message) return
return
announcement = redis_announcement()
announcement = redis_announcement() markup = types.InlineKeyboardMarkup()
markup = types.InlineKeyboardMarkup() btn1 = types.InlineKeyboardButton("删除公告", callback_data="announcement")
btn1 = types.InlineKeyboardButton("删除公告", callback_data="announcement") if is_admin and announcement:
if is_admin and announcement: markup.add(btn1)
markup.add(btn1)
bot.send_message(message.chat.id, f"目前公告:\n\n {announcement or '暂无公告'}", reply_markup=markup)
bot.send_message(message.chat.id, f"目前公告:\n\n {announcement or '暂无公告'}", reply_markup=markup)
@bot.callback_query_handler(func=lambda call: re.findall(r"announcement(\S*)", call.data))
@bot.callback_query_handler(func=lambda call: re.findall(r"announcement(\S*)", call.data)) def delete_announcement(call):
def delete_announcement(call): bot.send_chat_action(call.message.chat.id, 'typing')
bot.send_chat_action(call.message.chat.id, 'typing') redis_announcement(op="del")
redis_announcement(op="del")
bot.edit_message_text(f"目前公告:\n\n {redis_announcement() or '暂无公告'}",
bot.edit_message_text(f"目前公告:\n\n {redis_announcement() or '暂无公告'}", call.message.chat.id,
call.message.chat.id, call.message.message_id)
call.message.message_id)
@bot.message_handler(commands=['credits'])
@bot.message_handler(commands=['credits']) def send_credits(message):
def send_credits(message): bot.send_chat_action(message.chat.id, 'typing')
bot.send_chat_action(message.chat.id, 'typing') bot.send_message(message.chat.id, '''感谢字幕组的无私奉献!本机器人资源来源:\n
bot.send_message(message.chat.id, '''感谢字幕组的无私奉献!本机器人资源来源:\n <a href="http://www.zmz2019.com/">人人影视</a>
<a href="http://www.zmz2019.com/">人人影视</a> <a href="http://cili001.com/">磁力下载站</a>
<a href="http://cili001.com/">磁力下载站</a> <a href="http://www.zhuixinfan.com/main.php">追新番</a>
<a href="http://www.zhuixinfan.com/main.php">追新番</a> ''', parse_mode='html', disable_web_page_preview=True)
''', parse_mode='html', disable_web_page_preview=True)
def download_to_io(photo):
def download_to_io(photo): logging.info("Initializing bytes io...")
logging.info("Initializing bytes io...") mem = io.BytesIO()
mem = io.BytesIO() file_id = photo[-1].file_id
file_id = photo[-1].file_id logging.info("Downloading photos...")
logging.info("Downloading photos...") file_info = bot.get_file(file_id)
file_info = bot.get_file(file_id) content = bot.download_file(file_info.file_path)
content = bot.download_file(file_info.file_path) mem.write(content)
mem.write(content) logging.info("Downloading complete.")
logging.info("Downloading complete.") return mem
return mem
def send_my_response(message):
def send_my_response(message): bot.send_chat_action(message.chat.id, 'record_video_note')
bot.send_chat_action(message.chat.id, 'record_video_note') # I may also send picture
# I may also send picture photo = message.photo
photo = message.photo uid = message.reply_to_message.caption
uid = message.reply_to_message.caption text = f"主人说:{message.text or message.caption or '啥也没说😯'}"
text = f"主人说:{message.text or message.caption or '啥也没说😯'}" if photo:
if photo: bot.send_chat_action(message.chat.id, 'typing')
bot.send_chat_action(message.chat.id, 'typing') logging.info("Photo received from maintainer")
logging.info("Photo received from maintainer") mem = download_to_io(photo)
mem = download_to_io(photo) mem.name = f'{uid}.jpg'
mem.name = f'{uid}.jpg' r = bot.send_photo(uid, mem.getvalue(), caption=text)
r = bot.send_photo(uid, mem.getvalue(), caption=text) else:
else: r = bot.send_message(uid, text)
r = bot.send_message(uid, text)
logging.info("Reply has been sent to %s with message id %s", uid, r.message_id)
logging.info("Reply has been sent to %s with message id %s", uid, r.message_id) bot.reply_to(message, "回复已经发送给这位用户")
bot.reply_to(message, "回复已经发送给这位用户") fw = bot.forward_message(message.chat.id, uid, r.message_id)
fw = bot.forward_message(message.chat.id, uid, r.message_id) time.sleep(3)
time.sleep(3) bot.delete_message(message.chat.id, fw.message_id)
bot.delete_message(message.chat.id, fw.message_id) logging.info("Forward has been deleted.")
logging.info("Forward has been deleted.")
@bot.message_handler(content_types=["photo", "text"])
@bot.message_handler(content_types=["photo", "text"]) def send_search(message):
def send_search(message): yyets = YYeTs()
bot.send_chat_action(message.chat.id, 'typing') bot.send_chat_action(message.chat.id, 'typing')
today_request("total") today_request("total")
if message.reply_to_message and message.reply_to_message.document and \ if message.reply_to_message and message.reply_to_message.document and \
message.reply_to_message.document.file_name.startswith("error") and str(message.chat.id) == MAINTAINER: message.reply_to_message.document.file_name.startswith("error") and str(message.chat.id) == MAINTAINER:
today_request("answer") today_request("answer")
send_my_response(message) send_my_response(message)
return return
name = message.text name = message.text
logging.info('Receiving message: %s from user %s(%s)', name, message.chat.username, message.chat.id) logging.info('Receiving message: %s from user %s(%s)', name, message.chat.username, message.chat.id)
if name is None: if name is None:
today_request("invalid") today_request("invalid")
with open('assets/warning.webp', 'rb') as sti: with open('warning.webp', 'rb') as sti:
bot.send_message(message.chat.id, "不要调戏我!我会报警的") bot.send_message(message.chat.id, "不要调戏我!我会报警的")
bot.send_sticker(message.chat.id, sti) bot.send_sticker(message.chat.id, sti)
return return
if OFFLINE: if OFFLINE:
logging.warning("☢️ Going offline mode!!!") logging.warning("☢️ Going offline mode!!!")
bot.send_message(message.chat.id, "人人影视官网不可用,目前在使用离线模式,可能没有最新的剧集。") bot.send_message(message.chat.id, "人人影视官网不可用,目前在使用离线模式,可能没有最新的剧集。")
html = "" bot.send_chat_action(message.chat.id, 'upload_document')
bot.send_chat_action(message.chat.id, 'upload_document') result = yyets.offline_search_preview(name)
result = offline_search(name) else:
else: result = yyets.online_search_preview(name)
html = get_search_html(name)
result = analyse_search_html(html) markup = types.InlineKeyboardMarkup()
for url, detail in result.items():
markup = types.InlineKeyboardMarkup() btn = types.InlineKeyboardButton(detail, callback_data="choose%s" % url)
for url, detail in result.items(): markup.add(btn)
btn = types.InlineKeyboardButton(detail, callback_data="choose%s" % url)
markup.add(btn) if result:
logging.info("🎉 Resource match.")
if result: today_request("success")
logging.info("🎉 Resource match.") bot.send_message(message.chat.id, "呐,💐🌷🌹选一个呀!", reply_markup=markup)
today_request("success") else:
bot.send_message(message.chat.id, "呐,💐🌷🌹选一个呀!", reply_markup=markup) logging.warning("⚠️️ Resource not found")
else: today_request("fail")
logging.warning("⚠️️ Resource not found") bot.send_chat_action(message.chat.id, 'typing')
today_request("fail")
bot.send_chat_action(message.chat.id, 'typing') encoded = quote_plus(name)
bot.send_message(message.chat.id, f"没有找到你想要的信息,是不是你打了错别字,或者搜索了一些国产影视剧。🤪\n"
encoded = quote_plus(name) f"还是你想调戏我哦🙅‍️\n\n"
bot.send_message(message.chat.id, f"没有找到你想要的信息,是不是你打了错别字,或者搜索了一些国产影视剧。🤪\n" f"可以看看这个链接,看看有没有结果。 {SEARCH_URL.format(kw=encoded)} \n\n"
f"还是你想调戏我哦🙅‍️\n\n" "⚠️如果确定要我背锅,那么请使用 /help 来提交错误", disable_web_page_preview=True)
f"可以看看这个链接,看看有没有结果。 {SEARCH_URL.format(kw=encoded)} \n\n" if REPORT:
"⚠️如果确定要我背锅,那么请使用 /help 来提交错误", disable_web_page_preview=True) btn = types.InlineKeyboardButton("快来修复啦", callback_data="fix")
if REPORT: markup.add(btn)
btn = types.InlineKeyboardButton("快来修复啦", callback_data="fix") bot.send_chat_action(message.chat.id, 'upload_document')
markup.add(btn) bot.send_message(message.chat.id, f"{name}》😭\n大部分情况下机器人是好用的,不要怀疑我的代码质量.\n"
bot.send_chat_action(message.chat.id, 'upload_document') f"如果你真的确定是机器人出问题了,那么点下面的按钮叫 @BennyThink 来修!\n"
bot.send_message(message.chat.id, f"{name}》😭\n大部分情况下机器人是好用的,不要怀疑我的代码质量.\n" f"⚠️报错前请三思,不要乱点,确保这锅应该甩给我。否则我会很生气的😡小心被拉黑哦",
f"如果你真的确定是机器人出问题了,那么点下面的按钮叫 @BennyThink 来修!\n" reply_markup=markup)
f"⚠️报错前请三思,不要乱点,确保这锅应该甩给我。否则我会很生气的😡小心被拉黑哦", content = f""" 报告者:{message.chat.first_name}{message.chat.last_name or ""}@{message.chat.username or ""}({message.chat.id})
reply_markup=markup) 问题发生时间{time.strftime("%Y-%m-%data %H:%M:%S", time.localtime(message.date))}
content = f""" 报告者:{message.chat.first_name}{message.chat.last_name or ""}@{message.chat.username or ""}({message.chat.id}) 请求内容{name}
问题发生时间{time.strftime("%Y-%m-%data %H:%M:%S", time.localtime(message.date))} 请求URL{SEARCH_URL.format(kw=encoded)}\n\n
请求内容{name}
请求URL{SEARCH_URL.format(kw=encoded)}\n\n """
返回内容{html} save_error_dump(message.chat.id, content)
"""
save_error_dump(message.chat.id, content)
@bot.callback_query_handler(func=lambda call: re.findall(r"choose(\S*)", call.data))
def choose_link(call):
@bot.callback_query_handler(func=lambda call: re.findall(r"choose(\S*)", call.data)) yyets = YYeTs()
def choose_link(call): bot.send_chat_action(call.message.chat.id, 'typing')
bot.send_chat_action(call.message.chat.id, 'typing') # call.data is url, http://www.rrys2020.com/resource/36588
# call.data is url, http://www.rrys2020.com/resource/36588 resource_url = re.findall(r"choose(\S*)", call.data)[0]
resource_url = re.findall(r"choose(\S*)", call.data)[0] markup = types.InlineKeyboardMarkup()
markup = types.InlineKeyboardMarkup()
if OFFLINE:
if OFFLINE: worker_page = yyets.offline_search_result(resource_url)
worker_page = offline_link(resource_url) btn1 = types.InlineKeyboardButton("打开网页", url=worker_page)
btn1 = types.InlineKeyboardButton("打开网页", url=worker_page) markup.add(btn1)
markup.add(btn1) bot.send_message(call.message.chat.id, "离线模式,点击按钮打开网页获取结果", reply_markup=markup)
bot.send_message(call.message.chat.id, "离线模式,点击按钮打开网页获取结果", reply_markup=markup) return
return
btn1 = types.InlineKeyboardButton("分享页面", callback_data="share%s" % resource_url)
link = yyets_get_from_cache(resource_url) btn2 = types.InlineKeyboardButton("我全都要", callback_data="all%s" % resource_url)
if not link: markup.add(btn1, btn2)
link = get_detail_page(resource_url) text = "想要分享页面,还是我全都要?\n\n" \
save_to_cache(resource_url, link) "名词解释:“分享页面”会返回给你一个网站,从那里可以看到全部的下载链接。\n" \
"“我全都要”会给你发送一个txt文件文件里包含全部下载连接\n"
btn1 = types.InlineKeyboardButton("分享页面", callback_data="share%s" % resource_url) bot.send_message(call.message.chat.id, text, reply_markup=markup)
btn2 = types.InlineKeyboardButton("我全都要", callback_data="all%s" % resource_url)
markup.add(btn1, btn2)
text = "想要分享页面,还是我全都要?\n\n" \ @bot.callback_query_handler(func=lambda call: re.findall(r"share(\S*)", call.data))
"名词解释:“分享页面”会返回给你一个网站,从那里可以看到全部的下载链接。\n" \ def share_page(call):
"“我全都要”会给你发送一个txt文件文件里包含全部下载连接\n" yyets = YYeTs()
bot.send_message(call.message.chat.id, text, reply_markup=markup) bot.send_chat_action(call.message.chat.id, 'typing')
resource_url = re.findall(r"share(\S*)", call.data)[0]
result = yyets.online_search_result(resource_url)
@bot.callback_query_handler(func=lambda call: re.findall(r"share(\S*)", call.data)) bot.send_message(call.message.chat.id, result['share'])
def share_page(call):
bot.send_chat_action(call.message.chat.id, 'typing')
resource_url = re.findall(r"share(\S*)", call.data)[0] @bot.callback_query_handler(func=lambda call: re.findall(r"all(\S*)", call.data))
result = yyets_get_from_cache(resource_url) def all_episode(call):
bot.send_message(call.message.chat.id, result['share']) # just send a file
yyets = YYeTs()
bot.send_chat_action(call.message.chat.id, 'typing')
@bot.callback_query_handler(func=lambda call: re.findall(r"all(\S*)", call.data)) resource_url = re.findall(r"all(\S*)", call.data)[0]
def all_episode(call): result = yyets.online_search_result(resource_url)
# just send a file
bot.send_chat_action(call.message.chat.id, 'typing') with tempfile.NamedTemporaryFile(mode='wb+', prefix=result["cnname"], suffix=".txt") as tmp:
resource_url = re.findall(r"all(\S*)", call.data)[0] bytes_data = json.dumps(result["all"], ensure_ascii=False, indent=4).encode('u8')
result = yyets_get_from_cache(resource_url) tmp.write(bytes_data)
tmp.flush()
with tempfile.NamedTemporaryFile(mode='wb+', prefix=result["cnname"], suffix=".txt") as tmp: with open(tmp.name, "rb") as f:
bytes_data = json.dumps(result["all"], ensure_ascii=False, indent=4).encode('u8') bot.send_chat_action(call.message.chat.id, 'upload_document')
tmp.write(bytes_data) bot.send_document(call.message.chat.id, f)
tmp.flush()
with open(tmp.name, "rb") as f:
bot.send_chat_action(call.message.chat.id, 'upload_document') @bot.callback_query_handler(func=lambda call: re.findall(r"unwelcome(\d*)", call.data))
bot.send_document(call.message.chat.id, f) def send_unwelcome(call):
# this will come from me only
logging.warning("I'm so unhappy!")
@bot.callback_query_handler(func=lambda call: re.findall(r"unwelcome(\d*)", call.data)) message = call.message
def send_unwelcome(call): bot.send_chat_action(message.chat.id, 'typing')
# this will come from me only
logging.warning("I'm so unhappy!") # angry_count = angry_count + 1
message = call.message global angry_count
bot.send_chat_action(message.chat.id, 'typing') angry_count += 1
uid = re.findall(r"unwelcome(\d*)", call.data)[0]
# angry_count = angry_count + 1
global angry_count if uid:
angry_count += 1 text = "人人影视主要提供欧美日韩等海外资源,你的这个真没有🤷‍。\n" \
uid = re.findall(r"unwelcome(\d*)", call.data)[0] "<b>麻烦你先从自己身上找原因</b>,我又不是你的专属客服。\n" \
"不要再报告这种错误了🙄️,面倒な。😡"
if uid: bot.send_message(uid, text, parse_mode="html")
text = "人人影视主要提供欧美日韩等海外资源,你的这个真没有🤷‍。\n" \ bot.reply_to(message, f"有生之日 生气次数:{angry_count}")
"<b>麻烦你先从自己身上找原因</b>,我又不是你的专属客服。\n" \
"不要再报告这种错误了🙄️,面倒な。😡"
bot.send_message(uid, text, parse_mode="html") @bot.callback_query_handler(func=lambda call: call.data == 'fix')
bot.reply_to(message, f"有生之日 生气次数:{angry_count}") def report_error(call):
logging.error("Reporting error to maintainer.")
bot.send_chat_action(call.message.chat.id, 'typing')
@bot.callback_query_handler(func=lambda call: call.data == 'fix') error_content = get_error_dump(call.message.chat.id)
def report_error(call): if error_content == "":
logging.error("Reporting error to maintainer.") bot.answer_callback_query(call.id, '多次汇报重复的问题并不会加快处理速度。', show_alert=True)
bot.send_chat_action(call.message.chat.id, 'typing') return
error_content = get_error_dump(call.message.chat.id)
if error_content == "": text = f'人人影视机器人似乎出现了一些问题🤔🤔🤔……{error_content[0:300]}'
bot.answer_callback_query(call.id, '多次汇报重复的问题并不会加快处理速度。', show_alert=True)
return markup = types.InlineKeyboardMarkup()
btn = types.InlineKeyboardButton("unwelcome", callback_data=f"unwelcome{call.message.chat.id}")
text = f'人人影视机器人似乎出现了一些问题🤔🤔🤔……{error_content[0:300]}' markup.add(btn)
markup = types.InlineKeyboardMarkup() bot.send_message(MAINTAINER, text, disable_web_page_preview=True, reply_markup=markup)
btn = types.InlineKeyboardButton("unwelcome", callback_data=f"unwelcome{call.message.chat.id}")
markup.add(btn) with tempfile.NamedTemporaryFile(mode='wb+', prefix=f"error_{call.message.chat.id}_", suffix=".txt") as tmp:
tmp.write(error_content.encode('u8'))
bot.send_message(MAINTAINER, text, disable_web_page_preview=True, reply_markup=markup) tmp.flush()
with tempfile.NamedTemporaryFile(mode='wb+', prefix=f"error_{call.message.chat.id}_", suffix=".txt") as tmp: with open(tmp.name, "rb") as f:
tmp.write(error_content.encode('u8')) bot.send_chat_action(call.message.chat.id, 'upload_document')
tmp.flush() bot.send_document(MAINTAINER, f, caption=str(call.message.chat.id))
with open(tmp.name, "rb") as f: bot.answer_callback_query(call.id, 'Debug信息已经发送给维护者请耐心等待回复~', show_alert=True)
bot.send_chat_action(call.message.chat.id, 'upload_document')
bot.send_document(MAINTAINER, f, caption=str(call.message.chat.id))
if __name__ == '__main__':
bot.answer_callback_query(call.id, 'Debug信息已经发送给维护者请耐心等待回复~', show_alert=True) logging.info('YYeTs bot is running...')
scheduler = BackgroundScheduler()
scheduler.add_job(reset_request, 'cron', hour=0, minute=0)
if __name__ == '__main__': scheduler.start()
logging.info('YYeTs bot is running...') bot.polling(none_stop=True)
scheduler = BackgroundScheduler()
scheduler.add_job(reset_request, 'cron', hour=0, minute=0)
scheduler.start()
bot.polling(none_stop=True)

View File

@@ -1,30 +1,31 @@
# coding: utf-8 # coding: utf-8
# YYeTsBot - config.py # YYeTsBot - config.py
# 2019/8/15 18:42 # 2019/8/15 18:42
__author__ = 'Benny <benny.think@gmail.com>' __author__ = 'Benny <benny.think@gmail.com>'
import os import os
BASE_URL = "http://www.rrys2020.com" BASE_URL = "http://www.rrys2020.com"
LOGIN_URL = "http://www.rrys2020.com/user/login" LOGIN_URL = "http://www.rrys2020.com/user/login"
GET_USER = "http://www.rrys2020.com/user/login/getCurUserTopInfo" GET_USER = "http://www.rrys2020.com/user/login/getCurUserTopInfo"
# rss is unavailable as of 2021.01.10 SEARCH_URL = "http://www.rrys2020.com/search?keyword={kw}&type=resource"
RSS_URL = "http://rss.rrys.tv/rss/feed/{id}" AJAX_LOGIN = "http://www.rrys2020.com/User/Login/ajaxLogin"
SEARCH_URL = "http://www.rrys2020.com/search?keyword={kw}&type=resource" SHARE_URL = "http://www.rrys2020.com/resource/ushare"
AJAX_LOGIN = "http://www.rrys2020.com/User/Login/ajaxLogin" SHARE_WEB = "http://got002.com/resource.html?code={code}"
SHARE_URL = "http://www.rrys2020.com/resource/ushare" # http://got002.com/api/v1/static/resource/detail?code=9YxN91
SHARE_WEB = "http://got002.com/resource.html?code={code}" SHARE_API = "http://got002.com/api/v1/static/resource/detail?code={code}"
# http://got002.com/api/v1/static/resource/detail?code=9YxN91
SHARE_API = "http://got002.com/api/v1/static/resource/detail?code={code}" WORKERS = "https://yyets.yyetsdb.workers.dev/?id={id}"
WORKERS = "https://yyets.yyetsdb.workers.dev/?id={id}" TOKEN = os.environ.get("TOKEN") or "TOKEN"
USERNAME = os.environ.get("USERNAME") or "USERNAME"
TOKEN = os.environ.get("TOKEN") or "TOKEN" PASSWORD = os.environ.get("PASSWORD") or "password"
USERNAME = os.environ.get("USERNAME") or "USERNAME" PROXY = os.environ.get("PROXY")
PASSWORD = os.environ.get("PASSWORD") or "password" MAINTAINER = os.environ.get("MAINTAINER")
PROXY = os.environ.get("PROXY") REDIS = os.environ.get("REDIS") or "redis"
MAINTAINER = os.environ.get("MAINTAINER") REPORT = os.environ.get("REPORT") or False
REDIS = os.environ.get("REDIS") or "redis" OFFLINE = os.environ.get("OFFLINE") or False
REPORT = os.environ.get("REPORT") or False
OFFLINE = os.environ.get("OFFLINE") or False FIX_RESOURCE = "https://www.zimuxia.cn/portfolio/{name}"
FIX_SEARCH = "https://www.zimuxia.cn/?s={name}"

233
yyetsbot/fansub.py Normal file
View File

@@ -0,0 +1,233 @@
# coding: utf-8
# YYeTsBot - fansub.py
# 2019/8/15 18:30
__author__ = 'Benny <benny.think@gmail.com>'
import os
import logging
import requests
import pickle
import sys
import json
from bs4 import BeautifulSoup
from config import (SEARCH_URL, GET_USER, BASE_URL, SHARE_WEB,
SHARE_URL, WORKERS, SHARE_API, USERNAME, PASSWORD,
AJAX_LOGIN, REDIS)
import redis
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(filename)s [%(levelname)s]: %(message)s')
session = requests.Session()
ua = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
session.headers.update({"User-Agent": ua})
class BaseFansub:
"""
all the subclass should implement three kinds of methods:
1. online search, contains preview for bot and complete result
2. offline search (set pass if not applicable)
3. login and check (set pass if not applicable)
4. search_result this is critical for bot to draw markup
"""
label = None
cookie_file = None
def __init__(self):
self.data = None
self.url = None
self.redis = redis.StrictRedis(host=REDIS, decode_responses=True)
@property
def id(self):
# implement how to get the unique id for this resource
return None
def __get_search_html__(self, kw: str) -> str:
# return html text of search page
pass
def online_search_preview(self, search_text: str) -> dict:
# try to retrieve critical information from html
# this result must return to bot for manual selection
# {"url1": "name1", "url2": "name2"}
pass
def online_search_result(self, resource_url: str) -> dict:
"""
This will happen when user click one of the button, only by then we can know the resource link
From the information above, try to get a detail dict structure.
This method should check cache first if applicable
This method should set self.link and self.data
This method should call __execute_online_search
:param resource_url:
:return: {"all": rss_result, "share": share_link, "cnname": cnname}
"""
pass
def __execute_online_search_result(self) -> dict:
"""
Do the real search job, without any cache mechanism
:return: {"all": rss_result, "share": share_link, "cnname": cnname}
"""
pass
def offline_search_preview(self, search_text: str) -> dict:
# this result must return to bot for manual selection
# the same as online
pass
def offline_search_result(self, resource_url) -> dict:
"""
Same as online_search_result
:param resource_url:
:return:
"""
pass
def __execute_offline_search_result(self) -> dict:
"""
Do the search job, without any cache mechanism
:return: {"all": rss_result, "share": share_link, "cnname": cnname}
"""
pass
def __login_check(self):
pass
def __manual_login(self):
pass
def __save_cookies(self, requests_cookiejar):
with open(self.cookie_file, 'wb') as f:
pickle.dump(requests_cookiejar, f)
def __load_cookies(self):
with open(self.cookie_file, 'rb') as f:
return pickle.load(f)
def __get_from_cache(self, url: str, method_name: str) -> dict:
logging.info("Reading %s data from cache %s", self.label, url)
data = self.redis.get(url)
if data:
logging.info("Cache hit")
return json.loads(data)
else:
logging.info("Cache miss")
result_method = getattr(self, method_name)
self.__save_to_cache(url, result_method(url))
return self.__get_from_cache(url, method_name)
def __save_to_cache(self, url: str, value: dict, ex=3600 * 12) -> None:
data = json.dumps(value, ensure_ascii=False)
self.redis.set(url, data, ex=ex)
class YYeTs(BaseFansub):
label = "yyets"
cookie_file = os.path.join("data", "cookies.dump")
@property
def id(self):
# implement how to get the unique id for this resource
rid = self.url.split('/')[-1]
return rid
def __get_search_html__(self, kw: str) -> str:
self.__login_check()
cookie = self.__load_cookies()
logging.info("Searching for %s", kw)
r = session.get(SEARCH_URL.format(kw=kw), cookies=cookie)
r.close()
return r.text
def online_search_preview(self, search_text: str) -> dict:
html_text = self.__get_search_html__(search_text)
logging.info('Parsing html...')
soup = BeautifulSoup(html_text, 'lxml')
link_list = soup.find_all("div", class_="clearfix search-item")
dict_result = {}
for block in link_list:
name = block.find_all('a')[-1].text
url = BASE_URL + block.find_all('a')[-1].attrs['href']
dict_result[url] = name
return dict_result
def online_search_result(self, resource_url: str) -> dict:
self.url = resource_url
self.data = self.__get_from_cache(self.url, self.__execute_online_search_result.__name__)
return self.data
def __execute_online_search_result(self) -> dict:
logging.info("Loading detail page %s", self.url)
share_link, api_res = self.__get_share_page()
cnname = api_res["data"]["info"]["cnname"]
self.data = {"all": api_res, "share": share_link, "cnname": cnname}
return self.data
def offline_search_preview(self, search_text: str) -> dict:
# from cloudflare workers
# no redis cache for now
logging.info("Loading data from cfkv...")
index = WORKERS.format(id="index")
data: dict = requests.get(index).json()
logging.info("Loading complete, searching now...")
results = {}
for name, rid in data.items():
if search_text in name:
fake_url = f"http://www.rrys2020.com/resource/{rid}"
results[fake_url] = name.replace("\n", " ")
logging.info("Search complete")
return results
def offline_search_result(self, resource_url) -> dict:
self.url = resource_url
query_url = WORKERS.format(id=self.id)
self.data = {"all": None, "share": query_url, "cnname": None}
return self.data
def __login_check(self):
if not os.path.exists(self.cookie_file):
logging.warning("Cookie file not found")
self.__manual_login()
cookie = self.__load_cookies()
r = session.get(GET_USER, cookies=cookie)
if not r.json()['status'] == 1:
self.__manual_login()
def __manual_login(self):
data = {"account": USERNAME, "password": PASSWORD, "remember": 1}
logging.info("Login in as %s", data)
r = requests.post(AJAX_LOGIN, data=data)
resp = r.json()
if resp.get('status') == 1:
logging.info("Login success! %s", r.cookies)
self.__save_cookies(r.cookies)
else:
logging.error("Login failed! %s", resp)
sys.exit(1)
r.close()
def __get_share_page(self):
rid = self.id
res = session.post(SHARE_URL, data={"rid": rid}, cookies=self.__load_cookies()).json()
share_code = res['data'].split('/')[-1]
share_url = SHARE_WEB.format(code=share_code)
logging.info("Share url is %s", share_url)
# get api response
api_response = session.get(SHARE_API.format(code=share_code)).json()
return share_url, api_response
if __name__ == '__main__':
y = YYeTs()

View File

@@ -1,112 +1,71 @@
# coding: utf-8 # coding: utf-8
# YYeTsBot - utils.py # YYeTsBot - utils.py
# 2019/8/15 20:27 # 2019/8/15 20:27
__author__ = 'Benny <benny.think@gmail.com>' __author__ = 'Benny <benny.think@gmail.com>'
import os import os
import sys import sys
import pickle import pickle
import json import json
import logging import logging
import requests import requests
import redis import redis
from config import AJAX_LOGIN, USERNAME, PASSWORD, REDIS from config import AJAX_LOGIN, USERNAME, PASSWORD, REDIS
r = redis.StrictRedis(host=REDIS, decode_responses=True) r = redis.StrictRedis(host=REDIS, decode_responses=True)
cookie_file = os.path.join(os.path.dirname(__file__), 'data', 'cookies.dump') cookie_file = os.path.join(os.path.dirname(__file__), 'data', 'cookies.dump')
def save_to_cache(url: str, value: dict) -> None:
data = json.dumps(value, ensure_ascii=False)
r.set(url, data, ex=3600 * 12) def save_error_dump(uid, err: str):
r.set(uid, err)
def yyets_get_from_cache(url: str) -> dict:
logging.info("Reading data from cache %s", url) def get_error_dump(uid) -> str:
from html_request import get_detail_page err = r.get(uid)
r.delete(uid)
data = r.get(url) if not err:
if data: err = ""
logging.info("Cache hit") return err
return json.loads(data)
else:
logging.info("Cache miss") def redis_announcement(content="", op="get"):
save_to_cache(url, get_detail_page(url)) if op == "get":
return yyets_get_from_cache(url) return r.get("announcement")
elif op == "set":
r.set("announcement", content)
def save_error_dump(uid, err: str): elif op == "del":
r.set(uid, err) r.delete("announcement")
def get_error_dump(uid) -> str: def today_request(request_type: str):
err = r.get(uid) if r.exists("usage"):
r.delete(uid) data: str = r.get("usage")
if not err: dict_data: dict = json.loads(data)
err = "" dict_data[request_type] += 1
return err saved_data: str = json.dumps(dict_data)
else:
data_format: dict = dict(total=0, invalid=0, answer=0, success=0, fail=0)
def redis_announcement(content="", op="get"): data_format[request_type] += 1
if op == "get": saved_data: str = json.dumps(data_format)
return r.get("announcement")
elif op == "set": r.set("usage", saved_data)
r.set("announcement", content)
elif op == "del":
r.delete("announcement") def reset_request():
r.delete("usage")
def save_cookies(requests_cookiejar):
with open(cookie_file, 'wb') as f: def show_usage():
pickle.dump(requests_cookiejar, f) m = "今天我已经服务了{total}次🤓,无效请求{invalid}😆,主人回复{answer}次🤨,成功请求{success}次😝,失败请求{fail}次🤣"
data: str = r.get("usage")
if r.exists("usage"):
def load_cookies(): dict_data: dict = json.loads(data)
with open(cookie_file, 'rb') as f: else:
return pickle.load(f) dict_data: dict = dict(total=0, invalid=0, answer=0, success=0, fail=0)
return m.format(**dict_data)
def login():
data = {"account": USERNAME, "password": PASSWORD, "remember": 1}
logging.info("Login in as %s", data)
r = requests.post(AJAX_LOGIN, data=data)
resp = r.json()
if resp.get('status') == 1:
logging.info("Login success! %s", r.cookies)
save_cookies(r.cookies)
else:
logging.error("Login failed! %s", resp)
sys.exit(1)
r.close()
def today_request(request_type: str):
if r.exists("usage"):
data: str = r.get("usage")
dict_data: dict = json.loads(data)
dict_data[request_type] += 1
saved_data: str = json.dumps(dict_data)
else:
data_format: dict = dict(total=0, invalid=0, answer=0, success=0, fail=0)
data_format[request_type] += 1
saved_data: str = json.dumps(data_format)
r.set("usage", saved_data)
def reset_request():
r.delete("usage")
def show_usage():
m = "今天我已经服务了{total}次🤓,无效请求{invalid}😆,主人回复{answer}次🤨,成功请求{success}次😝,失败请求{fail}次🤣"
data: str = r.get("usage")
if r.exists("usage"):
dict_data: dict = json.loads(data)
else:
dict_data: dict = dict(total=0, invalid=0, answer=0, success=0, fail=0)
return m.format(**dict_data)

View File

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB