add several features

This commit is contained in:
John Xina 2023-07-12 22:52:47 +08:00
parent f17e332ee4
commit 9df44d5a72
7 changed files with 131 additions and 32 deletions

82
app.py
View File

@ -2,7 +2,6 @@ import asyncio
import aiotieba import aiotieba
from aioflask import render_template, request, escape from aioflask import render_template, request, escape
from flask_caching import Cache
from urllib.parse import quote_plus from urllib.parse import quote_plus
from datetime import datetime from datetime import datetime
@ -14,6 +13,21 @@ from extra import *
###################################################################### ######################################################################
# Clean a leading part and append the text.
def append_with_leading_clean(orig, content):
if orig.endswith('<br>'):
return orig[:-4] + content
else:
return orig + content
# Return the corresponding user name for an id.
async def cache_name_from_id(c, i):
if not cache.get(i):
r = await c.get_user_info(i, require=aiotieba.enums.ReqUInfo.USER_NAME)
cache.set(i, r)
######################################################################
# Convert a timestamp to its simpliest readable date format. # Convert a timestamp to its simpliest readable date format.
@app.template_filter('simpledate') @app.template_filter('simpledate')
def _jinja2_filter_simpledate(ts): def _jinja2_filter_simpledate(ts):
@ -44,28 +58,44 @@ def _jinja2_filter_trim(text):
# Format fragments to its equiviant HTML. # Format fragments to its equiviant HTML.
@app.template_filter('translate') @app.template_filter('translate')
def _jinja2_filter_translate(frags): async def _jinja2_filter_translate(frags, reply_id=0):
htmlfmt = '' htmlfmt = ''
if reply_id:
htmlfmt += f'<a href="/home/main?id={reply_id}">@{ cache.get(reply_id) }</a> '
for frag in frags: for i in range(len(frags)):
frag = frags[i]
if isinstance(frag, FragText): if isinstance(frag, FragText):
subfrags = frag.text.split('\n') subfrags = frag.text.split('\n')
for subfrag in subfrags: for subfrag in subfrags:
htmlfmt += '<p>' + str(escape(subfrag)) + '</p>' htmlfmt += str(escape(subfrag)) + '<br>'
elif isinstance(frag, FragImage_p): elif isinstance(frag, FragImage_p):
htmlfmt += \ htmlfmt += \
f'<a target="_blank" href="/proxy/pic/{ extract_image_name(frag.origin_src) }">' \ f'<a target="_blank" href="/proxy/pic/{ extract_image_name(frag.origin_src) }">' \
f'<img width="{ frag.show_width}" height="{ frag.show_height }" '\ f'<img width="{ frag.show_width}" height="{ frag.show_height }" '\
f'src="/proxy/pic/{ extract_image_name(frag.src) }"></a>' f'src="/proxy/pic/{ extract_image_name(frag.src) }"></a>'
elif isinstance(frag, FragEmoji_p): elif isinstance(frag, FragEmoji_p):
clear_leading = False htmlfmt = append_with_leading_clean(htmlfmt,
if htmlfmt.endswith('</p>'): f'<img class="emoticons" alt="[{ frag.desc }]"'
clear_leading = True f'src="/static/emoticons/{ quote_plus(frag.desc) }.png">')
htmlfmt = htmlfmt.rstrip('</p>') if i+1 < len(frags) and isinstance(frags[i+1], FragImage_p):
htmlfmt += f'<img class="emoticons" alt="[{ frag.desc }]" src="/static/emoticons/{ quote_plus(frag.desc) }.png">' htmlfmt += '<br>'
if clear_leading: elif isinstance(frag, FragLink):
htmlfmt += '</p>' markup = '<a '; url = frag.raw_url
if frag.is_external:
markup += 'style="text-color: #ff0000;" '
else:
url = frag.raw_url.lstrip('https://tieba.baidu.com')
markup += f'href="{ url }">{ frag.title }</a>'
htmlfmt = append_with_leading_clean(htmlfmt, markup)
elif isinstance(frag, FragAt):
htmlfmt = append_with_leading_clean(htmlfmt,
f'<a href="/home/main?id={ frag.user_id }">{ frag.text }</a>')
else:
print('Unhandled: ', type(frag))
print(frag)
return htmlfmt return htmlfmt
###################################################################### ######################################################################
@ -77,23 +107,39 @@ async def thread_view(tid):
async with aiotieba.Client() as tieba: async with aiotieba.Client() as tieba:
# Default to 15 posts per page, confirm to tieba.baidu.com # Default to 15 posts per page, confirm to tieba.baidu.com
thread_info = await tieba.get_posts(tid, rn=15, pn=pn) thread_info = await tieba.get_posts(tid, rn=15, pn=pn,
with_comments=should_fetch_comments)
for post in thread_info: available_users = []
print(post.comments) for floor in thread_info:
for comment in floor.comments:
available_users.append(comment.author_id)
cache.set(comment.author_id, comment.user.user_name)
all_users = {}
for floor in thread_info:
for comment in floor.comments:
if 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) return await render_template('thread.html', info=thread_info)
@app.route('/f') @app.route('/f')
async def forum_view(): async def forum_view():
fname = request.args['kw'] fname = request.args['kw']
pn = int(request.args.get('pn') or 1) pn = int(request.args.get('pn') or 1)
sort = int(request.args.get('sort') or 0)
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)) tieba.get_threads(fname, rn=50, pn=pn, sort=sort))
return await render_template('bar.html', info=forum_info, threads=threads) return await render_template('bar.html', info=forum_info, threads=threads, sort=sort)
if __name__ == '__main__': if __name__ == '__main__':
app.run(debug=True) app.run(debug=True)

