Files
YYeTsBot/yyetsweb/handler.py

1082 lines
33 KiB
Python
Raw Normal View History

2021-06-17 10:39:47 +08:00
#!/usr/local/bin/python3
# coding: utf-8
# YYeTsBot - handler.py
# 6/16/21 20:30
#
__author__ = "Benny <benny.think@gmail.com>"
2021-08-15 12:28:19 +08:00
import contextlib
2021-07-11 15:53:39 +08:00
import importlib
2021-06-17 10:39:47 +08:00
import json
import logging
import os
2022-02-22 18:54:02 +08:00
import pathlib
2021-06-17 10:39:47 +08:00
import re
2021-07-24 13:45:57 +08:00
import sys
2021-06-17 10:39:47 +08:00
import time
2021-08-15 12:28:19 +08:00
import uuid
2021-06-17 10:39:47 +08:00
from concurrent.futures import ThreadPoolExecutor
from datetime import date, timedelta
from hashlib import sha1
from http import HTTPStatus
2023-02-08 20:55:26 +01:00
from urllib.parse import urlencode
2021-06-17 10:39:47 +08:00
2021-07-11 15:53:39 +08:00
import filetype
2023-02-08 20:55:26 +01:00
import requests
2022-04-08 20:34:54 +08:00
import zhconv
2021-07-11 15:53:39 +08:00
from tornado import escape, gen, web
2023-02-08 22:20:15 +01:00
from tornado.auth import GoogleOAuth2Mixin, OAuth2Mixin
2021-06-17 10:39:47 +08:00
from tornado.concurrent import run_on_executor
2022-04-08 20:34:54 +08:00
2022-04-10 13:59:00 +08:00
from database import CaptchaResource, Redis
2022-09-09 18:07:55 +08:00
from utils import Cloudflare
2021-06-17 10:39:47 +08:00
escape.json_encode = lambda value: json.dumps(value, ensure_ascii=False)
logging.basicConfig(level=logging.INFO)
2022-09-09 18:07:55 +08:00
cf = Cloudflare()
2021-07-24 13:45:57 +08:00
if getattr(sys, '_MEIPASS', None):
adapter = "SQLite"
else:
adapter = "Mongo"
2021-06-17 10:39:47 +08:00
logging.info("%s Running with %s. %s", "#" * 10, adapter, "#" * 10)
2022-02-06 18:51:41 +08:00
index = pathlib.Path(__file__).parent.joinpath("templates", "index.html").as_posix()
2021-08-15 12:28:19 +08:00
2021-06-17 10:39:47 +08:00
2022-04-10 13:59:00 +08:00
class SecurityHandler(web.RequestHandler):
key = "user_blacklist"
def __init__(self, application, request, **kwargs):
super().__init__(application, request, **kwargs)
self.r = Redis().r
def prepare(self):
if self.check_request():
self.set_status(HTTPStatus.FORBIDDEN)
self.finish()
def data_received(self, chunk):
pass
def check_request(self):
ban = self.__ip_check()
user = self.__user_check()
result = ban or user
if result:
self.ban()
return result
def get_real_ip(self):
x_real = self.request.headers.get("X-Real-IP")
remote_ip = self.request.remote_ip
logging.debug("X-Real-IP:%s, Remote-IP:%s", x_real, remote_ip)
return x_real or remote_ip
def ban(self):
ip = self.get_real_ip()
self.r.incr(ip)
count = int(self.r.get(ip))
# ban rule: (count-10)*600
2022-04-10 15:20:11 +08:00
if count <= 10:
ex = 120
else:
ex = (count - 10) * 600
if count >= 30:
2022-09-09 18:07:55 +08:00
cf.ban_new_ip(ip)
2022-04-10 13:59:00 +08:00
self.r.set(ip, count, ex)
user = self.get_current_user()
if user:
self.r.hincrby(self.key, user)
def get_current_user(self) -> str:
username = self.get_secure_cookie("username") or b""
return username.decode("u8")
def __user_check(self):
count = self.r.hget(self.key, self.get_current_user()) or 0
count = int(count)
if count >= 20:
return True
def __ip_check(self):
d = self.r.get(self.get_real_ip()) or 0
if int(d) >= 10:
return True
class BaseHandler(SecurityHandler):
2021-06-17 10:39:47 +08:00
executor = ThreadPoolExecutor(200)
class_name = f"Fake{adapter}Resource"
adapter_module = importlib.import_module(f"{adapter}")
def __init__(self, application, request, **kwargs):
super().__init__(application, request, **kwargs)
2021-08-15 12:28:19 +08:00
self.json = {}
with contextlib.suppress(ValueError):
self.json: dict = json.loads(self.request.body)
2021-06-17 10:39:47 +08:00
self.instance = getattr(self.adapter_module, self.class_name)()
def write_error(self, status_code, **kwargs):
if status_code in [HTTPStatus.FORBIDDEN,
HTTPStatus.UNAUTHORIZED,
2022-04-10 13:59:00 +08:00
HTTPStatus.NOT_FOUND,
HTTPStatus.INTERNAL_SERVER_ERROR,
]:
2021-06-17 10:39:47 +08:00
self.write(str(kwargs.get('exc_info')))
class TopHandler(BaseHandler):
class_name = f"Top{adapter}Resource"
# from Mongo import TopMongoResource
# instance = TopMongoResource()
def get_user_like(self) -> list:
username = self.get_current_user()
return self.instance.get_user_like(username)
def get_most(self) -> list:
return self.instance.get_most()
@run_on_executor()
def get_top_resource(self):
return self.instance.get_top_resource()
@gen.coroutine
def get(self):
resp = yield self.get_top_resource()
self.write(resp)
class IndexHandler(BaseHandler):
@run_on_executor()
def send_index(self):
with open(index, encoding="u8") as f:
2021-06-17 10:39:47 +08:00
html = f.read()
return html
@gen.coroutine
def get(self):
resp = yield self.send_index()
self.write(resp)
class UserHandler(BaseHandler):
class_name = f"User{adapter}Resource"
# from Mongo import UserMongoResource
# instance = UserMongoResource()
def set_login(self, username):
self.set_secure_cookie("username", username, 365)
@run_on_executor()
def login_user(self):
2021-08-15 12:28:19 +08:00
data = self.json
2021-06-17 10:39:47 +08:00
username = data["username"]
password = data["password"]
2021-08-15 12:28:19 +08:00
captcha = data.get("captcha")
captcha_id = data.get("captcha_id", "")
2022-04-10 13:59:00 +08:00
ip = self.get_real_ip()
2021-06-17 10:39:47 +08:00
browser = self.request.headers['user-agent']
2021-08-15 12:28:19 +08:00
response = self.instance.login_user(username, password, captcha, captcha_id, ip, browser)
2021-06-17 10:39:47 +08:00
if response["status_code"] in (HTTPStatus.CREATED, HTTPStatus.OK):
self.set_login(username)
else:
2021-08-15 12:28:19 +08:00
self.set_status(response["status_code"])
2021-06-17 10:39:47 +08:00
2021-08-15 12:28:19 +08:00
return response
2021-06-17 10:39:47 +08:00
@run_on_executor()
2021-08-15 12:28:19 +08:00
def update_info(self):
result = self.instance.update_user_info(self.current_user, self.json)
self.set_status(result.get("status_code", HTTPStatus.IM_A_TEAPOT))
return result
2021-06-17 10:39:47 +08:00
@run_on_executor()
def get_user_info(self) -> dict:
username = self.get_current_user()
if username:
data = self.instance.get_user_info(username)
else:
2022-05-18 20:10:54 +08:00
# self.set_status(HTTPStatus.UNAUTHORIZED)
self.clear_cookie("username")
2021-08-15 12:28:19 +08:00
data = {"message": "Please try to login"}
2021-06-17 10:39:47 +08:00
return data
@gen.coroutine
def post(self):
resp = yield self.login_user()
self.write(resp)
@gen.coroutine
def get(self):
resp = yield self.get_user_info()
self.write(resp)
# everytime we receive a GET request to this api, we'll update last_date and last_ip
username = self.get_current_user()
if username:
2022-04-10 13:59:00 +08:00
now_ip = self.get_real_ip()
2021-06-17 10:39:47 +08:00
self.instance.update_user_last(username, now_ip)
2021-08-15 12:28:19 +08:00
@gen.coroutine
@web.authenticated
def patch(self):
resp = yield self.update_info()
self.write(resp)
2021-06-17 10:39:47 +08:00
class ResourceHandler(BaseHandler):
class_name = f"Resource{adapter}Resource"
# from Mongo import ResourceMongoResource
# instance = ResourceMongoResource()
@run_on_executor()
def get_resource_data(self):
2022-04-10 13:59:00 +08:00
resource_id = int(self.get_query_argument("id"))
username = self.get_current_user()
data = self.instance.get_resource_data(resource_id, username)
2021-06-17 10:39:47 +08:00
if not data:
2022-04-10 13:59:00 +08:00
self.ban()
2021-06-17 10:39:47 +08:00
self.set_status(HTTPStatus.NOT_FOUND)
data = {}
return data
@run_on_executor()
def search_resource(self):
kw = self.get_query_argument("keyword").lower()
2022-04-08 20:34:54 +08:00
# convert any text to zh-hans
kw = zhconv.convert(kw, "zh-hans")
2021-06-17 10:39:47 +08:00
return self.instance.search_resource(kw)
@gen.coroutine
def get(self):
if self.get_query_argument("id", None):
resp = yield self.get_resource_data()
elif self.get_query_argument("keyword", None):
resp = yield self.search_resource()
else:
resp = "error"
self.write(resp)
2021-08-15 12:28:19 +08:00
# patch and post are available to every login user
# these are rare operations, so no gen.coroutine and run_on_executor
@web.authenticated
def patch(self):
if self.instance.is_admin(self.get_current_user()):
# may consider add admin restrictions
pass
for item in self.json["items"].values():
for i in item:
i["creator"] = self.get_current_user()
i["itemid"] = uuid.uuid4().hex
self.instance.patch_resource(self.json)
self.set_status(HTTPStatus.CREATED)
self.write({})
@web.authenticated
def post(self):
self.json["data"]["list"] = []
self.json["data"]["info"]["creator"] = self.get_current_user()
self.set_status(HTTPStatus.CREATED)
resp = self.instance.add_resource(self.json)
self.write(resp)
@web.authenticated
def delete(self):
if not self.instance.is_admin(self.get_current_user()):
self.set_status(HTTPStatus.FORBIDDEN)
self.write({"status": False, "message": "admin only"})
return
self.instance.delete_resource(self.json)
self.set_status(HTTPStatus.ACCEPTED)
self.write({})
class ResourceLatestHandler(BaseHandler):
class_name = f"ResourceLatest{adapter}Resource"
# from Mongo import ResourceLatestMongoResource
# instance = ResourceLatestMongoResource()
@run_on_executor()
def get_latest(self):
size = int(self.get_query_argument("size", "100"))
result = self.instance.get_latest_resource()
result["data"] = result["data"][:size]
return result
@gen.coroutine
def get(self):
resp = yield self.get_latest()
self.write(resp)
#
# class ResourceLatestHandler(BaseHandler):
# from concurrent.futures import ProcessPoolExecutor
#
# class_name = f"ResourceLatest{adapter}Resource"
# executor = ProcessPoolExecutor(200)
#
# # from Mongo import ResourceLatestMongoResource
# # instance = ResourceLatestMongoResource()
#
# @gen.coroutine
# def get(self):
# # This returns a concurrent.futures.Future
# fut = self.executor.submit(self.instance.get_latest_resource)
# ret = yield fut
# self.write(ret)
2021-06-17 10:39:47 +08:00
2021-08-15 12:28:19 +08:00
class LikeHandler(BaseHandler):
class_name = f"Like{adapter}Resource"
2021-06-17 10:39:47 +08:00
2021-08-15 12:28:19 +08:00
# from Mongo import LikeMongoResource
2021-06-17 10:39:47 +08:00
# instance = UserLikeMongoResource()
@run_on_executor()
def like_data(self):
username = self.get_current_user()
return {"LIKE": self.instance.get_user_like(username)}
@gen.coroutine
2021-06-22 20:01:10 +08:00
@web.authenticated
2021-06-17 10:39:47 +08:00
def get(self):
resp = yield self.like_data()
self.write(resp)
2021-08-15 12:28:19 +08:00
@run_on_executor()
def add_remove_fav(self):
data = self.json
resource_id = int(data["resource_id"])
username = self.get_current_user()
if username:
response = self.instance.add_remove_fav(resource_id, username)
self.set_status(response["status_code"])
else:
response = {"message": "请先登录"}
self.set_status(HTTPStatus.UNAUTHORIZED)
return response["message"]
@gen.coroutine
@web.authenticated
def patch(self):
resp = yield self.add_remove_fav()
self.write(resp)
2021-06-17 10:39:47 +08:00
class NameHandler(BaseHandler):
class_name = f"Name{adapter}Resource"
# from Mongo import NameMongoResource
# instance = NameMongoResource()
@run_on_executor()
def get_names(self):
is_readable = self.get_query_argument("human", None)
return self.instance.get_names(is_readable)
@gen.coroutine
def get(self):
resp = yield self.get_names()
self.write(resp)
class CommentHandler(BaseHandler):
class_name = f"Comment{adapter}Resource"
# from Mongo import CommentMongoResource
# instance = CommentMongoResource()
@staticmethod
def hide_phone(data: list):
for item in data:
if item["username"].isdigit() and len(item["username"]) == 11:
item["username"] = re.sub(r"(\d{3})\d{4}(\d{4})", r"\g<1>****\g<2>", item["username"])
return data
@run_on_executor()
def get_comment(self):
resource_id = int(self.get_argument("resource_id", "0"))
size = int(self.get_argument("size", "5"))
page = int(self.get_argument("page", "1"))
2023-01-30 20:26:02 +01:00
inner_size = int(self.get_argument("inner_size", "3"))
inner_page = int(self.get_argument("inner_page", "1"))
2021-09-23 18:24:24 +08:00
comment_id = self.get_argument("comment_id", None)
2021-06-17 10:39:47 +08:00
if not resource_id:
self.set_status(HTTPStatus.BAD_REQUEST)
return {"status": False, "message": "请提供resource id"}
2021-09-23 18:24:24 +08:00
comment_data = self.instance.get_comment(
resource_id, page, size,
inner_size=inner_size, inner_page=inner_page,
comment_id=comment_id
)
2021-06-17 10:39:47 +08:00
self.hide_phone((comment_data["data"]))
return comment_data
@run_on_executor()
def add_comment(self):
2021-08-15 12:28:19 +08:00
payload = self.json
2021-06-17 10:39:47 +08:00
captcha = payload["captcha"]
captcha_id = payload["id"]
content = payload["content"]
resource_id = payload["resource_id"]
comment_id = payload.get("comment_id")
2022-04-10 13:59:00 +08:00
real_ip = self.get_real_ip()
2021-06-17 10:39:47 +08:00
username = self.get_current_user()
browser = self.request.headers['user-agent']
result = self.instance.add_comment(captcha, captcha_id, content, resource_id, real_ip,
username, browser, comment_id)
2021-06-17 10:39:47 +08:00
self.set_status(result["status_code"])
return result
@run_on_executor()
def delete_comment(self):
# need resource_id & id
# payload = {"id": "obj_id"}
2021-08-15 12:28:19 +08:00
payload = self.json
2021-06-17 10:39:47 +08:00
username = self.get_current_user()
2021-06-22 20:01:10 +08:00
comment_id = payload["comment_id"]
2021-06-17 10:39:47 +08:00
if self.instance.is_admin(username):
2021-06-22 20:01:10 +08:00
result = self.instance.delete_comment(comment_id)
2021-06-17 10:39:47 +08:00
self.set_status(result["status_code"])
return result
else:
self.set_status(HTTPStatus.UNAUTHORIZED)
return {"count": 0, "message": "You're unauthorized to delete comment."}
@gen.coroutine
def get(self):
resp = yield self.get_comment()
self.write(resp)
@gen.coroutine
2021-06-22 20:01:10 +08:00
@web.authenticated
2021-06-17 10:39:47 +08:00
def post(self):
resp = yield self.add_comment()
self.write(resp)
@gen.coroutine
2021-06-22 20:01:10 +08:00
@web.authenticated
2021-06-17 10:39:47 +08:00
def delete(self):
resp = yield self.delete_comment()
self.write(resp)
2021-07-26 20:24:32 +08:00
2021-08-15 12:28:19 +08:00
class CommentReactionHandler(BaseHandler):
class_name = f"CommentReaction{adapter}Resource"
# from Mongo import CommentReactionMongoResource
# instance = CommentReactionMongoResource()
@run_on_executor()
def comment_reaction(self):
self.json.update(method=self.request.method)
username = self.get_current_user()
result = self.instance.react_comment(username, self.json)
self.set_status(result.get("status_code"))
return result
@gen.coroutine
@web.authenticated
def post(self):
resp = yield self.comment_reaction()
self.write(resp)
2021-07-26 20:24:32 +08:00
@gen.coroutine
@web.authenticated
2021-08-15 12:28:19 +08:00
def delete(self):
2021-07-26 20:24:32 +08:00
resp = yield self.comment_reaction()
self.write(resp)
2021-06-17 10:39:47 +08:00
class CommentChildHandler(CommentHandler):
class_name = f"CommentChild{adapter}Resource"
2021-07-05 18:33:29 +08:00
# from Mongo import CommentChildResource
# instance = CommentChildResource()
@run_on_executor()
def get_comment(self):
parent_id = self.get_argument("parent_id", "0")
2023-01-30 20:26:02 +01:00
size = int(self.get_argument("size", "3"))
page = int(self.get_argument("page", "1"))
if not parent_id:
self.set_status(HTTPStatus.BAD_REQUEST)
return {"status": False, "message": "请提供 parent_id"}
comment_data = self.instance.get_comment(parent_id, page, size)
self.hide_phone((comment_data["data"]))
2021-07-11 11:37:45 +08:00
return comment_data
@gen.coroutine
def get(self):
resp = yield self.get_comment()
self.write(resp)
class CommentNewestHandler(CommentHandler):
class_name = f"CommentNewest{adapter}Resource"
# from Mongo import CommentNewestResource
# instance = CommentNewestResource()
@run_on_executor()
def get_comment(self):
size = int(self.get_argument("size", "5"))
page = int(self.get_argument("page", "1"))
comment_data = self.instance.get_comment(page, size)
self.hide_phone((comment_data["data"]))
return comment_data
@gen.coroutine
def get(self):
resp = yield self.get_comment()
self.write(resp)
2021-09-23 18:24:24 +08:00
class CommentSearchHandler(CommentHandler):
class_name = f"CommentSearch{adapter}Resource"
# from Mongo import CommentNewestResource
# instance = CommentNewestResource()
@run_on_executor()
def search_comment(self):
size = int(self.get_argument("size", "5"))
page = int(self.get_argument("page", "1"))
keyword = self.get_argument("keyword", "")
comment_data = self.instance.get_comment(page, size, keyword)
self.hide_phone((comment_data["data"]))
return comment_data
@gen.coroutine
def get(self):
resp = yield self.search_comment()
self.write(resp)
2021-06-17 10:39:47 +08:00
class AnnouncementHandler(BaseHandler):
class_name = f"Announcement{adapter}Resource"
# from Mongo import AnnouncementMongoResource
# instance = AnnouncementMongoResource()
@run_on_executor()
def get_announcement(self):
size = int(self.get_argument("size", "5"))
page = int(self.get_argument("page", "1"))
return self.instance.get_announcement(page, size)
@run_on_executor()
def add_announcement(self):
username = self.get_current_user()
if not self.instance.is_admin(username):
self.set_status(HTTPStatus.FORBIDDEN)
return {"message": "只有管理员可以设置公告"}
2021-08-15 12:28:19 +08:00
payload = self.json
2021-06-17 10:39:47 +08:00
content = payload["content"]
2022-04-10 13:59:00 +08:00
real_ip = self.get_real_ip()
2021-06-17 10:39:47 +08:00
browser = self.request.headers['user-agent']
self.instance.add_announcement(username, content, real_ip, browser)
self.set_status(HTTPStatus.CREATED)
return {"message": "添加成功"}
@gen.coroutine
def get(self):
resp = yield self.get_announcement()
self.write(resp)
@gen.coroutine
2021-06-22 20:01:10 +08:00
@web.authenticated
2021-06-17 10:39:47 +08:00
def post(self):
resp = yield self.add_announcement()
self.write(resp)
class CaptchaHandler(BaseHandler, CaptchaResource):
2021-07-18 11:51:39 +08:00
@run_on_executor()
def verify_captcha(self):
2021-08-15 12:28:19 +08:00
data = self.json
2021-07-18 11:51:39 +08:00
captcha_id = data.get("id", None)
userinput = data.get("captcha", None)
if captcha_id is None or userinput is None:
self.set_status(HTTPStatus.BAD_REQUEST)
return "Please supply id or captcha parameter."
returned = self.verify_code(userinput, captcha_id)
status_code = returned.get("status")
if not status_code:
self.set_status(HTTPStatus.FORBIDDEN)
return returned
2021-06-17 10:39:47 +08:00
@run_on_executor()
def captcha(self):
request_id = self.get_argument("id", None)
if request_id is None:
self.set_status(HTTPStatus.BAD_REQUEST)
return "Please supply id parameter."
return self.get_captcha(request_id)
@gen.coroutine
def get(self):
resp = yield self.captcha()
self.write(resp)
2021-07-18 11:51:39 +08:00
@gen.coroutine
def post(self):
resp = yield self.verify_captcha()
self.write(resp)
2021-07-13 21:41:24 +08:00
2021-06-17 10:39:47 +08:00
class MetricsHandler(BaseHandler):
class_name = f"Metrics{adapter}Resource"
# from Mongo import MetricsMongoResource
# instance = MetricsMongoResource()
@run_on_executor()
def set_metrics(self):
2021-08-15 12:28:19 +08:00
payload = self.json
2021-06-17 10:39:47 +08:00
metrics_type = payload["type"]
self.instance.set_metrics(metrics_type)
self.set_status(HTTPStatus.CREATED)
return {}
@run_on_executor()
def get_metrics(self):
2021-07-13 21:41:24 +08:00
if not self.instance.is_admin(self.get_current_user()):
self.set_status(HTTPStatus.NOT_FOUND)
return ""
2021-06-17 10:39:47 +08:00
# only return latest 7 days. with days parameter to generate different range
from_date = self.get_query_argument("from", None)
to_date = self.get_query_argument("to", None)
if to_date is None:
to_date = time.strftime("%Y-%m-%d", time.localtime())
if from_date is None:
from_date = time.strftime("%Y-%m-%d", time.localtime(time.time() - 3600 * 24 * 7))
return self.instance.get_metrics(from_date, to_date)
@gen.coroutine
def get(self):
resp = yield self.get_metrics()
self.write(resp)
@gen.coroutine
def post(self):
resp = yield self.set_metrics()
self.write(resp)
class GrafanaIndexHandler(BaseHandler):
def get(self):
self.write({})
class GrafanaSearchHandler(BaseHandler):
def post(self):
2021-07-05 18:33:29 +08:00
data = ["resource", "top", "home", "search", "extra", "discuss", "multiDownload", "download", "user", "share",
"me", "database", "help", "backOld", "favorite", "unFavorite", "comment"]
2021-06-17 10:39:47 +08:00
self.write(json.dumps(data))
class GrafanaQueryHandler(BaseHandler):
class_name = f"GrafanaQuery{adapter}Resource"
# from Mongo import GrafanaQueryMongoResource
# instance = GrafanaQueryMongoResource()
@staticmethod
def generate_date_series(start: str, end: str) -> list:
start_int = [int(i) for i in start.split("-")]
end_int = [int(i) for i in end.split("-")]
sdate = date(*start_int) # start date
edate = date(*end_int) # end date
delta = edate - sdate # as timedelta
days = []
for i in range(delta.days + 1):
day = sdate + timedelta(days=i)
days.append(day.strftime("%Y-%m-%d"))
return days
@staticmethod
def time_str_int(text):
return time.mktime(time.strptime(text, "%Y-%m-%d"))
def post(self):
2021-08-15 12:28:19 +08:00
payload = self.json
2021-06-17 10:39:47 +08:00
start = payload["range"]["from"].split("T")[0]
end = payload["range"]["to"].split("T")[0]
date_series = self.generate_date_series(start, end)
targets = [i["target"] for i in payload["targets"] if i["target"]]
grafana_data = []
for target in targets:
data_points = []
result = self.instance.get_grafana_data(date_series)
2021-07-05 18:33:29 +08:00
i: dict
2021-06-17 10:39:47 +08:00
for i in result:
2021-07-05 18:33:29 +08:00
datum = [i[target], self.time_str_int(i["date"]) * 1000] if i.get(target) else []
2021-06-17 10:39:47 +08:00
data_points.append(datum)
temp = {
"target": target,
"datapoints": data_points
}
grafana_data.append(temp)
self.write(json.dumps(grafana_data))
class BlacklistHandler(BaseHandler):
class_name = f"Blacklist{adapter}Resource"
# from Mongo import BlacklistMongoResource
# instance = BlacklistMongoResource()
@run_on_executor()
def get_black_list(self):
return self.instance.get_black_list()
@gen.coroutine
def get(self):
resp = yield self.get_black_list()
self.write(resp)
class NotFoundHandler(BaseHandler):
def get(self): # for react app
2022-04-10 17:11:18 +08:00
# if self.request.uri not in ["/", "/home", "/discuss", "/login", "/404", "/search",
# "/resource", "/me", "/database", "help", "/statistics"
# ]:
# self.ban()
2021-08-15 12:28:19 +08:00
self.render(index)
2021-06-17 10:39:47 +08:00
class DBDumpHandler(BaseHandler):
@staticmethod
def sizeof_fmt(num: int, suffix='B'):
for unit in ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi']:
if abs(num) < 1024.0:
return "%3.1f%s%s" % (num, unit, suffix)
num /= 1024.0
return "%.1f%s%s" % (num, 'Yi', suffix)
@staticmethod
def ts_date(ts):
return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(ts))
def file_info(self, file_path) -> dict:
result = {}
2021-08-22 18:35:04 +08:00
for fp in file_path:
try:
checksum = self.checksum(fp)
creation = self.ts_date(os.stat(fp).st_ctime)
size = self.sizeof_fmt(os.stat(fp).st_size)
result[fp] = [checksum, creation, size]
except Exception as e:
result[fp] = str(e), "", ""
2021-06-17 10:39:47 +08:00
return result
@staticmethod
def checksum(file_path) -> str:
sha = sha1()
try:
with open(file_path, "rb") as f:
sha.update(f.read())
checksum = sha.hexdigest()
except Exception as e:
checksum = str(e)
return checksum
@run_on_executor()
@Redis.cache(3600)
def get_hash(self):
2021-08-22 18:35:04 +08:00
file_list = [
2022-03-04 20:04:55 +08:00
"templates/dump/yyets_mongo.gz",
"templates/dump/yyets_mysql.zip",
"templates/dump/yyets_sqlite.zip"
2021-08-22 18:35:04 +08:00
]
2021-06-17 10:39:47 +08:00
result = {}
data = self.file_info(file_list)
for file, value in data.items():
2022-02-06 18:51:41 +08:00
filename = pathlib.Path(file).name
2021-06-17 10:39:47 +08:00
result[filename] = {
"checksum": value[0],
"date": value[1],
"size": value[2],
}
2022-02-06 18:51:41 +08:00
print(result)
2021-06-17 10:39:47 +08:00
return result
@gen.coroutine
def get(self):
resp = yield self.get_hash()
self.write(resp)
2021-07-10 23:28:44 +08:00
class DoubanHandler(BaseHandler):
class_name = f"Douban{adapter}Resource"
# from Mongo import DoubanMongoResource
# instance = DoubanMongoResource()
@run_on_executor()
def douban_data(self):
rid = self.get_query_argument("resource_id")
data = self.instance.get_douban_data(int(rid))
data.pop("posterData")
2021-07-10 23:28:44 +08:00
return data
def get_image(self) -> bytes:
rid = self.get_query_argument("resource_id")
return self.instance.get_douban_image(int(rid))
@gen.coroutine
def get(self):
_type = self.get_query_argument("type", None)
if _type == "image":
data = self.get_image()
self.set_header("content-type", filetype.guess_mime(data))
self.write(data)
else:
resp = yield self.douban_data()
self.write(resp)
2021-07-18 11:51:39 +08:00
class DoubanReportHandler(BaseHandler):
class_name = f"DoubanReport{adapter}Resource"
# from Mongo import DoubanReportMongoResource
# instance = DoubanReportMongoResource()
@run_on_executor()
def get_error(self):
return self.instance.get_error()
@run_on_executor()
def report_error(self):
2021-08-15 12:28:19 +08:00
data = self.json
2021-07-18 11:51:39 +08:00
user_captcha = data["captcha_id"]
captcha_id = data["id"]
content = data["content"]
resource_id = data["resource_id"]
returned = self.instance.report_error(user_captcha, captcha_id, content, resource_id)
status_code = returned.get("status_code", HTTPStatus.CREATED)
self.set_status(status_code)
return self.instance.report_error(user_captcha, captcha_id, content, resource_id)
@gen.coroutine
def post(self):
resp = yield self.report_error()
self.write(resp)
@gen.coroutine
def get(self):
resp = yield self.get_error()
self.write(resp)
2021-08-15 12:28:19 +08:00
class NotificationHandler(BaseHandler):
class_name = f"Notification{adapter}Resource"
# from Mongo import NotificationResource
# instance = NotificationResource()
@run_on_executor()
def get_notification(self):
username = self.get_current_user()
size = int(self.get_argument("size", "5"))
page = int(self.get_argument("page", "1"))
return self.instance.get_notification(username, page, size)
@run_on_executor()
def update_notification(self):
username = self.get_current_user()
verb = self.json["verb"]
comment_id = self.json["comment_id"]
if verb not in ["read", "unread"]:
self.set_status(HTTPStatus.BAD_REQUEST)
return {"status": False, "message": "verb: read or unread"}
self.set_status(HTTPStatus.CREATED)
return self.instance.update_notification(username, verb, comment_id)
@gen.coroutine
@web.authenticated
def get(self):
resp = yield self.get_notification()
self.write(resp)
@gen.coroutine
@web.authenticated
def patch(self):
resp = yield self.update_notification()
self.write(resp)
class UserEmailHandler(BaseHandler):
class_name = f"UserEmail{adapter}Resource"
# from Mongo import UserEmailResource
# instance = UserEmailResource()
@run_on_executor()
def verify_email(self):
result = self.instance.verify_email(self.get_current_user(), self.json["code"])
self.set_status(result.get("status_code"))
return result
@gen.coroutine
@web.authenticated
def post(self):
resp = yield self.verify_email()
self.write(resp)
class CategoryHandler(BaseHandler):
class_name = f"Category{adapter}Resource"
2021-12-16 20:04:37 +08:00
# from Mongo import CategoryResource
# instance = CategoryResource()
2021-08-15 12:28:19 +08:00
@run_on_executor()
def get_data(self):
self.json = {k: self.get_argument(k) for k in self.request.arguments}
self.json["size"] = int(self.json.get("size", 15))
self.json["page"] = int(self.json.get("page", 1))
self.json["douban"] = self.json.get("douban", False)
return self.instance.get_category(self.json)
@gen.coroutine
def get(self):
resp = yield self.get_data()
self.write(resp)
2021-12-16 20:04:37 +08:00
class SpamProcessHandler(BaseHandler):
class_name = f"SpamProcess{adapter}Resource"
2021-12-20 19:12:23 +08:00
# from Mongo import SpamProcessMongoResource
# instance = SpamProcessMongoResource()
2021-12-16 20:04:37 +08:00
def process(self, method):
obj_id = self.json.get("obj_id")
token = self.json.get("token")
ua = self.request.headers['user-agent']
2022-04-10 13:59:00 +08:00
ip = self.get_real_ip()
2021-12-16 20:04:37 +08:00
logging.info("Authentication %s(%s) for spam API now...", ua, ip)
if token == os.getenv("TOKEN"):
return getattr(self.instance, method)(obj_id)
else:
self.set_status(HTTPStatus.FORBIDDEN)
2022-09-09 18:07:55 +08:00
return {"status": False, "message": "This token is not allowed to access this API"}
2021-12-16 20:04:37 +08:00
@gen.coroutine
def post(self):
self.write(self.process("restore_spam"))
@gen.coroutine
def delete(self):
2022-09-09 18:07:55 +08:00
self.write(self.process("ban_spam"))
2023-02-08 20:55:26 +01:00
class GitHubOAuth2LoginHandler(BaseHandler, OAuth2Mixin):
_OAUTH_AUTHORIZE_URL = "https://github.com/login/oauth/authorize"
_OAUTH_ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token"
_OAUTH_API_REQUEST_URL = "https://api.github.com/user"
class_name = f"OAuthRegisterResource"
def add_oauth_user(self, username):
ip = self.get_real_ip()
browser = self.request.headers['user-agent']
response = self.instance.add_user(username, ip, browser)
return response
def get(self):
2023-02-08 22:20:15 +01:00
settings = self.settings.get("github_oauth")
github_client_id = settings.get("key")
github_client_secret = settings.get("secret")
redirect_uri = os.getenv("DOMAIN") + self.request.path
2023-02-08 20:55:26 +01:00
code = self.get_argument('code', None)
if code:
2023-02-08 22:20:15 +01:00
body = {"client_id": github_client_id, "client_secret": github_client_secret, "code": code}
access = requests.post(self._OAUTH_ACCESS_TOKEN_URL, data=body,
headers={"Accept": "application/json"}).json()
resp = requests.get(self._OAUTH_API_REQUEST_URL,
headers={"Authorization": "Bearer {}".format(access["access_token"])}
).json()
2023-02-08 20:55:26 +01:00
username = resp["login"]
logging.info("User %s login with GitHub now...", username)
result = self.add_oauth_user(username)
if result["status"] == "success":
self.set_secure_cookie("username", username, 365)
self.redirect("/login?" + urlencode(result))
else:
self.authorize_redirect(
2023-02-08 22:20:15 +01:00
redirect_uri=redirect_uri,
client_id=github_client_id,
2023-02-08 20:55:26 +01:00
scope=[],
response_type='code')
2023-02-08 22:20:15 +01:00
class GoogleOAuth2LoginHandler(BaseHandler, GoogleOAuth2Mixin):
class_name = f"OAuthRegisterResource"
def add_oauth_user(self, email):
ip = self.get_real_ip()
browser = self.request.headers['user-agent']
response = self.instance.add_user(email, ip, browser)
return response
async def get(self):
redirect_uri = os.getenv("DOMAIN") + self.request.path
code = self.get_argument('code', None)
if code:
access = await self.get_authenticated_user(
redirect_uri=redirect_uri,
code=code)
user = await self.oauth2_request(
"https://www.googleapis.com/oauth2/v1/userinfo",
access_token=access["access_token"])
email = user["email"]
logging.info("User %s login with GitHub now...", email)
result = self.add_oauth_user(email)
if result["status"] == "success":
self.set_secure_cookie("username", email, 365)
self.redirect("/login?" + urlencode(result))
else:
self.authorize_redirect(
redirect_uri=redirect_uri,
client_id=self.settings['google_oauth']['key'],
scope=['email'],
response_type='code',
extra_params={'approval_prompt': 'auto'})