Browse Source

基础功能

fudan
欧醚 4 years ago
parent
commit
3767436f7c
Signed by: OmmyZhang GPG Key ID: 757D312E7C9D13F7
4 changed files with 243 additions and 0 deletions
  1. +7
    -0
      .gitignore
  2. +114
    -0
      app.py
  3. +8
    -0
      config.sample.py
  4. +114
    -0
      templates/inbox.html

+ 7
- 0
.gitignore View File

@ -1,4 +1,11 @@
# ---> Python # ---> Python
#sqlite
*.db
#config
config.py
# Byte-compiled / optimized / DLL files # Byte-compiled / optimized / DLL files
__pycache__/ __pycache__/
*.py[cod] *.py[cod]

+ 114
- 0
app.py View File

@ -0,0 +1,114 @@
from flask import Flask, request, render_template, send_from_directory, abort, redirect
from flask_sqlalchemy import SQLAlchemy
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
from mastodon import Mastodon
import re
from datetime import datetime
from dateutil.tz import tzlocal
import html2text
from config import C
app = Flask(__name__)
app.config.from_object('config.C')
th = Mastodon(
access_token = C.token,
api_base_url = 'https://' + C.domain
)
limiter = Limiter(
app,
key_func=get_remote_address,
default_limits=["50 / minute"],
)
h2t = html2text.HTML2Text()
h2t.ignore_links = True
db = SQLAlchemy(app)
class Candidate(db.Model):
id = db.Column(db.Integer, primary_key=True)
content = db.Column(db.String(400))
url = db.Column(db.String(50))
time = db.Column(db.DateTime)
toot = db.Column(db.BigInteger)
db.create_all()
@app.route('/js/<path:path>')
def send_js(path):
return send_from_directory('static/js', path)
@app.route('/img/<path:path>')
def send_img(path):
return send_from_directory('static/img', path)
@app.route('/ordinary/')
def inbox():
cans = [{
'content': c.content,
'url' : c.url,
'toot': c.toot,
'time': c.time.replace(tzinfo=tzlocal())
} for c in Candidate.query.all()
]
return render_template('inbox.html', cans=cans)
@app.route('/ordinary/new', methods=['POST'])
@limiter.limit("5 / hour; 1 / 2 second")
def new_one():
content = request.form.get('text')
print(content)
if not content or len(content)>400:
abort(422)
if not Candidate.query.filter_by(content=content).first():
toot = th.status_post(
f"叮~ 有新的自荐报名(大家可以直接在此处评论):\n\n{content}",
visibility='unlisted'
)
c = Candidate(content=content, toot=toot.id, time = datetime.now())
db.session.add(c)
db.session.commit()
return redirect(".")
@limiter.limit("100 / hour; 2 / second")
@app.route('/ordinary/<int:toot>')
def get_replies(toot):
c = Candidate.query.filter_by(toot=toot).first()
if not c:
abort(404)
context = th.status_context(toot)
replies = [
{
'disp': (t.account.display_name or t.account.acct),
'url': t.account.url,
'content': h2t.handle(t.content).replace(C.bot_name,'').strip(),
'time': str(t.created_at)
}
for t in context.descendants
]
d = list(filter(
lambda r: r['content'] == '删除' and r['url'].split('/@')[1] in C.admins,
replies
))
print(d)
if d:
db.session.delete(c)
db.session.commit()
th.status_delete(toot)
return '该内容已被删除', 404
return {'replies': replies}
if __name__ == '__main__':
app.run(debug=True)

+ 8
- 0
config.sample.py View File

@ -0,0 +1,8 @@
class C(object):
SQLALCHEMY_DATABASE_URI = 'sqlite:///ord.db'
SQLALCHEMY_TRACK_MODIFICATIONS = False
JSON_AS_ASCII = True
domain = 'thu.closed.social'
bot_name = '@ordinary_bot'
token = 'token'
admins = []

+ 114
- 0
templates/inbox.html View File

