Multiple features

This commit is contained in:
John Xina 2023-07-13 18:48:49 +08:00
parent 9df44d5a72
commit 965c8f2cc0
7 changed files with 261 additions and 7 deletions

View 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
View File

@ -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)

View File

@ -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')

View File

@ -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
View 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;">
&#x1F643;
</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>

View File

@ -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
View 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