|
|
|
@ -1,12 +1,13 @@
|
|
|
|
|
from flask import Flask, request, render_template, send_from_directory, abort, redirect |
|
|
|
|
from flask_sqlalchemy import SQLAlchemy |
|
|
|
|
import re |
|
|
|
|
import random |
|
|
|
|
import string |
|
|
|
|
|
|
|
|
|
from flask import Flask, request, abort, redirect |
|
|
|
|
from flask_limiter import Limiter |
|
|
|
|
from flask_limiter.util import get_remote_address |
|
|
|
|
from flask_migrate import Migrate |
|
|
|
|
|
|
|
|
|
from mastodon import Mastodon |
|
|
|
|
import re, random, string, datetime, hashlib,requests |
|
|
|
|
|
|
|
|
|
from models import db, User, Post, Comment, Attention, TagRecord, Syslog |
|
|
|
|
from utils import require_token, map_post, map_comment, map_syslog, check_attention, hash_name, look, get_num, tmp_token |
|
|
|
|
|
|
|
|
@ -19,8 +20,6 @@ app.config.from_pyfile('config.py')
|
|
|
|
|
db.init_app(app) |
|
|
|
|
migrate = Migrate(app, db) |
|
|
|
|
|
|
|
|
|
#with app.app_context(): |
|
|
|
|
# db.create_all() |
|
|
|
|
|
|
|
|
|
CS_LOGIN_URL = Mastodon(api_base_url=app.config['MASTODON_URL']) \ |
|
|
|
|
.auth_request_url( |
|
|
|
@ -28,9 +27,6 @@ CS_LOGIN_URL = Mastodon(api_base_url=app.config['MASTODON_URL']) \
|
|
|
|
|
redirect_uris=app.config['REDIRECT_URI'], |
|
|
|
|
scopes=['read:accounts'] |
|
|
|
|
) |
|
|
|
|
THUHOLE_SEND_URL = f"{app.config.get('THUHOLE_ADDRESS')}/services/thuhole/api.php?action=docomment&PKUHelperAPI=3.0&jsapiver=v0.3.1.133-444340&user_token=" |
|
|
|
|
|
|
|
|
|
THUHOLE_GET_URL = f"{app.config.get('THUHOLE_ADDRESS')}/services/thuhole/api.php?action=getcomment&pid={app.config.get('THUHOLE_PID')}&PKUHelperAPI=3.0&jsapiver=v0.3.1.133-444340&user_token=" |
|
|
|
|
|
|
|
|
|
limiter = Limiter( |
|
|
|
|
app, |
|
|
|
@ -40,62 +36,17 @@ limiter = Limiter(
|
|
|
|
|
|
|
|
|
|
PER_PAGE = 50 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.route('/_login') |
|
|
|
|
@limiter.limit("5 / minute, 50 / hour") |
|
|
|
|
def login(): |
|
|
|
|
provider = request.args.get('p') |
|
|
|
|
if provider == 'cs': |
|
|
|
|
return redirect(CS_LOGIN_URL) |
|
|
|
|
elif provider == 'thuhole': |
|
|
|
|
token = request.args.get('token') |
|
|
|
|
try: |
|
|
|
|
rt = ''.join(random.choices(string.ascii_letters + string.digits, k=16)) |
|
|
|
|
headers = { |
|
|
|
|
'user-agent': 'holeBot; hole.thu.monster', |
|
|
|
|
'host': app.config.get('THUHOLE_HOST') |
|
|
|
|
} |
|
|
|
|
r = requests.post( |
|
|
|
|
THUHOLE_SEND_URL+token, |
|
|
|
|
headers=headers, |
|
|
|
|
data={ |
|
|
|
|
'pid': app.config.get('THUHOLE_PID'), |
|
|
|
|
'text': rt, |
|
|
|
|
'user_token': token |
|
|
|
|
} |
|
|
|
|
) |
|
|
|
|
r = requests.get( |
|
|
|
|
THUHOLE_GET_URL+token, |
|
|
|
|
headers=headers |
|
|
|
|
) |
|
|
|
|
c = r.json() |
|
|
|
|
data = c.get('data') |
|
|
|
|
|
|
|
|
|
mat = [c['name'] for c in data if c['text'].endswith(rt)] |
|
|
|
|
|
|
|
|
|
if mat: |
|
|
|
|
name = mat[0] |
|
|
|
|
else: |
|
|
|
|
abort(401) |
|
|
|
|
|
|
|
|
|
name = 'th_' + ''.join(map(lambda s: s[0], name.split())) |
|
|
|
|
|
|
|
|
|
u = v = User.query.filter_by(name=name).first() |
|
|
|
|
|
|
|
|
|
if not u: |
|
|
|
|
u = User(name=name) |
|
|
|
|
db.session.add(u) |
|
|
|
|
|
|
|
|
|
if not v or False: #TODO: reset token |
|
|
|
|
u.token = ''.join(random.choices(string.ascii_letters + string.digits, k=16)) |
|
|
|
|
db.session.commit() |
|
|
|
|
|
|
|
|
|
return redirect('/?token='+ u.token) |
|
|
|
|
except : |
|
|
|
|
abort(401) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
abort(404) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.route('/_auth') |
|
|
|
|
@limiter.limit("5 / minute") |
|
|
|
|
def auth(): |
|
|
|
@ -106,7 +57,7 @@ def auth():
|
|
|
|
|
client_secret=app.config['CLIENT_SECRET'], |
|
|
|
|
api_base_url=app.config['MASTODON_URL'] |
|
|
|
|
) |
|
|
|
|
token = client.log_in( |
|
|
|
|
client.log_in( |
|
|
|
|
code=code, |
|
|
|
|
redirect_uri=app.config['REDIRECT_URI'], |
|
|
|
|
scopes=['read:accounts'] |
|
|
|
@ -122,10 +73,15 @@ def auth():
|
|
|
|
|
db.session.add(u) |
|
|
|
|
|
|
|
|
|
if not v or False: # TODO: reset token |
|
|
|
|
u.token = ''.join(random.choices(string.ascii_letters + string.digits, k=16)) |
|
|
|
|
u.token = ''.join( |
|
|
|
|
random.choices( |
|
|
|
|
string.ascii_letters + |
|
|
|
|
string.digits, |
|
|
|
|
k=16)) |
|
|
|
|
db.session.commit() |
|
|
|
|
|
|
|
|
|
return redirect('/?token='+ u.token) |
|
|
|
|
return redirect('/?token=%s' % u.token) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.route('/_api/v1/getlist') |
|
|
|
|
def get_list(): |
|
|
|
@ -136,7 +92,9 @@ def get_list():
|
|
|
|
|
posts = Post.query.filter_by(deleted=False) |
|
|
|
|
if 'no_cw' in request.args: |
|
|
|
|
posts = posts.filter_by(cw=None) |
|
|
|
|
posts = posts.order_by(db.desc('comment_timestamp')) if 'by_c' in request.args else posts.order_by(db.desc('id')) |
|
|
|
|
posts = posts.order_by( |
|
|
|
|
db.desc('comment_timestamp')) if 'by_c' in request.args else posts.order_by( |
|
|
|
|
db.desc('id')) |
|
|
|
|
posts = posts.paginate(p, PER_PAGE) |
|
|
|
|
|
|
|
|
|
data = list(map(map_post, posts.items, [u.name] * len(posts.items))) |
|
|
|
@ -147,15 +105,19 @@ def get_list():
|
|
|
|
|
'count': len(data), |
|
|
|
|
'data': data |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.route('/_api/v1/getone') |
|
|
|
|
def get_one(): |
|
|
|
|
u = require_token() |
|
|
|
|
|
|
|
|
|
pid = get_num(request.args.get('pid')) |
|
|
|
|
pid = request.args.get('pid', type=int) |
|
|
|
|
|
|
|
|
|
post = Post.query.get(pid) |
|
|
|
|
if not post: abort(404) |
|
|
|
|
if post.deleted: abort(451) |
|
|
|
|
if not post: |
|
|
|
|
abort(404) |
|
|
|
|
if post.deleted: |
|
|
|
|
abort(451) |
|
|
|
|
|
|
|
|
|
data = map_post(post, u.name) |
|
|
|
|
|
|
|
|
@ -164,18 +126,45 @@ def get_one():
|
|
|
|
|
'data': data |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.route('/_api/v1/search') |
|
|
|
|
def search(): |
|
|
|
|
u = require_token() |
|
|
|
|
|
|
|
|
|
page = get_num(request.args.get('page')) |
|
|
|
|
pagesize = max(get_num(request.args.get('pagesize')), 200) |
|
|
|
|
page = request.args.get('page', type=int, default=1) |
|
|
|
|
pagesize = min(request.args.get('pagesize', type=int, default=200), 200) |
|
|
|
|
keywords = request.args.get('keywords') |
|
|
|
|
if not keywords: |
|
|
|
|
abort(422) |
|
|
|
|
|
|
|
|
|
pids = [tr.pid for tr in TagRecord.query.filter_by(tag=keywords).order_by(db.desc('pid')).paginate(page, pagesize).items] |
|
|
|
|
|
|
|
|
|
data = [ map_post(Post.query.get(pid), u.name) |
|
|
|
|
for pid in pids if Post.query.get(pid) and not Post.query.get(pid).deleted |
|
|
|
|
tag_pids = TagRecord.query.with_entities( |
|
|
|
|
TagRecord.pid |
|
|
|
|
).filter_by( |
|
|
|
|
tag=keywords |
|
|
|
|
).all() |
|
|
|
|
|
|
|
|
|
print(tag_pids) |
|
|
|
|
|
|
|
|
|
tag_pids = [tag_pid for tag_pid, in tag_pids] or [0] # sql not allowed empty in |
|
|
|
|
|
|
|
|
|
posts = Post.query.filter( |
|
|
|
|
Post.search_text.like("%{}%".format(keywords)) |
|
|
|
|
).filter( |
|
|
|
|
Post.id.notin_(tag_pids) |
|
|
|
|
).order_by( |
|
|
|
|
Post.id.desc() |
|
|
|
|
).limit(pagesize).offset((page - 1) * pagesize).all() |
|
|
|
|
|
|
|
|
|
if page == 1: |
|
|
|
|
posts = Post.query.filter( |
|
|
|
|
Post.id.in_(tag_pids) |
|
|
|
|
).filter_by(deleted=False).order_by( |
|
|
|
|
Post.id.desc() |
|
|
|
|
).all() + posts |
|
|
|
|
|
|
|
|
|
data = [ |
|
|
|
|
map_post(post, u.name) |
|
|
|
|
for post in posts |
|
|
|
|
] |
|
|
|
|
|
|
|
|
|
return { |
|
|
|
@ -185,12 +174,13 @@ def search():
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.route('/_api/v1/dopost', methods=['POST']) |
|
|
|
|
@limiter.limit("50 / hour; 1 / 3 second") |
|
|
|
|
def do_post(): |
|
|
|
|
u = require_token() |
|
|
|
|
|
|
|
|
|
allow_search = request.form.get('allow_search') |
|
|
|
|
print(allow_search) |
|
|
|
|
content = request.form.get('text') |
|
|
|
|
content = content.strip() if content else None |
|
|
|
|
content = '[tmp]\n' + content if u.name[:4] == 'tmp_' else content |
|
|
|
@ -198,12 +188,18 @@ def do_post():
|
|
|
|
|
cw = request.form.get('cw') |
|
|
|
|
cw = cw.strip() if cw else None |
|
|
|
|
|
|
|
|
|
if not content or len(content) > 4096: abort(422) |
|
|
|
|
if cw and len(cw)>32: abort(422) |
|
|
|
|
if not content or len(content) > 4096: |
|
|
|
|
abort(422) |
|
|
|
|
if cw and len(cw) > 32: |
|
|
|
|
abort(422) |
|
|
|
|
|
|
|
|
|
search_text = content.replace( |
|
|
|
|
'\n', '') if allow_search else '' |
|
|
|
|
|
|
|
|
|
p = Post( |
|
|
|
|
name_hash=hash_name(u.name), |
|
|
|
|
content=content, |
|
|
|
|
search_text=search_text, |
|
|
|
|
post_type=post_type, |
|
|
|
|
cw=cw or None, |
|
|
|
|
likenum=1, |
|
|
|
@ -221,11 +217,11 @@ def do_post():
|
|
|
|
|
db.session.add(p) |
|
|
|
|
db.session.commit() |
|
|
|
|
|
|
|
|
|
tags = re.findall('(^|\s)#([^#\s]{1,32})', content) |
|
|
|
|
tags = re.findall('(^|\\s)#([^#\\s]{1,32})', content) |
|
|
|
|
# print(tags) |
|
|
|
|
for t in tags: |
|
|
|
|
tag = t[1] |
|
|
|
|
if not re.match('\d+', tag): |
|
|
|
|
if not re.match('\\d+', tag): |
|
|
|
|
db.session.add(TagRecord(tag=tag, pid=p.id)) |
|
|
|
|
|
|
|
|
|
db.session.add(Attention(name_hash=hash_name(u.name), pid=p.id)) |
|
|
|
@ -236,6 +232,7 @@ def do_post():
|
|
|
|
|
'date': p.id |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.route('/_api/v1/editcw', methods=['POST']) |
|
|
|
|
@limiter.limit("50 / hour; 1 / 2 second") |
|
|
|
|
def edit_cw(): |
|
|
|
@ -245,16 +242,20 @@ def edit_cw():
|
|
|
|
|
pid = get_num(request.form.get('pid')) |
|
|
|
|
|
|
|
|
|
cw = cw.strip() if cw else None |
|
|
|
|
if cw and len(cw)>32: abort(422) |
|
|
|
|
if cw and len(cw) > 32: |
|
|
|
|
abort(422) |
|
|
|
|
|
|
|
|
|
post = Post.query.get(pid) |
|
|
|
|
if not post: abort(404) |
|
|
|
|
if post.deleted: abort(451) |
|
|
|
|
if not post: |
|
|
|
|
abort(404) |
|
|
|
|
if post.deleted: |
|
|
|
|
abort(451) |
|
|
|
|
|
|
|
|
|
if not (u.name in app.config.get('ADMINS') or hash_name(u.name) == post.name_hash): |
|
|
|
|
if not (u.name in app.config.get('ADMINS') |
|
|
|
|
or hash_name(u.name) == post.name_hash): |
|
|
|
|
abort(403) |
|
|
|
|
|
|
|
|
|
post.cw = cw; |
|
|
|
|
post.cw = cw |
|
|
|
|
db.session.commit() |
|
|
|
|
|
|
|
|
|
return {'code': 0} |
|
|
|
@ -267,8 +268,10 @@ def get_comment():
|
|
|
|
|
pid = get_num(request.args.get('pid')) |
|
|
|
|
|
|
|
|
|
post = Post.query.get(pid) |
|
|
|
|
if not post: abort(404) |
|
|
|
|
if post.deleted: abort(451) |
|
|
|
|
if not post: |
|
|
|
|
abort(404) |
|
|
|
|
if post.deleted: |
|
|
|
|
abort(451) |
|
|
|
|
|
|
|
|
|
data = map_comment(post, u.name) |
|
|
|
|
|
|
|
|
@ -279,6 +282,7 @@ def get_comment():
|
|
|
|
|
'data': data |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.route('/_api/v1/docomment', methods=['POST']) |
|
|
|
|
@limiter.limit("50 / hour; 1 / 3 second") |
|
|
|
|
def do_comment(): |
|
|
|
@ -287,13 +291,16 @@ def do_comment():
|
|
|
|
|
pid = get_num(request.form.get('pid')) |
|
|
|
|
|
|
|
|
|
post = Post.query.get(pid) |
|
|
|
|
if not post: abort(404) |
|
|
|
|
if post.deleted: abort(451) |
|
|
|
|
if not post: |
|
|
|
|
abort(404) |
|
|
|
|
if post.deleted: |
|
|
|
|
abort(451) |
|
|
|
|
|
|
|
|
|
content = request.form.get('text') |
|
|
|
|
content = content.strip() if content else None |
|
|
|
|
content = '[tmp]\n' + content if u.name[:4] == 'tmp_' else content |
|
|
|
|
if not content or len(content) > 4096: abort(422) |
|
|
|
|
if not content or len(content) > 4096: |
|
|
|
|
abort(422) |
|
|
|
|
|
|
|
|
|
c = Comment( |
|
|
|
|
name_hash=hash_name(u.name), |
|
|
|
@ -308,21 +315,27 @@ def do_comment():
|
|
|
|
|
'data': pid |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.route('/_api/v1/attention', methods=['POST']) |
|
|
|
|
@limiter.limit("200 / hour; 1 / second") |
|
|
|
|
def attention(): |
|
|
|
|
u = require_token() |
|
|
|
|
if u.name[:4] == 'tmp_': abort(403) |
|
|
|
|
if u.name[:4] == 'tmp_': |
|
|
|
|
abort(403) |
|
|
|
|
|
|
|
|
|
s = request.form.get('switch') |
|
|
|
|
if s not in ['0', '1']: abort(422) |
|
|
|
|
if s not in ['0', '1']: |
|
|
|
|
abort(422) |
|
|
|
|
|
|
|
|
|
pid = get_num(request.form.get('pid')) |
|
|
|
|
|
|
|
|
|
post = Post.query.get(pid) |
|
|
|
|
if not post: abort(404) |
|
|
|
|
if not post: |
|
|
|
|
abort(404) |
|
|
|
|
|
|
|
|
|
at = Attention.query.filter_by(name_hash=hash_name(u.name), pid=pid).first() |
|
|
|
|
at = Attention.query.filter_by( |
|
|
|
|
name_hash=hash_name( |
|
|
|
|
u.name), pid=pid).first() |
|
|
|
|
|
|
|
|
|
if not at: |
|
|
|
|
at = Attention(name_hash=hash_name(u.name), pid=pid, disabled=True) |
|
|
|
@ -330,7 +343,7 @@ def attention():
|
|
|
|
|
|
|
|
|
|
if(at.disabled != (s == '0')): |
|
|
|
|
at.disabled = (s == '0') |
|
|
|
|
post.likenum += 1 - 2 * int(s == '0'); |
|
|
|
|
post.likenum += 1 - 2 * int(s == '0') |
|
|
|
|
db.session.commit() |
|
|
|
|
|
|
|
|
|
return { |
|
|
|
@ -339,11 +352,14 @@ def attention():
|
|
|
|
|
'attention': (s == '1') |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.route('/_api/v1/getattention') |
|
|
|
|
def get_attention(): |
|
|
|
|
u = require_token() |
|
|
|
|
|
|
|
|
|
ats = Attention.query.filter_by(name_hash=hash_name(u.name), disabled=False) |
|
|
|
|
ats = Attention.query.filter_by( |
|
|
|
|
name_hash=hash_name( |
|
|
|
|
u.name), disabled=False) |
|
|
|
|
|
|
|
|
|
posts = [Post.query.get(at.pid) for at in ats.all()] |
|
|
|
|
data = [map_post(post, u.name, 10) |
|
|
|
@ -357,6 +373,7 @@ def get_attention():
|
|
|
|
|
'data': data |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.route('/_api/v1/delete', methods=['POST']) |
|
|
|
|
@limiter.limit("50 / hour; 1 / 3 second") |
|
|
|
|
def delete(): |
|
|
|
@ -366,18 +383,21 @@ def delete():
|
|
|
|
|
obj_id = get_num(request.form.get('id')) |
|
|
|
|
note = request.form.get('note') |
|
|
|
|
|
|
|
|
|
if note and len(note)>100: abort(422) |
|
|
|
|
if note and len(note) > 100: |
|
|
|
|
abort(422) |
|
|
|
|
|
|
|
|
|
obj = None |
|
|
|
|
if obj_type == 'pid': |
|
|
|
|
obj = Post.query.get(obj_id) |
|
|
|
|
elif obj_type == 'cid': |
|
|
|
|
obj = Comment.query.get(obj_id) |
|
|
|
|
if not obj: abort(404) |
|
|
|
|
if not obj: |
|
|
|
|
abort(404) |
|
|
|
|
|
|
|
|
|
if obj.name_hash == hash_name(u.name): |
|
|
|
|
if obj_type == 'pid': |
|
|
|
|
if len(obj.comments): abort(403) |
|
|
|
|
if len(obj.comments): |
|
|
|
|
abort(403) |
|
|
|
|
Attention.query.filter_by(pid=obj.id).delete() |
|
|
|
|
TagRecord.query.filter_by(pid=obj.id).delete() |
|
|
|
|
db.session.delete(obj) |
|
|
|
@ -402,9 +422,10 @@ def delete():
|
|
|
|
|
db.session.commit() |
|
|
|
|
return {'code': 0} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.route('/_api/v1/systemlog') |
|
|
|
|
def system_log(): |
|
|
|
|
u = require_token() |
|
|
|
|
require_token() |
|
|
|
|
|
|
|
|
|
ss = Syslog.query.order_by(db.desc('timestamp')).limit(100).all() |
|
|
|
|
|
|
|
|
@ -415,6 +436,7 @@ def system_log():
|
|
|
|
|
'data': list(map(map_syslog, ss)) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.route('/_api/v1/report', methods=['POST']) |
|
|
|
|
@limiter.limit("50 / hour; 1 / 3 second") |
|
|
|
|
def report(): |
|
|
|
@ -436,4 +458,3 @@ def report():
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__': |
|
|
|
|
app.run(debug=True) |
|
|
|
|
|
|
|
|
|