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

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