View File

@ -6,9 +6,13 @@ import re
from shared import * from shared import *
# TODO: known bug, can't extract from super old editor images.
def extract_image_name(url): def extract_image_name(url):
match = re.search(r'/(\w+)\.jpg', url) match = re.search(r'/(\w+)\.jpg', url)
return match.group(1) + '.jpg' try:
return match.group(1) + '.jpg'
except:
return '404.jpg'
@cache.cached(timeout=60, key_prefix='tieba_info') @cache.cached(timeout=60, key_prefix='tieba_info')
def find_tieba_info(tname): def find_tieba_info(tname):

11
main.py
View File

@ -138,11 +138,12 @@ class ReverseProxyResource(Resource):
# To start this function for testing: python -c 'import main; main.twisted_start()' # To start this function for testing: python -c 'import main; main.twisted_start()'
def twisted_start(): def twisted_start():
flask_res = proxy.ReverseProxyResource('127.0.0.1', 5000, b'') flask_port = int(app.config['SERVER_NAME'].split(':')[1])
flask_res = proxy.ReverseProxyResource('127.0.0.1', flask_port, b'')
flask_res.putChild(b'proxy', ReverseProxyResource(b'/proxy')) flask_res.putChild(b'proxy', ReverseProxyResource(b'/proxy'))
flask_res.putChild(b'static', File('static')) flask_res.putChild(b'static', File('static'))
flask_port = int(app.config['SERVER_NAME'].split(':')[1]) print(f' *** SERVER IS RUNNING ON PORT {flask_port-1} ***')
site = server.Site(flask_res) site = server.Site(flask_res)
reactor.listenTCP(flask_port-1, site) reactor.listenTCP(flask_port-1, site)
@ -154,11 +155,7 @@ def flask_start():
# If we're executed directly, also start the flask daemon. # If we're executed directly, also start the flask daemon.
if __name__ == '__main__': if __name__ == '__main__':
flask_port = int(app.config['SERVER_NAME'].split(':')[1])
print(f' *** SERVER IS RUNNING ON PORT {flask_port-1} ***')
twisted_start()
flask_task = multiprocessing.Process(target=flask_start) flask_task = multiprocessing.Process(target=flask_start)
flask_task.daemon = True # Exit the child if the parent was killed :-( flask_task.daemon = True # Exit the child if the parent was killed :-(
flask_task.start() flask_task.start()
twisted_start()

View File

@ -12,7 +12,13 @@ def awaitify(sync_func):
app = Flask(__name__) app = Flask(__name__)
app.config['SERVER_NAME'] = ':6666' ######################################################################
app.config['SERVER_NAME'] = '127.0.0.1:8886'
should_fetch_comments = True
######################################################################
app.config['CACHE_TYPE'] = 'SimpleCache' app.config['CACHE_TYPE'] = 'SimpleCache'
cache = Cache(app) cache = Cache(app)

View File

