华清大学特普通奖学金初选报名系统
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

119 lines
3.3 KiB

3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
  1. from flask import Flask, request, render_template, send_from_directory, abort, redirect
  2. from flask_sqlalchemy import SQLAlchemy
  3. from flask_limiter import Limiter
  4. from flask_limiter.util import get_remote_address
  5. from mastodon import Mastodon
  6. import re
  7. from datetime import datetime
  8. from dateutil.tz import tzlocal
  9. import html2text
  10. from config import C
  11. app = Flask(__name__)
  12. app.config.from_object('config.C')
  13. th = Mastodon(
  14. access_token = C.token,
  15. api_base_url = 'https://' + C.domain
  16. )
  17. limiter = Limiter(
  18. app,
  19. key_func=get_remote_address,
  20. default_limits=["50 / minute"],
  21. )
  22. h2t = html2text.HTML2Text()
  23. h2t.ignore_links = True
  24. db = SQLAlchemy(app)
  25. class Candidate(db.Model):
  26. id = db.Column(db.Integer, primary_key=True)
  27. content = db.Column(db.String(4000))
  28. private = db.Column(db.String(1000))
  29. url = db.Column(db.String(50))
  30. time = db.Column(db.DateTime)
  31. toot = db.Column(db.BigInteger)
  32. db.create_all()
  33. @app.route('/ordinary/')
  34. def list():
  35. key = request.args.get('key')
  36. pag = Candidate.query.order_by(db.desc('id')).paginate(max_per_page=100)
  37. vs = [{
  38. 'name': name,
  39. 'ques': ques,
  40. 'hint': hint
  41. } for name, ques, hint, ans in C.verify
  42. ]
  43. return render_template('list.html', pagination=pag, vs=vs, showPrivate=(key==C.key))
  44. @app.route('/ordinary/new', methods=['POST'])
  45. @limiter.limit("5 / hour; 1 / 2 second")
  46. def new_one():
  47. content = request.form.get('text')
  48. private = request.form.get('privateText')
  49. url = request.form.get('url')
  50. for name, ques, hint, ans in C.verify:
  51. if request.form.get(name) != ans: abort(401)
  52. if not content or len(content)>4000: abort(422)
  53. if private and len(private)>1000: abort(422)
  54. if url and not re.match('https://(cloud\.tsinghua\.edu\.cn/f/[0-9a-z]+/(\?dl=1)?)|(closed\.social/safeShare/\d([a-zA-Z]+)?)', url): abort(422)
  55. if not Candidate.query.filter_by(content=content).first():
  56. toot = th.status_post(
  57. f"叮~ 有新的自荐报名(大家可以直接在此处评论):\n\n{content}",
  58. visibility='unlisted'
  59. )
  60. c = Candidate(
  61. content=content,
  62. private=private,
  63. url=url,
  64. toot=toot.id,
  65. time=datetime.now()
  66. )
  67. db.session.add(c)
  68. db.session.commit()
  69. return redirect(".")
  70. @limiter.limit("100 / hour; 2 / second")
  71. @app.route('/ordinary/<int:toot>')
  72. def get_replies(toot):
  73. c = Candidate.query.filter_by(toot=toot).first()
  74. if not c:
  75. abort(404)
  76. context = th.status_context(toot)
  77. replies = [
  78. {
  79. 'disp': (t.account.display_name or t.account.acct),
  80. 'url': t.account.url,
  81. 'content': h2t.handle(t.content).replace(C.bot_name,'').strip(),
  82. 'time': str(t.created_at)
  83. }
  84. for t in context.descendants
  85. ]
  86. d = list(filter(
  87. lambda r: r['content'] == '删除' and r['url'].split('/@')[1] in C.admins,
  88. replies
  89. ))
  90. if d:
  91. db.session.delete(c)
  92. db.session.commit()
  93. th.status_delete(toot)
  94. return '该内容已被删除', 404
  95. return {'replies': replies}
  96. if __name__ == '__main__':
  97. app.run(debug=True)