mirror of
https://0xacab.org/johnxina/rat.git
synced 2024-12-23 04:59:09 +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
|
||||
|
26
app.py
26
app.py
@ -119,14 +119,12 @@ async def thread_view(tid):
|
||||
all_users = {}
|
||||
for floor in thread_info:
|
||||
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.pop(0, None)
|
||||
all_users = list(all_users.keys())
|
||||
|
||||
await asyncio.gather(*(cache_name_from_id(tieba, i) for i in all_users))
|
||||
|
||||
|
||||
|
||||
return await render_template('thread.html', info=thread_info)
|
||||
|
||||
@app.route('/f')
|
||||
@ -138,8 +136,28 @@ async def forum_view():
|
||||
async with aiotieba.Client() as tieba:
|
||||
forum_info, threads = await asyncio.gather(awaitify(find_tieba_info)(fname),
|
||||
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)
|
||||
|
||||
@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__':
|
||||
app.run(debug=True)
|
||||
|
11
extra.py
11
extra.py
@ -14,7 +14,7 @@ def extract_image_name(url):
|
||||
except:
|
||||
return '404.jpg'
|
||||
|
||||
@cache.cached(timeout=60, key_prefix='tieba_info')
|
||||
@cache.memoize(timeout=60)
|
||||
def find_tieba_info(tname):
|
||||
"""Get the tiebat avatar for the forum name.
|
||||
|
||||
@ -24,7 +24,14 @@ def find_tieba_info(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')
|
||||
|
||||
elems = soup.select('#forum-card-head')
|
||||
|
@ -17,6 +17,18 @@
|
||||
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 */
|
||||
:root {
|
||||
--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">
|
||||
{% for p in info %}
|
||||
<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 class="userinfo">
|
||||
<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