mirror of
https://0xacab.org/johnxina/rat.git
synced 2024-12-23 04:59:09 +00:00
minimal
This commit is contained in:
parent
aafa24ddc7
commit
f17e332ee4
42
app.py
42
app.py
@ -1,12 +1,16 @@
|
||||
import asyncio
|
||||
import aiotieba
|
||||
|
||||
from aioflask import Flask, render_template, request
|
||||
from aioflask import render_template, request, escape
|
||||
from flask_caching import Cache
|
||||
from urllib.parse import quote_plus
|
||||
from datetime import datetime
|
||||
|
||||
from extra import *
|
||||
from aiotieba.api.get_posts._classdef import *
|
||||
from aiotieba.api._classdef.contents import *
|
||||
|
||||
app = Flask(__name__)
|
||||
from shared import *
|
||||
from extra import *
|
||||
|
||||
######################################################################
|
||||
|
||||
@ -38,6 +42,32 @@ def _jinja2_filter_intsep(i):
|
||||
def _jinja2_filter_trim(text):
|
||||
return text[:78] + '……' if len(text) > 78 else text
|
||||
|
||||
# Format fragments to its equiviant HTML.
|
||||
@app.template_filter('translate')
|
||||
def _jinja2_filter_translate(frags):
|
||||
htmlfmt = ''
|
||||
|
||||
for frag in frags:
|
||||
if isinstance(frag, FragText):
|
||||
subfrags = frag.text.split('\n')
|
||||
for subfrag in subfrags:
|
||||
htmlfmt += '<p>' + str(escape(subfrag)) + '</p>'
|
||||
elif isinstance(frag, FragImage_p):
|
||||
htmlfmt += \
|
||||
f'<a target="_blank" href="/proxy/pic/{ extract_image_name(frag.origin_src) }">' \
|
||||
f'<img width="{ frag.show_width}" height="{ frag.show_height }" '\
|
||||
f'src="/proxy/pic/{ extract_image_name(frag.src) }"></a>'
|
||||
elif isinstance(frag, FragEmoji_p):
|
||||
clear_leading = False
|
||||
if htmlfmt.endswith('</p>'):
|
||||
clear_leading = True
|
||||
htmlfmt = htmlfmt.rstrip('</p>')
|
||||
htmlfmt += f'<img class="emoticons" alt="[{ frag.desc }]" src="/static/emoticons/{ quote_plus(frag.desc) }.png">'
|
||||
if clear_leading:
|
||||
htmlfmt += '</p>'
|
||||
|
||||
return htmlfmt
|
||||
|
||||
######################################################################
|
||||
|
||||
@app.route('/p/<tid>')
|
||||
@ -49,8 +79,8 @@ async def thread_view(tid):
|
||||
# Default to 15 posts per page, confirm to tieba.baidu.com
|
||||
thread_info = await tieba.get_posts(tid, rn=15, pn=pn)
|
||||
|
||||
for fragment in thread_info[0].contents:
|
||||
print(fragment)
|
||||
for post in thread_info:
|
||||
print(post.comments)
|
||||
|
||||
return await render_template('thread.html', info=thread_info)
|
||||
|
||||
@ -60,7 +90,7 @@ async def forum_view():
|
||||
pn = int(request.args.get('pn') or 1)
|
||||
|
||||
async with aiotieba.Client() as tieba:
|
||||
forum_info, threads = await asyncio.gather(find_tieba_info(fname),
|
||||
forum_info, threads = await asyncio.gather(awaitify(find_tieba_info)(fname),
|
||||
tieba.get_threads(fname, rn=50, pn=pn))
|
||||
|
||||
return await render_template('bar.html', info=forum_info, threads=threads)
|
||||
|
13
extra.py
13
extra.py
@ -4,7 +4,14 @@ import requests
|
||||
import bs4
|
||||
import re
|
||||
|
||||
async def find_tieba_info(tname):
|
||||
from shared import *
|
||||
|
||||
def extract_image_name(url):
|
||||
match = re.search(r'/(\w+)\.jpg', url)
|
||||
return match.group(1) + '.jpg'
|
||||
|
||||
@cache.cached(timeout=60, key_prefix='tieba_info')
|
||||
def find_tieba_info(tname):
|
||||
"""Get the tiebat avatar for the forum name.
|
||||
|
||||
:param tname: the name of the target forum.
|
||||
@ -17,9 +24,7 @@ async def find_tieba_info(tname):
|
||||
soup = bs4.BeautifulSoup(res.text, 'html.parser')
|
||||
|
||||
elems = soup.select('#forum-card-head')
|
||||
match = re.search(r'/(\w+)\.jpg', elems[0]['src'])
|
||||
|
||||
info['avatar'] = match.group(1) + '.jpg'
|
||||
info['avatar'] = extract_image_name(elems[0]['src'])
|
||||
|
||||
footer = soup.select('.th_footer_l')[0]
|
||||
stat_elems = footer.findAll('span', {'class': 'red_text'}, recursive=False)
|
||||
|
14
main.py
14
main.py
@ -6,6 +6,7 @@ from urllib.parse import quote as urlquote, urlparse, urlunparse
|
||||
from twisted.web.http import _QUEUED_SENTINEL, HTTPChannel, HTTPClient, Request
|
||||
from twisted.web.resource import Resource
|
||||
from twisted.web import proxy, server
|
||||
from twisted.web.static import File
|
||||
from twisted.internet.protocol import ClientFactory
|
||||
from twisted.internet import reactor, utils
|
||||
|
||||
@ -139,18 +140,25 @@ class ReverseProxyResource(Resource):
|
||||
def twisted_start():
|
||||
flask_res = proxy.ReverseProxyResource('127.0.0.1', 5000, b'')
|
||||
flask_res.putChild(b'proxy', ReverseProxyResource(b'/proxy'))
|
||||
flask_res.putChild(b'static', File('static'))
|
||||
|
||||
flask_port = int(app.config['SERVER_NAME'].split(':')[1])
|
||||
|
||||
site = server.Site(flask_res)
|
||||
reactor.listenTCP(5001, site)
|
||||
reactor.listenTCP(flask_port-1, site)
|
||||
reactor.run()
|
||||
|
||||
# To start this function for testing: python -c 'import main; main.flask_start()'
|
||||
def flask_start():
|
||||
app.run(port=5000+1)
|
||||
app.run()
|
||||
|
||||
# If we're executed directly, also start the flask daemon.
|
||||
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.daemon = True # Exit the child if the parent was killed :-(
|
||||
flask_task.start()
|
||||
twisted_start()
|
||||
|
@ -1,5 +1,7 @@
|
||||
aioflask==0.4.0
|
||||
flask==2.1.3
|
||||
aiotieba==3.4.5
|
||||
Flask-Caching
|
||||
beautifulsoup4
|
||||
requests
|
||||
twisted
|
||||
|
18
shared.py
Normal file
18
shared.py
Normal file
@ -0,0 +1,18 @@
|
||||
from aioflask import Flask
|
||||
from flask_caching import Cache
|
||||
|
||||
from functools import wraps
|
||||
|
||||
def awaitify(sync_func):
|
||||
"""Wrap a synchronous callable to allow ``await``'ing it"""
|
||||
@wraps(sync_func)
|
||||
async def async_func(*args, **kwargs):
|
||||
return sync_func(*args, **kwargs)
|
||||
return async_func
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
app.config['SERVER_NAME'] = ':6666'
|
||||
|
||||
app.config['CACHE_TYPE'] = 'SimpleCache'
|
||||
cache = Cache(app)
|
@ -1,19 +1,25 @@
|
||||
.emoticons {
|
||||
max-width: 5% !important;
|
||||
}
|
||||
|
||||
/* global styling */
|
||||
:root {
|
||||
--bg-color: #eeeecc;
|
||||
--fg-color: #ffffdd;
|
||||
--replies-color: #f0f0d0;
|
||||
--text-color: #663333;
|
||||
--border-color: #66333388;
|
||||
--primary-color: #0066cc;
|
||||
--important-color: #ff0000;
|
||||
}
|
||||
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--bg-color: #000033;
|
||||
--fg-color: #202044;
|
||||
--replies-color: #16163a;
|
||||
--text-color: #cccccc;
|
||||
--border-color: #cccccc44;
|
||||
--primary-color: #6699ff;
|
||||
--important-color: #ff0000;
|
||||
}
|
||||
@ -27,6 +33,15 @@ body {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
footer {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
padding: 1rem;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
a[href] {
|
||||
color: var(--primary-color);
|
||||
text-decoration: none;
|
||||
@ -40,7 +55,24 @@ img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.bar-nav, .thread-nav {
|
||||
.paginator {
|
||||
padding: 1rem;
|
||||
gap: .3rem;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.paginator a {
|
||||
flex: 0 0 auto;
|
||||
height: 1rem;
|
||||
line-height: 1rem;
|
||||
padding: .5rem;
|
||||
text-align: center;
|
||||
background-color: var(--replies-color);
|
||||
}
|
||||
|
||||
header {
|
||||
background-color: var(--fg-color);
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@ -50,38 +82,29 @@ img {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.thread-nav > .title {
|
||||
font-size: 1.2rem;
|
||||
flex: 1 0 70%;
|
||||
}
|
||||
|
||||
.thread-nav > .from {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.bar-nav > img {
|
||||
width: 5rem;
|
||||
height: 5rem;
|
||||
}
|
||||
|
||||
.bar-nav .title {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.bar-nav .stats small {
|
||||
margin-right: .5rem;
|
||||
}
|
||||
|
||||
.list {
|
||||
background-color: var(--fg-color);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
/* thread.html: nav bar */
|
||||
|
||||
.thread-nav .title {
|
||||
font-size: 1.2rem;
|
||||
flex: 1 0 70%;
|
||||
}
|
||||
|
||||
.thread-nav .from {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
/* thread.html: user post */
|
||||
|
||||
.post {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0 1rem;
|
||||
border-bottom: 1px solid var(--text-color);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
@ -120,6 +143,39 @@ img {
|
||||
float: right;
|
||||
}
|
||||
|
||||
/* thread.html: replies to a user post */
|
||||
|
||||
.post .replies {
|
||||
background-color: var(--replies-color);
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.post .replies .post {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.post .replies .post .avatar {
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
}
|
||||
|
||||
/* bar.html: nav bar */
|
||||
|
||||
.bar-nav img {
|
||||
width: 5rem;
|
||||
height: 5rem;
|
||||
}
|
||||
|
||||
.bar-nav .title {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.bar-nav .stats small {
|
||||
margin-right: .5rem;
|
||||
}
|
||||
|
||||
/* bar.html: thread list */
|
||||
|
||||
.thread {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
@ -155,12 +211,3 @@ img {
|
||||
padding-left: .3rem;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
footer {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
padding: 1rem;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
@ -44,6 +44,32 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="paginator">
|
||||
{% if threads.page.current_page > 1 %}
|
||||
<a href="/f?kw={{ info['name'] }}">首页</a>
|
||||
{% endif %}
|
||||
|
||||
{% for i in range(5) %}
|
||||
{% set np = threads.page.current_page - 5 + i %}
|
||||
{% if np > 0 %}
|
||||
<a href="/f?kw={{ info['name'] }}&pn={{ np }}">{{ np }}</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
<a>{{ threads.page.current_page }}</a>
|
||||
|
||||
{% for i in range(5) %}
|
||||
{% set np = threads.page.current_page + 1 + i %}
|
||||
{% if np <= threads.page.total_page %}
|
||||
<a href="/f?kw={{ info['name'] }}&pn={{ np }}">{{ np }}</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% if threads.page.current_page < threads.page.total_page %}
|
||||
<a href="/f?kw={{ info['name'] }}&pn={{ threads.page.total_page }}">尾页</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<footer>
|
||||
<div><a href="#">RAT Ain't Tieba</a></div>
|
||||
|
@ -26,14 +26,41 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="content">
|
||||
{{ p['text'] }}
|
||||
{{ p.contents|translate|safe }}
|
||||
</div>
|
||||
<small class="date">{{ p.create_time|date }}</small>
|
||||
<small class="permalink"><a href="#1">{{ p.floor }}</a></small>
|
||||
<small class="permalink"><a href="#{{ p.floor }}">{{ p.floor }}</a></small>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="paginator">
|
||||
{% if info.page.current_page > 1 %}
|
||||
<a href="/p/{{ info.thread.tid }}">首页</a>
|
||||
{% endif %}
|
||||
|
||||
{% for i in range(5) %}
|
||||
{% set np = info.page.current_page - 5 + i %}
|
||||
{% if np > 0 %}
|
||||
<a href="/p/{{ info.thread.tid }}?pn={{ np }}">{{ np }}</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
<a>{{ info.page.current_page }}</a>
|
||||
|
||||
{% for i in range(5) %}
|
||||
{% set np = info.page.current_page + 1 + i %}
|
||||
{% if np <= info.page.total_page %}
|
||||
<a href="/p/{{ info.thread.tid }}?pn={{ np }}">{{ np }}</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% if info.page.current_page < info.page.total_page %}
|
||||
<a href="/p/{{ info.thread.tid }}?pn={{ info.page.total_page }}">尾页</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<div><a href="/">RAT Ain't Tieba</a></div>
|
||||
<div><a href="#">自豪地以 AGPLv3 释出</a></div>
|
||||
|
Loading…
Reference in New Issue
Block a user