@ -2,6 +2,21 @@
max-width: 5% !important; max-width: 5% !important;
} }
.vlist {
display: flex;
justify-content: space-between;
}
.vlist > div {
flex: 1;
text-align: center;
margin: 0.5em;
}
.current-sel {
color: inherit !important;
}
/* global styling */ /* global styling */
:root { :root {
--bg-color: #eeeecc; --bg-color: #eeeecc;

View File

@ -21,6 +21,16 @@
</div> </div>
</div> </div>
</header> </header>
<div class="list">
<div class="vlist">
<div><a {% if sort == 0 %} class="current-sel" {% endif %}
href="/f?kw={{ info['name'] }}&pn={{ threads.page.current_page }}&sort=0">时下热门</a></div>
<div><a {% if sort == 1 %} class="current-sel" {% endif %}
href="/f?kw={{ info['name'] }}&pn={{ threads.page.current_page }}&sort=1">最新发布</a></div>
<div><a {% if sort == 5 %} class="current-sel" {% endif %}
href="/f?kw={{ info['name'] }}&pn={{ threads.page.current_page }}&sort=5">最新回复</a></div>
</div>
</div>
<div class="list"> <div class="list">
{% for t in threads %} {% for t in threads %}
<div class="thread"> <div class="thread">
@ -34,9 +44,12 @@
%}<span class="tag tag-blue">置顶</span>{% %}<span class="tag tag-blue">置顶</span>{%
endif %}{% if t.is_good endif %}{% if t.is_good
%}<span class="tag tag-red"></span>{% endif %}<span class="tag tag-red"></span>{% endif
%}<a href="/p/{{ t.tid }}">{{ t.title }} </a> %}<a href="/p/{{ t.tid }}">{{ t.title if t.title else t.text|trim }} </a>
</div> </div>
{% if t.title %}
<div>{{ t.text[(t.title|length):]|trim }}</div> <div>{{ t.text[(t.title|length):]|trim }}</div>
{% endif %}
</div> </div>
<div class="participants"> <div class="participants">
<div>🧑<a href="">{{ t.user.user_name }}</a></div> <div>🧑<a href="">{{ t.user.user_name }}</a></div>
@ -47,13 +60,13 @@
<div class="paginator"> <div class="paginator">
{% if threads.page.current_page > 1 %} {% if threads.page.current_page > 1 %}
<a href="/f?kw={{ info['name'] }}">首页</a> <a href="/f?kw={{ info['name'] }}&sort={{ sort }}">首页</a>
{% endif %} {% endif %}
{% for i in range(5) %} {% for i in range(5) %}
{% set np = threads.page.current_page - 5 + i %} {% set np = threads.page.current_page - 5 + i %}
{% if np > 0 %} {% if np > 0 %}
<a href="/f?kw={{ info['name'] }}&pn={{ np }}">{{ np }}</a> <a href="/f?kw={{ info['name'] }}&pn={{ np }}&sort={{ sort }}">{{ np }}</a>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
@ -62,12 +75,12 @@
{% for i in range(5) %} {% for i in range(5) %}
{% set np = threads.page.current_page + 1 + i %} {% set np = threads.page.current_page + 1 + i %}
{% if np <= threads.page.total_page %} {% if np <= threads.page.total_page %}
<a href="/f?kw={{ info['name'] }}&pn={{ np }}">{{ np }}</a> <a href="/f?kw={{ info['name'] }}&pn={{ np }}&sort={{ sort }}">{{ np }}</a>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% if threads.page.current_page < threads.page.total_page %} {% if threads.page.current_page < threads.page.total_page %}
<a href="/f?kw={{ info['name'] }}&pn={{ threads.page.total_page }}">尾页</a> <a href="/f?kw={{ info['name'] }}&pn={{ threads.page.total_page }}&sort={{ sort }}">尾页</a>
{% endif %} {% endif %}
</div> </div>
</div> </div>

View File

@ -30,6 +30,24 @@
</div> </div>
<small class="date">{{ p.create_time|date }}</small> <small class="date">{{ p.create_time|date }}</small>
<small class="permalink"><a href="#{{ p.floor }}">{{ p.floor }}</a></small> <small class="permalink"><a href="#{{ p.floor }}">{{ p.floor }}</a></small>
{% if p.comments %}
<div class="replies">
{% for comment in p.comments %}
<div class="post">
<img class="avatar" src="/proxy/avatar/{{ comment.user.portrait }}">
<div>
<div class="userinfo">
<a href="/home/main?id={{ comment.user.user_id }}">{{ comment.user.user_name }}</a>
</div>
<div class="content">
{{ comment.contents|translate(comment.reply_to_id)|safe }}
</div>
<small class="date">{{ comment.create_time|date }}</small>
</div>
</div>
{% endfor %}
</div>
{% endif %}
</div> </div>
</div> </div>
{% endfor %} {% endfor %}