华清大学特普通奖学金初选报名系统
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.

188 lines
5.2 KiB

4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
  1. from flask import Flask, request, render_template, send_from_directory, abort, redirect, session
  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. import random
  8. from datetime import datetime
  9. from dateutil.tz import tzlocal
  10. import html2text
  11. from config import C
  12. app = Flask(__name__)
  13. app.config.from_object('config.C')
  14. app.secret_key = C.session_key
  15. th = Mastodon(
  16. access_token = C.token,
  17. api_base_url = 'https://' + C.domain
  18. )
  19. limiter = Limiter(
  20. app,
  21. key_func=get_remote_address,
  22. default_limits=["50 / minute"],
  23. )
  24. h2t = html2text.HTML2Text()
  25. h2t.ignore_links = True
  26. db = SQLAlchemy(app)
  27. class Candidate(db.Model):
  28. id = db.Column(db.Integer, primary_key=True)
  29. content = db.Column(db.String(4000))
  30. private = db.Column(db.String(1000))
  31. url = db.Column(db.String(50))
  32. time = db.Column(db.DateTime)
  33. toot = db.Column(db.BigInteger)
  34. likeNum = db.Column(db.Integer, default=0)
  35. # always increment 1 for the id of a new record
  36. __table_args__ = { 'sqlite_autoincrement': True }
  37. class Like(db.Model):
  38. id = db.Column(db.Integer, primary_key=True)
  39. cid = db.Column(db.Integer) # (member) id of class Candidate
  40. uid = db.Column(db.Integer) # id of user
  41. db.create_all()
  42. @app.route('/img/<path:path>')
  43. def send_img(path):
  44. return send_from_directory('static/img', path)
  45. @app.route('/ordinary/set_session')
  46. @limiter.limit("2 / hour; 1 / 5 minute")
  47. def set_session():
  48. if 'uid' not in session:
  49. session['uid'] = random.randint(0, 2000000000)
  50. session.permanent = True
  51. return redirect('.')
  52. @app.route('/ordinary/')
  53. def can_list():
  54. key = request.args.get('key')
  55. sort_by = request.args.get('sort_by', 'time')
  56. if 'uid' not in session:
  57. return redirect('set_session')
  58. uid = session['uid']
  59. q = Candidate.query
  60. q = q.order_by(db.desc('likeNum')) if sort_by=='likeNum' else q.order_by(db.desc('id'))
  61. pag = q.paginate(max_per_page=100)
  62. def check_like(c):
  63. c.liked = 'liked' if Like.query.filter_by(uid=uid, cid=c.id).count() else 'like'
  64. return c
  65. pag.items = map(check_like, pag.items)
  66. vs = [{
  67. 'name': name,
  68. 'ques': ques,
  69. 'hint': hint
  70. } for name, ques, hint, ans in C.verify
  71. ]
  72. return render_template('list.html', pagination=pag, vs=vs, showPrivate=(key==C.key), sort_by=sort_by, key=key)
  73. @app.route('/ordinary/new', methods=['POST'])
  74. @limiter.limit("5 / hour; 1 / 2 second")
  75. def new_one():
  76. content = request.form.get('text')
  77. private = request.form.get('privateText')
  78. url = request.form.get('url')
  79. for name, ques, hint, ans in C.verify:
  80. if request.form.get(name) != ans:
  81. return '''<html>
  82. <head>
  83. <meta charset='UTF-8'>
  84. <meta name='viewport' content='width=device-width initial-scale=1'>
  85. <title></title>
  86. </head>
  87. <body>
  88. <h1></h1>
  89. <a href="##" onclick="window.history.back()">退</a>
  90. </body>
  91. </html>
  92. ''', 401
  93. if not content or len(content)>4000: abort(422)
  94. if private and len(private)>1000: abort(422)
  95. 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)
  96. if not Candidate.query.filter_by(content=content).first():
  97. toot = th.status_post(
  98. f"有新的自荐报名(大家可以直接在此处评论):\n\n{content}",
  99. visibility=C.visibility
  100. )
  101. c = Candidate(
  102. content=content,
  103. private=private,
  104. url=url,
  105. toot=toot.id,
  106. time=datetime.now()
  107. )
  108. db.session.add(c)
  109. db.session.commit()
  110. return redirect(".")
  111. @limiter.limit("100 / hour; 2 / second")
  112. @app.route('/ordinary/<int:toot>/comments')
  113. def get_comments(toot):
  114. c = Candidate.query.filter_by(toot=toot).first()
  115. if not c:
  116. abort(404)
  117. context = th.status_context(toot)
  118. replies = [
  119. {
  120. 'disp': (t.account.display_name or t.account.acct),
  121. 'url': t.account.url,
  122. 'content': h2t.handle(t.content).replace(C.bot_name,'').strip(),
  123. 'time': str(t.created_at)
  124. }
  125. for t in context.descendants
  126. ]
  127. d = list(filter(
  128. lambda r: r['content'] == '删除' and r['url'].split('/@')[1] in C.admins,
  129. replies
  130. ))
  131. if d:
  132. db.session.delete(c)
  133. db.session.commit()
  134. th.status_delete(toot)
  135. return '该内容已被删除', 404
  136. return {'replies': replies}
  137. @limiter.limit("100 / hour")
  138. @app.route('/ordinary/<int:toot>/like', methods=['POST'])
  139. def like(toot):
  140. c = Candidate.query.filter_by(toot=toot).first()
  141. if not c:
  142. abort(404)
  143. uid = session['uid']
  144. if not uid: abort(401)
  145. if Like.query.filter_by(uid=uid, cid=c.id).first():
  146. return '点赞过了', 403
  147. l = Like(uid=uid, cid=c.id)
  148. c.likeNum += 1
  149. db.session.add(l)
  150. db.session.commit()
  151. return str(c.likeNum)
  152. if __name__ == '__main__':
  153. app.run(debug=True)