A copyright violation detector running on Wikimedia Cloud Services https://tools.wmflabs.org/copyvios/
Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.
 
 
 
 
 

190 řádky
7.2 KiB

  1. # -*- coding: utf-8 -*-
  2. from datetime import datetime
  3. from hashlib import sha256
  4. from urlparse import urlparse
  5. from earwigbot import exceptions
  6. from earwigbot.wiki.copyvios.markov import EMPTY, MarkovChain
  7. from earwigbot.wiki.copyvios.parsers import ArticleTextParser
  8. from earwigbot.wiki.copyvios.result import CopyvioSource, CopyvioCheckResult
  9. from .misc import Query, get_cache_db
  10. from .sites import get_site, get_sites
  11. __all__ = ["do_check", "T_POSSIBLE", "T_SUSPECT"]
  12. T_POSSIBLE = 0.4
  13. T_SUSPECT = 0.75
  14. def do_check():
  15. query = Query()
  16. if query.lang:
  17. query.lang = query.orig_lang = query.lang.lower()
  18. if "::" in query.lang:
  19. query.lang, query.name = query.lang.split("::", 1)
  20. if query.project:
  21. query.project = query.project.lower()
  22. query.all_langs, query.all_projects = get_sites()
  23. query.submitted = query.project and query.lang and (query.title or query.oldid)
  24. if query.submitted:
  25. query.site = get_site(query)
  26. if query.site:
  27. _get_results(query, follow=query.noredirect is None)
  28. return query
  29. def _get_results(query, follow=True):
  30. if query.oldid:
  31. page = query.page = _get_page_by_revid(query.site, query.oldid)
  32. if not page:
  33. return
  34. else:
  35. page = query.page = query.site.get_page(query.title)
  36. try:
  37. page.get() # Make sure that the page exists before we check it!
  38. except (exceptions.PageNotFoundError, exceptions.InvalidPageError):
  39. return
  40. if page.is_redirect and follow:
  41. try:
  42. query.title = page.get_redirect_target()
  43. except exceptions.RedirectError:
  44. pass # Something's wrong. Continue checking the original page.
  45. else:
  46. query.redirected_from = page
  47. return _get_results(query, follow=False)
  48. if not query.action:
  49. query.action = "compare" if query.url else "search"
  50. if query.action == "search":
  51. conn = get_cache_db()
  52. use_engine = 0 if query.use_engine == "0" else 1
  53. use_links = 0 if query.use_links == "0" else 1
  54. if not use_engine and not use_links:
  55. query.error = "no search method"
  56. return
  57. mode = "{0}:{1}:".format(use_engine, use_links)
  58. if not query.nocache:
  59. query.result = _get_cached_results(page, conn, mode, query.noskip)
  60. if not query.result:
  61. try:
  62. query.result = page.copyvio_check(
  63. min_confidence=T_SUSPECT, max_queries=10, max_time=45,
  64. no_searches=not use_engine, no_links=not use_links,
  65. short_circuit=not query.noskip)
  66. except exceptions.SearchQueryError as exc:
  67. query.error = "search error"
  68. query.exception = exc
  69. return
  70. query.result.cached = False
  71. _cache_result(page, query.result, conn, mode)
  72. elif query.action == "compare":
  73. if not query.url:
  74. query.error = "no URL"
  75. return
  76. scheme = urlparse(query.url).scheme
  77. if not scheme and query.url[0] not in ":/":
  78. query.url = "http://" + query.url
  79. elif scheme not in ["http", "https"]:
  80. query.error = "bad URI"
  81. return
  82. result = page.copyvio_compare(query.url, min_confidence=T_SUSPECT,
  83. max_time=30)
  84. if result.best.chains[0] is EMPTY:
  85. query.error = "timeout" if result.time > 30 else "no data"
  86. return
  87. query.result = result
  88. query.result.cached = False
  89. else:
  90. query.error = "bad action"
  91. def _get_page_by_revid(site, revid):
  92. res = site.api_query(action="query", prop="info|revisions", revids=revid,
  93. rvprop="content|timestamp", inprop="protection|url")
  94. try:
  95. page_data = res["query"]["pages"].values()[0]
  96. title = page_data["title"]
  97. page_data["revisions"][0]["*"] # Only need to check that these exist
  98. page_data["revisions"][0]["timestamp"]
  99. except KeyError:
  100. return
  101. page = site.get_page(title)
  102. # EarwigBot doesn't understand old revisions of pages, so we use a somewhat
  103. # dirty hack to make this work:
  104. page._load_attributes(res)
  105. page._load_content(res)
  106. return page
  107. def _get_cached_results(page, conn, mode, noskip):
  108. query1 = """DELETE FROM cache
  109. WHERE cache_time < DATE_SUB(CURRENT_TIMESTAMP, INTERVAL 3 DAY)"""
  110. query2 = """SELECT cache_time, cache_queries, cache_process_time
  111. FROM cache
  112. WHERE cache_id = ?"""
  113. query3 = """SELECT cdata_url, cdata_confidence, cdata_skipped
  114. FROM cache_data
  115. WHERE cdata_cache_id = ?"""
  116. cache_id = buffer(sha256(mode + page.get().encode("utf8")).digest())
  117. with conn.cursor() as cursor:
  118. cursor.execute(query1)
  119. cursor.execute(query2, (cache_id,))
  120. results = cursor.fetchall()
  121. if not results:
  122. return None
  123. cache_time, queries, check_time = results[0]
  124. cursor.execute(query3, (cache_id,))
  125. data = cursor.fetchall()
  126. if not data: # TODO: do something less hacky for this edge case
  127. artchain = MarkovChain(ArticleTextParser(page.get()).strip())
  128. result = CopyvioCheckResult(False, [], queries, check_time, artchain)
  129. result.cached = True
  130. result.cache_time = cache_time.strftime("%b %d, %Y %H:%M:%S UTC")
  131. result.cache_age = _format_date(cache_time)
  132. return result
  133. url, confidence, skipped = data.pop(0)
  134. if skipped: # Should be impossible: data must be bad; run a new check
  135. return None
  136. result = page.copyvio_compare(url, min_confidence=T_SUSPECT, max_time=30)
  137. if abs(result.confidence - confidence) >= 0.0001:
  138. return None
  139. for url, confidence, skipped in data:
  140. if noskip and skipped:
  141. return None
  142. source = CopyvioSource(None, url)
  143. source.confidence = confidence
  144. source.skipped = bool(skipped)
  145. result.sources.append(source)
  146. result.queries = queries
  147. result.time = check_time
  148. result.cached = True
  149. result.cache_time = cache_time.strftime("%b %d, %Y %H:%M:%S UTC")
  150. result.cache_age = _format_date(cache_time)
  151. return result
  152. def _format_date(cache_time):
  153. diff = datetime.utcnow() - cache_time
  154. if diff.seconds > 3600:
  155. return "{0} hours".format(diff.seconds / 3600)
  156. if diff.seconds > 60:
  157. return "{0} minutes".format(diff.seconds / 60)
  158. return "{0} seconds".format(diff.seconds)
  159. def _cache_result(page, result, conn, mode):
  160. query1 = "DELETE FROM cache WHERE cache_id = ?"
  161. query2 = "INSERT INTO cache VALUES (?, DEFAULT, ?, ?)"
  162. query3 = "INSERT INTO cache_data VALUES (DEFAULT, ?, ?, ?, ?)"
  163. cache_id = buffer(sha256(mode + page.get().encode("utf8")).digest())
  164. data = [(cache_id, source.url, source.confidence, source.skipped)
  165. for source in result.sources]
  166. with conn.cursor() as cursor:
  167. cursor.execute("START TRANSACTION")
  168. cursor.execute(query1, (cache_id,))
  169. cursor.execute(query2, (cache_id, result.queries, result.time))
  170. cursor.executemany(query3, data)
  171. cursor.execute("COMMIT")