mirror of
https://0xacab.org/johnxina/rat.git
synced 2024-12-23 13:09:08 +00:00
Multiple features
This commit is contained in:
parent
9df44d5a72
commit
965c8f2cc0
40
aiotieba-handle-exception-expose.patch
Normal file
40
aiotieba-handle-exception-expose.patch
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
--- venv/lib/python3.11/site-packages/aiotieba/helper/utils.py
|
||||||
|
+++ venv/lib/python3.11/site-packages/aiotieba/helper/utils.py
|
||||||
|
@@ -141,35 +141,6 @@
|
||||||
|
|
||||||
|
def wrapper(func):
|
||||||
|
async def awrapper(*args, **kwargs):
|
||||||
|
- try:
|
||||||
|
- ret = await func(*args, **kwargs)
|
||||||
|
-
|
||||||
|
- except Exception as err:
|
||||||
|
- meth_name = func.__name__
|
||||||
|
- tb = err.__traceback__
|
||||||
|
- while tb := tb.tb_next:
|
||||||
|
- frame = tb.tb_frame
|
||||||
|
- if frame.f_code.co_name == meth_name:
|
||||||
|
- break
|
||||||
|
- frame = tb.tb_next.tb_frame
|
||||||
|
-
|
||||||
|
- log_str: str = frame.f_locals.get('__log__', '')
|
||||||
|
- if not no_format: # need format
|
||||||
|
- log_str = log_str.format(**frame.f_locals)
|
||||||
|
- log_str = f"{err}. {log_str}"
|
||||||
|
-
|
||||||
|
- logger = get_logger()
|
||||||
|
- if logger.isEnabledFor(log_level):
|
||||||
|
- record = logger.makeRecord(logger.name, log_level, None, 0, log_str, None, None, meth_name)
|
||||||
|
- logger.handle(record)
|
||||||
|
-
|
||||||
|
- exc_handlers._handle(meth_name, err)
|
||||||
|
-
|
||||||
|
- return null_ret_factory()
|
||||||
|
-
|
||||||
|
- else:
|
||||||
|
- return ret
|
||||||
|
-
|
||||||
|
+ return await func(*args, **kwargs)
|
||||||
|
return awrapper
|
||||||
|
-
|
||||||
|
return wrapper
|
||||||
|
|
24
app.py
24
app.py
@ -119,14 +119,12 @@ async def thread_view(tid):
|
|||||||
all_users = {}
|
all_users = {}
|
||||||
for floor in thread_info:
|
for floor in thread_info:
|
||||||
for comment in floor.comments:
|
for comment in floor.comments:
|
||||||
if not comment.reply_to_id in available_users:
|
if comment.reply_to_id and not comment.reply_to_id in available_users:
|
||||||
all_users[comment.reply_to_id] = ''
|
all_users[comment.reply_to_id] = ''
|
||||||
all_users.pop(0, None)
|
|
||||||
all_users = list(all_users.keys())
|
all_users = list(all_users.keys())
|
||||||
|
|
||||||
await asyncio.gather(*(cache_name_from_id(tieba, i) for i in all_users))
|
await asyncio.gather(*(cache_name_from_id(tieba, i) for i in all_users))
|
||||||
|
|
||||||
|
|
||||||
return await render_template('thread.html', info=thread_info)
|
return await render_template('thread.html', info=thread_info)
|
||||||
|
|
||||||
@app.route('/f')
|
@app.route('/f')
|
||||||
@ -138,8 +136,28 @@ async def forum_view():
|
|||||||
async with aiotieba.Client() as tieba:
|
async with aiotieba.Client() as tieba:
|
||||||
forum_info, threads = await asyncio.gather(awaitify(find_tieba_info)(fname),
|
forum_info, threads = await asyncio.gather(awaitify(find_tieba_info)(fname),
|
||||||
tieba.get_threads(fname, rn=50, pn=pn, sort=sort))
|
tieba.get_threads(fname, rn=50, pn=pn, sort=sort))
|
||||||
|
if threads.page.current_page > threads.page.total_page or pn < 1:
|
||||||
|
return await render_template('error.html', msg = \
|
||||||
|
f'请求越界,本贴吧共有 { threads.page.total_page } 页'
|
||||||
|
f'而您查询了第 { threads.page.current_page} 页')
|
||||||
|
|
||||||
return await render_template('bar.html', info=forum_info, threads=threads, sort=sort)
|
return await render_template('bar.html', info=forum_info, threads=threads, sort=sort)
|
||||||
|
|
||||||
|
@app.route('/home/main')
|
||||||
|
async def user_view():
|
||||||
|
return 'UNDER CONSTRUCTION'
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
@app.errorhandler(RuntimeError)
|
||||||
|
async def runtime_error_view(e):
|
||||||
|
if e.msg:
|
||||||
|
return await render_template('error.html', msg=e.msg)
|
||||||
|
return await render_template('error.html', msg='错误信息不可用')
|
||||||
|
|
||||||
|
@app.errorhandler(Exception)
|
||||||
|
async def general_error_view(e):
|
||||||
|
return await render_template('error.html', msg=e)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
app.run(debug=True)
|
app.run(debug=True)
|
||||||
|
11
extra.py
11
extra.py
@ -14,7 +14,7 @@ def extract_image_name(url):
|
|||||||
except:
|
except:
|
||||||
return '404.jpg'
|
return '404.jpg'
|
||||||
|
|
||||||
@cache.cached(timeout=60, key_prefix='tieba_info')
|
@cache.memoize(timeout=60)
|
||||||
def find_tieba_info(tname):
|
def find_tieba_info(tname):
|
||||||
"""Get the tiebat avatar for the forum name.
|
"""Get the tiebat avatar for the forum name.
|
||||||
|
|
||||||
@ -24,7 +24,14 @@ def find_tieba_info(tname):
|
|||||||
"""
|
"""
|
||||||
info = { 'name': tname }
|
info = { 'name': tname }
|
||||||
|
|
||||||
res = requests.get('https://tieba.baidu.com/f', params={'kw': tname})
|
res = requests.get('https://tieba.baidu.com/f',
|
||||||
|
params={'kw': tname},
|
||||||
|
allow_redirects=False)
|
||||||
|
|
||||||
|
# Baidu will bring us to the search page, so we ignore it.
|
||||||
|
if res.status_code == 302:
|
||||||
|
raise ValueError('您搜索的贴吧不存在')
|
||||||
|
|
||||||
soup = bs4.BeautifulSoup(res.text, 'html.parser')
|
soup = bs4.BeautifulSoup(res.text, 'html.parser')
|
||||||
|
|
||||||
elems = soup.select('#forum-card-head')
|
elems = soup.select('#forum-card-head')
|
||||||
|
@ -17,6 +17,18 @@
|
|||||||
color: inherit !important;
|
color: inherit !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.post :nth-child(1) {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post .tier {
|
||||||
|
text-align: center;
|
||||||
|
padding: .3rem;
|
||||||
|
font-size: .8rem;
|
||||||
|
background-color: var(--replies-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* global styling */
|
/* global styling */
|
||||||
:root {
|
:root {
|
||||||
--bg-color: #eeeecc;
|
--bg-color: #eeeecc;
|
||||||
|
25
templates/error.html
Normal file
25
templates/error.html
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<head>
|
||||||
|
<title>错误 - RAT</title>
|
||||||
|
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
|
<link href="/static/css/main.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="list" style="margin: 5%; padding: 1%; text-align: center;">
|
||||||
|
<div style="font-size: 10em;">
|
||||||
|
<a title="带我回去" href="javascript:window.history.back();" style="text-decoration: none;">
|
||||||
|
🙃
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<h1>{{ msg }}</h1>
|
||||||
|
</div>
|
||||||
|
<footer>
|
||||||
|
<div><a href="#">RAT Ain't Tieba</a></div>
|
||||||
|
<div><a href="#">自豪地以 AGPLv3 释出</a></div>
|
||||||
|
<div><a href="#">源代码</a></div>
|
||||||
|
</footer>
|
||||||
|
</body>
|
@ -17,7 +17,13 @@
|
|||||||
<div class="list">
|
<div class="list">
|
||||||
{% for p in info %}
|
{% for p in info %}
|
||||||
<div class="post" id="{{ p.floor }}">
|
<div class="post" id="{{ p.floor }}">
|
||||||
<img class="avatar" src="/proxy/avatar/{{ p.user.portrait }}">
|
<div>
|
||||||
|
<img class="avatar" src="/proxy/avatar/{{ p.user.portrait }}">
|
||||||
|
{% if p.user.is_bawu %}
|
||||||
|
<div style="background-color: var(--important-color); !important" class="tier">吧务</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="tier">level {{ p.user.level }}</div>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="userinfo">
|
<div class="userinfo">
|
||||||
<a href="/home/main?id={{ p.user.user_id }}">{{ p.user.user_name }}</a>
|
<a href="/home/main?id={{ p.user.user_id }}">{{ p.user.user_name }}</a>
|
||||||
|
146
utils.py
Normal file
146
utils.py
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
import asyncio
|
||||||
|
import functools
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
from types import FrameType
|
||||||
|
from typing import Any, Callable
|
||||||
|
|
||||||
|
import async_timeout
|
||||||
|
|
||||||
|
from ..exception import exc_handlers
|
||||||
|
from ..logging import get_logger
|
||||||
|
|
||||||
|
try:
|
||||||
|
import simdjson as jsonlib
|
||||||
|
|
||||||
|
_JSON_PARSER = jsonlib.Parser()
|
||||||
|
parse_json = _JSON_PARSER.parse
|
||||||
|
|
||||||
|
except ImportError:
|
||||||
|
import json as jsonlib
|
||||||
|
|
||||||
|
parse_json = jsonlib.loads
|
||||||
|
|
||||||
|
pack_json = functools.partial(jsonlib.dumps, separators=(',', ':'))
|
||||||
|
|
||||||
|
if sys.version_info >= (3, 9):
|
||||||
|
|
||||||
|
def removeprefix(s: str, prefix: str) -> str:
|
||||||
|
"""
|
||||||
|
移除字符串前缀
|
||||||
|
|
||||||
|
Args:
|
||||||
|
s (str): 待移除前缀的字符串
|
||||||
|
prefix (str): 待移除的前缀
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: 移除前缀后的字符串
|
||||||
|
"""
|
||||||
|
|
||||||
|
return s.removeprefix(prefix)
|
||||||
|
|
||||||
|
def removesuffix(s: str, suffix: str) -> str:
|
||||||
|
"""
|
||||||
|
移除字符串前缀
|
||||||
|
|
||||||
|
Args:
|
||||||
|
s (str): 待移除前缀的字符串
|
||||||
|
suffix (str): 待移除的前缀
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: 移除前缀后的字符串
|
||||||
|
"""
|
||||||
|
|
||||||
|
return s.removesuffix(suffix)
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
def removeprefix(s: str, prefix: str) -> str:
|
||||||
|
"""
|
||||||
|
移除字符串前缀
|
||||||
|
|
||||||
|
Args:
|
||||||
|
s (str): 待移除前缀的字符串
|
||||||
|
prefix (str): 待移除的前缀
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: 移除前缀后的字符串
|
||||||
|
|
||||||
|
Note:
|
||||||
|
该函数不会拷贝字符串
|
||||||
|
"""
|
||||||
|
|
||||||
|
if s.startswith(prefix):
|
||||||
|
return s[len(prefix) :]
|
||||||
|
else:
|
||||||
|
return s
|
||||||
|
|
||||||
|
def removesuffix(s: str, suffix: str) -> str:
|
||||||
|
"""
|
||||||
|
移除字符串后缀
|
||||||
|
该函数将不会拷贝字符串
|
||||||
|
|
||||||
|
Args:
|
||||||
|
s (str): 待移除前缀的字符串
|
||||||
|
suffix (str): 待移除的前缀
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: 移除前缀后的字符串
|
||||||
|
|
||||||
|
Note:
|
||||||
|
该函数不会拷贝字符串
|
||||||
|
"""
|
||||||
|
|
||||||
|
if s.endswith(suffix):
|
||||||
|
return s[: len(suffix)]
|
||||||
|
else:
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
def is_portrait(portrait: str) -> bool:
|
||||||
|
"""
|
||||||
|
简单判断输入是否符合portrait格式
|
||||||
|
"""
|
||||||
|
|
||||||
|
return isinstance(portrait, str) and portrait.startswith('tb.')
|
||||||
|
|
||||||
|
|
||||||
|
def timeout(delay: float, loop: asyncio.AbstractEventLoop) -> async_timeout.Timeout:
|
||||||
|
now = loop.time()
|
||||||
|
when = round(now) + delay
|
||||||
|
return async_timeout.timeout_at(when)
|
||||||
|
|
||||||
|
|
||||||
|
def log_success(frame: FrameType, log_str: str = '', log_level: int = logging.INFO):
|
||||||
|
"""
|
||||||
|
成功日志
|
||||||
|
|
||||||
|
Args:
|
||||||
|
frame (FrameType): 帧对象
|
||||||
|
log_str (str): 附加日志
|
||||||
|
log_level (int): 日志等级
|
||||||
|
"""
|
||||||
|
|
||||||
|
meth_name = frame.f_code.co_name
|
||||||
|
log_str = "Suceeded. " + log_str
|
||||||
|
logger = get_logger()
|
||||||
|
if logger.isEnabledFor(log_level):
|
||||||
|
record = logger.makeRecord(logger.name, log_level, None, 0, log_str, None, None, meth_name)
|
||||||
|
logger.handle(record)
|
||||||
|
|
||||||
|
|
||||||
|
def handle_exception(null_ret_factory: Callable[[], Any], no_format: bool = False, log_level: int = logging.WARNING):
|
||||||
|
"""
|
||||||
|
处理request抛出的异常
|
||||||
|
|
||||||
|
Args:
|
||||||
|
null_ret_factory (Callable[[], Any]): 空构造工厂 用于返回一个默认值
|
||||||
|
no_format (bool, optional): 不需要再次格式化日志字符串 常见于不论成功与否都会记录日志的api. Defaults to False.
|
||||||
|
log_level (int, optional): 日志等级. Defaults to logging.WARNING.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def wrapper(func):
|
||||||
|
async def awrapper(*args, **kwargs):
|
||||||
|
return await func(*args, **kwargs)
|
||||||
|
return awrapper
|
||||||
|
return wrapper
|
Loading…
Reference in New Issue
Block a user