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.

197 lines
4.8 KiB

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