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.

207 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. expireTimeInSeconds = 3600 * 5
  59. currentTimestamp = int(time.time())
  60. privilegeExpiredTs = currentTimestamp + expireTimeInSeconds
  61. return RtcTokenBuilder.buildTokenWithUid(C.app_id, C.app_certificate, cid, uid, Role_Attendee, privilegeExpiredTs)
  62. def calc_rtm_token(username):
  63. expireTimeInSeconds = 3600 * 5
  64. currentTimestamp = int(time.time())
  65. privilegeExpiredTs = currentTimestamp + expireTimeInSeconds
  66. return RtmTokenBuilder.buildToken(C.app_id, C.app_certificate, username, Role_Rtm_User, privilegeExpiredTs)
  67. @app.route('/call/')
  68. @login_required
  69. def homepage():
  70. me = current_user
  71. app_id = C.app_id
  72. rtm_token = calc_rtm_token(me.acct)
  73. _cid = request.args.get('cid')
  74. if _cid and not rds.hexists(RDS_KEY, _cid):
  75. abort(404)
  76. r = requests.get(
  77. C.ago_api + '/dev/v1/channel/' + app_id,
  78. headers={'Authorization': C.ago_auth, 'Content-Type': 'application/json'}
  79. ) # TODO 加缓存
  80. j = r.json()
  81. if not j.get('success'):
  82. return '连接声网出错', 500
  83. remote_list = j.get('data').get('channels')
  84. cnt_dict = {}
  85. for ch in remote_list:
  86. cnt_dict[ch['channel_name']] = ch['user_count']
  87. chs = [(_cid, rds.hget(RDS_KEY, _cid))] if _cid else rds.hgetall(RDS_KEY).items()
  88. ch_list = []
  89. for cid, s_info in chs:
  90. info = json.loads(s_info)
  91. if cid not in cnt_dict:
  92. if not info['empty_time']:
  93. info['empty_time'] = int(time.time())
  94. rds.hset(RDS_KEY, cid, json.dumps(info))
  95. elif int(time.time()) - info['empty_time'] > 1800:
  96. rds.hdel(RDS_KEY, cid)
  97. continue
  98. ch_list.append(
  99. (cid, info['title'], info['is_private'],
  100. User.query.get(info['creator_id']),
  101. calc_token(cid, me.id), cnt_dict.get(cid, 0))
  102. )
  103. return render_template('homepage.html', **locals())
  104. @app.route('/call/new', methods=['POST'])
  105. @login_required
  106. def new_channel():
  107. title = request.form.get('title')
  108. cid = request.form.get('cid') or str(time.time())
  109. is_private = request.form.get('private') == 'on'
  110. if not title or len(title) > 50:
  111. abort(400)
  112. ch_d = {
  113. 'title': title,
  114. 'is_private': is_private,
  115. 'creator_id': current_user.id,
  116. 'empty_time': None
  117. }
  118. rds.hset(RDS_KEY, cid, json.dumps(ch_d))
  119. return redirect('.?cid=' + cid)
  120. @app.route('/call/api/user/<int:uid>')
  121. @login_required
  122. def user_info(uid):
  123. user = User.query.get(uid)
  124. if not user:
  125. abort(404)
  126. return {
  127. key: getattr(user, key)
  128. for key in ('acct', 'disp', 'avat', 'url')
  129. }
  130. @app.route('/call/auth')
  131. @limiter.limit("10 / hour")
  132. def auth():
  133. code = request.args.get('code')
  134. client = Mastodon(client_id=C.client_id, client_secret=C.client_secret, api_base_url=C.mas_base_url)
  135. token = client.log_in(code=code, redirect_uri=C.redirect_uri, scopes=['read:accounts'])
  136. info = client.account_verify_credentials()
  137. u = User.query.get(info.id)
  138. if not u:
  139. u = User(info.id)
  140. db.session.add(u)
  141. u.acct = info.acct
  142. u.disp = info.display_name
  143. u.avat = info.avatar
  144. u.url = info.url
  145. db.session.commit()
  146. login_user(u, remember=True)
  147. return redirect('.')
  148. @app.route('/call/logout')
  149. @login_required
  150. def logout():
  151. logout_user()
  152. return redirect('.')
  153. if __name__ == '__main__':
  154. app.run(debug=True)