@ -0,0 +1,114 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<meta property="og:title" content="华清特将报名" />
<meta property="og:description" content="华清大学特普通奖学金" />
<link href="https://cdn.bootcss.com/twitter-bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/twitter-bootstrap/4.3.1/js/bootstrap.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery-timeago/1.6.7/jquery.timeago.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery-timeago/1.6.7/locales/jquery.timeago.zh-CN.js"></script>
<title>华清特将报名</title>
<style>
.qbox {
background:#e8e8e8;
padding:5px;
color:black;
box-shadow: 2px 2px 10px 2px rgb(219 219 220);
border-radius: .5rem;
margin: 25px 0;
}
pre {
margin: 15px 0 0 15px;
white-space: pre-wrap;
}
.timeago {
font-size: 0.5em;
text-align: right;
}
.display_name {
margin: 0;
}
.card-body {
padding: 0.75em;
}
</style>
</head>
<body style="background-color: #001a37;color:#e8e8e8">
<div style="max-width:700px;" class="container">
<div style="background-color:rgba(230,230,250,0.5);margin-bottom:80px">
<div align='center' style='background-color:rgba(180,180,250,0.5);padding:10px;color:#ffffff;'>
<h1 align='center'>华清大学<br/>特普通奖学金</h1>
</div>
</div>
<form action="new" method="post">
<div class="form-group qbox">
<label>自荐提名</label>
<textarea class="form-control" name="text" rows="5" maxlength="400" placeholder="简单介绍一下自己,禁止内卷,限400字" required="required"></textarea>
<button type="submit" class="btn btn-primary">报名!</button>
</div>
</form>
<hr/>
<h3>已有的报名</h3>
{% for c in cans|reverse %}
<div class="qbox">
<pre>{{c.content}}</pre>
<div style="text-align:right">
<time class="timeago" datetime="{{c.time}}"></time>
<a class="btn btn-link request-answer" data-toggle="collapse" href="#collapse-{{c.toot}}" role="button" aria-expanded="false" aria-controls="collapse-{{c.toot}}">
查看评论
<span><svg fill="#007bff" viewBox="0 0 24 24" width="24" height="24" ><path d="M12 13L8.285 9.218a.758.758 0 0 0-1.064 0 .738.738 0 0 0 0 1.052l4.249 4.512a.758.758 0 0 0 1.064 0l4.246-4.512a.738.738 0 0 0 0-1.052.757.757 0 0 0-1.063 0L12.002 13z" style="animation: downn 1.5s infinite;"></path></svg></span>
</a>
</div>
<div class="collapse" id="collapse-{{c.toot}}">
<div class="card card-body">
加载中...
</div>
</div>
</div>
{% endfor %}
<script>
$('.timeago').timeago();
$('.collapse').on('show.bs.collapse', (e) => {
let self = e.target;
let toot = self.id.split('-')[1];
$.ajax({
type:'GET',
url:toot,
success:(result,status,xhr) => {
console.log(result+' : '+status);
if(result.replies.length) {
$(self).empty();
result.replies.forEach((rp) => {
$(self).append(`<div class="card card-body"><p class="display_name"><a href="${rp.url}">${rp.disp}</a>:</p><pre>${rp.content}</pre><time class="timeago" datetime="${rp.time}"></time></div>`)
});
console.log($(self).find('.timeago'));
$(self).find('.timeago').timeago();
}else {
$(self).empty();
$(self).append(`<div class="card card-body">(暂无回复)</div>`)
}
},
error:(xhr,status,error) => {
console.log(error, status, xhr.status, xhr.responseText);
$(self).empty();
$(self).append(`<div class="card card-body"><p style="color:red">${xhr.status} ${xhr.responseText}</p></div>`);
if(xhr.status == 404)
setTimeout(()=>{$(self).parent().remove()}, 3000);
}
});
});
</script>
</div>
</body>
</html>

Loading…
Cancel
Save