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.

208 lines
5.2 KiB

2 years ago
2 years ago
2 years ago
  1. from flask import Flask, request, session, render_template, send_from_directory, abort, redirect
  2. from flask_sqlalchemy import SQLAlchemy
  3. from flask_login import LoginManager, login_user, logout_user, login_required, current_user
  4. from flask_limiter import Limiter
  5. from flask_limiter.util import get_remote_address
  6. from mastodon import Mastodon
  7. import os
  8. import time
  9. import json
  10. import requests
  11. from Tools.RtcTokenBuilder import RtcTokenBuilder, Role_Attendee
  12. from Tools.RtmTokenBuilder import RtmTokenBuilder, Role_Rtm_User
  13. from config import C, rds
  14. app = Flask(__name__)
  15. app.config.from_object('config.C')
  16. app.secret_key = C.session_key
  17. login_manager = LoginManager()
  18. login_manager.init_app(app)
  19. db = SQLAlchemy(app)
  20. limiter = Limiter(
  21. app,
  22. key_func=get_remote_address,
  23. default_limits=["50 / minute"],
  24. )
  25. RDS_KEY = 'call_channel_list'
  26. class User(db.Model):
  27. id = db.Column(db.Integer, primary_key=True)
  28. acct = db.Column(db.String(64))
  29. disp = db.Column(db.String(64))
  30. avat = db.Column(db.String(256))
  31. url = db.Column(db.String(128))
  32. def __init__(self, id):
  33. self.id = id
  34. def __repr__(self):
  35. return f"{self.id}@{self.acct}[{self.disp}]"
  36. @property
  37. def is_active(self):
  38. return True
  39. @property
  40. def is_authenticated(self):
  41. return True
  42. @property
  43. def is_anonymous(self):
  44. return False
  45. def get_id(self):
  46. return self.id
  47. db.create_all()
  48. @login_manager.user_loader
  49. def load_user(id):
  50. return User.query.get(id)
  51. @login_manager.unauthorized_handler
  52. def unauthorized():
  53. return redirect(C.login_url)
  54. @app.route('/call/static/<path:path>')
  55. def send_static_file(path):
  56. return send_from_directory('static/', path)
  57. def calc_token(cid, uid):
  58. uid = uid % (2**30 - 1)
  59. expireTimeInSeconds = 3600 * 5
  60. currentTimestamp = int(time.time())
  61. privilegeExpiredTs = currentTimestamp + expireTimeInSeconds
  62. return RtcTokenBuilder.buildTokenWithUid(C.app_id, C.app_certificate, cid, uid, Role_Attendee, privilegeExpiredTs)
  63. def calc_rtm_token(username):
  64. expireTimeInSeconds = 3600 * 5
  65. currentTimestamp = int(time.time())
  66. privilegeExpiredTs = currentTimestamp + expireTimeInSeconds
  67. return RtmTokenBuilder.buildToken(C.app_id, C.app_certificate, username, Role_Rtm_User, privilegeExpiredTs)
  68. @app.route('/call/')
  69. @login_required
  70. def homepage():
  71. me = current_user
  72. app_id = C.app_id
  73. rtm_token = calc_rtm_token(me.acct)
  74. _cid = request.args.get('cid')
  75. if _cid and not rds.hexists(RDS_KEY, _cid):
  76. abort(404)
  77. r = requests.get(
  78. C.ago_api + '/dev/v1/channel/' + app_id,
  79. headers={'Authorization': C.ago_auth, 'Content-Type': 'application/json'}
  80. ) # TODO 加缓存
  81. j = r.json()
  82. if not j.get('success'):
  83. return '连接声网出错', 500
  84. remote_list = j.get('data').get('channels')
  85. cnt_dict = {}
  86. for ch in remote_list:
  87. cnt_dict[ch['channel_name']] = ch['user_count']
  88. chs = [(_cid, rds.hget(RDS_KEY, _cid))] if _cid else rds.hgetall(RDS_KEY).items()
  89. ch_list = []
  90. for cid, s_info in chs:
  91. info = json.loads(s_info)
  92. if cid not in cnt_dict:
  93. if not info['empty_time']:
  94. info['empty_time'] = int(time.time())
  95. rds.hset(RDS_KEY, cid, json.dumps(info))
  96. elif int(time.time()) - info['empty_time'] > 1800:
  97. rds.hdel(RDS_KEY, cid)
  98. continue
  99. ch_list.append(
  100. (cid, info['title'], info['is_private'],
  101. User.query.get(info['creator_id']),
  102. calc_token(cid, me.id), cnt_dict.get(cid, 0))
  103. )
  104. return render_template('homepage.html', **locals())
  105. @app.route('/call/new', methods=['POST'])
  106. @login_required
  107. def new_channel():
  108. title = request.form.get('title')
  109. cid = request.form.get('cid') or str(time.time())
  110. is_private = request.form.get('private') == 'on'
  111. if not title or len(title) > 50:
  112. abort(400)
  113. ch_d = {
  114. 'title': title,
  115. 'is_private': is_private,
  116. 'creator_id': current_user.id,
  117. 'empty_time': None
  118. }
  119. rds.hset(RDS_KEY, cid, json.dumps(ch_d))
  120. return redirect('.?cid=' + cid)
  121. @app.route('/call/api/user/<int:uid>')
  122. @login_required
  123. def user_info(uid):
  124. user = User.query.get(uid)
  125. if not user:
  126. abort(404)
  127. return {
  128. key: getattr(user, key)
  129. for key in ('acct', 'disp', 'avat', 'url')
  130. }
  131. @app.route('/call/auth')
  132. @limiter.limit("10 / hour")
  133. def auth():
  134. code = request.args.get('code')
  135. client = Mastodon(client_id=C.client_id, client_secret=C.client_secret, api_base_url=C.mas_base_url)
  136. token = client.log_in(code=code, redirect_uri=C.redirect_uri, scopes=['read:accounts'])
  137. info = client.account_verify_credentials()
  138. u = User.query.get(info.id)
  139. if not u:
  140. u = User(info.id)
  141. db.session.add(u)
  142. u.acct = info.acct
  143. u.disp = info.display_name
  144. u.avat = info.avatar
  145. u.url = info.url
  146. db.session.commit()
  147. login_user(u, remember=True)
  148. return redirect('.')
  149. @app.route('/call/logout')
  150. @login_required
  151. def logout():
  152. logout_user()
  153. return redirect('.')
  154. if __name__ == '__main__':
  155. app.run(debug=True)