A copyright violation detector running on Wikimedia Cloud Services https://tools.wmflabs.org/copyvios/
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.
 
 
 
 
 

176 lines
4.5 KiB

  1. #! /usr/bin/env python
  2. import logging
  3. from functools import wraps
  4. from hashlib import md5
  5. from json import dumps
  6. from logging.handlers import TimedRotatingFileHandler
  7. from os import path
  8. from time import asctime
  9. from traceback import format_exc
  10. from earwigbot.bot import Bot
  11. from earwigbot.wiki.copyvios import globalize
  12. from flask import Flask, g, make_response, request
  13. from flask_mako import MakoTemplates, TemplateError, render_template
  14. from copyvios.api import format_api_error, handle_api_request
  15. from copyvios.checker import do_check
  16. from copyvios.cookies import parse_cookies
  17. from copyvios.misc import cache, get_notice
  18. from copyvios.settings import process_settings
  19. from copyvios.sites import update_sites
  20. app = Flask(__name__)
  21. MakoTemplates(app)
  22. hand = TimedRotatingFileHandler("logs/app.log", when="midnight", backupCount=7)
  23. hand.setLevel(logging.DEBUG)
  24. app.logger.addHandler(hand)
  25. app.logger.info("Flask server started " + asctime())
  26. app._hash_cache = {}
  27. def catch_errors(func):
  28. @wraps(func)
  29. def inner(*args, **kwargs):
  30. try:
  31. return func(*args, **kwargs)
  32. except TemplateError as exc:
  33. app.logger.error(f"Caught exception:\n{exc.text}")
  34. return render_template("error.mako", traceback=exc.text)
  35. except Exception:
  36. app.logger.exception("Caught exception:")
  37. return render_template("error.mako", traceback=format_exc())
  38. return inner
  39. @app.before_first_request
  40. def setup_app():
  41. cache.bot = Bot(".earwigbot", 100)
  42. cache.langs, cache.projects = [], []
  43. cache.last_sites_update = 0
  44. cache.background_data = {}
  45. cache.last_background_updates = {}
  46. globalize(num_workers=8)
  47. @app.before_request
  48. def prepare_request():
  49. g._db = None
  50. g.cookies = parse_cookies(
  51. request.script_root or "/", request.environ.get("HTTP_COOKIE")
  52. )
  53. g.new_cookies = []
  54. @app.after_request
  55. def add_new_cookies(response):
  56. for cookie in g.new_cookies:
  57. response.headers.add("Set-Cookie", cookie)
  58. return response
  59. @app.after_request
  60. def write_access_log(response):
  61. msg = "%s %s %s %s -> %s"
  62. app.logger.debug(
  63. msg,
  64. asctime(),
  65. request.method,
  66. request.path,
  67. request.values.to_dict(),
  68. response.status_code,
  69. )
  70. return response
  71. @app.teardown_appcontext
  72. def close_databases(error):
  73. if g._db:
  74. g._db.close()
  75. def external_url_handler(error, endpoint, values):
  76. if endpoint == "static" and "file" in values:
  77. fpath = path.join(app.static_folder, values["file"])
  78. mtime = path.getmtime(fpath)
  79. cache = app._hash_cache.get(fpath)
  80. if cache and cache[0] == mtime:
  81. hashstr = cache[1]
  82. else:
  83. with open(fpath, "rb") as f:
  84. hashstr = md5(f.read()).hexdigest()
  85. app._hash_cache[fpath] = (mtime, hashstr)
  86. return f"/static/{values['file']}?v={hashstr}"
  87. raise error
  88. app.url_build_error_handlers.append(external_url_handler)
  89. @app.route("/")
  90. @catch_errors
  91. def index():
  92. notice = get_notice()
  93. update_sites()
  94. query = do_check()
  95. return render_template(
  96. "index.mako",
  97. notice=notice,
  98. query=query,
  99. result=query.result,
  100. turnitin_result=query.turnitin_result,
  101. )
  102. @app.route("/settings", methods=["GET", "POST"])
  103. @catch_errors
  104. def settings():
  105. status = process_settings() if request.method == "POST" else None
  106. update_sites()
  107. default = cache.bot.wiki.get_site()
  108. kwargs = {
  109. "status": status,
  110. "default_lang": default.lang,
  111. "default_project": default.project,
  112. }
  113. return render_template("settings.mako", **kwargs)
  114. @app.route("/api")
  115. @catch_errors
  116. def api():
  117. return render_template("api.mako", help=True)
  118. @app.route("/api.json")
  119. @catch_errors
  120. def api_json():
  121. if not request.args:
  122. return render_template("api.mako", help=True)
  123. format = request.args.get("format", "json")
  124. if format in ["json", "jsonfm"]:
  125. update_sites()
  126. try:
  127. result = handle_api_request()
  128. except Exception as exc:
  129. result = format_api_error("unhandled_exception", exc)
  130. else:
  131. errmsg = f"Unknown format: '{format}'"
  132. result = format_api_error("unknown_format", errmsg)
  133. if format == "jsonfm":
  134. return render_template("api.mako", help=False, result=result)
  135. resp = make_response(dumps(result))
  136. resp.mimetype = "application/json"
  137. resp.headers["Access-Control-Allow-Origin"] = "*"
  138. return resp
  139. if __name__ == "__main__":
  140. app.run()