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.

228 lines
6.8 KiB

  1. #!/usr/bin/env python3
  2. import re
  3. import httpx
  4. import sys
  5. import socket
  6. import urllib.request
  7. import json
  8. import argparse
  9. domains = []
  10. bad_stuns = {
  11. r"stun[\d]{0,1}\.l\.google\.com": "Google"
  12. }
  13. badAsnDesc = [
  14. "GOOGLE",
  15. "AMAZON",
  16. "CLOUDFLARE",
  17. "MICROSOFT"
  18. ]
  19. analyticsOptions = [
  20. "googleAnalyticsTrackingId",
  21. "amplitudeAPPKey",
  22. "scriptURLs"
  23. ]
  24. authOptions = [
  25. "anonymousdomain"
  26. ]
  27. def getPage(domain):
  28. isAvailable = True
  29. corporateStun = False
  30. jitsiVersion = None
  31. authEnabled = False
  32. analyticsEnabled = False
  33. try:
  34. page = httpx.get('https://' + domain + "/sdlkndflfdldhdfhdsofofbvcxbsd", timeout=30.0)
  35. if page.status_code != 200:
  36. return False
  37. return page
  38. except httpx._exceptions.NetworkError as err:
  39. sys.stderr.write(str(err) + "\n")
  40. return False
  41. def getJitsiVersion(page):
  42. if not page:
  43. return False
  44. jitsiVersion = None
  45. matches = re.findall("all\.css\?v\=(.*)\"", page.text)
  46. for match in matches:
  47. jitsiVersion = match;
  48. return jitsiVersion;
  49. # Checks if options are commented out or active
  50. def checkActiveOptions(page,items):
  51. if not page:
  52. return False
  53. for item in items:
  54. matches = re.findall("\n.*" + item, page.text)
  55. for match in matches:
  56. if "//" not in match:
  57. return True
  58. break
  59. return False;
  60. def checkHoster(result):
  61. if result['status'] == "success":
  62. for asnDesc in badAsnDesc:
  63. if result['as'].casefold().find(asnDesc.casefold()) != -1:
  64. return True
  65. return False
  66. def getDataFromAPI(domains):
  67. results = {}
  68. current = []
  69. while domains != []:
  70. if len(domains) >= 100:
  71. current = domains[:100]
  72. domains = domains[100:]
  73. else:
  74. current = domains
  75. domains = []
  76. ips = [None] * len(current)
  77. for i in range(len(current)):
  78. try:
  79. ips[i] = socket.gethostbyname(current[i])
  80. except socket.gaierror as err:
  81. sys.stderr.write(str(err) + "\n")
  82. r = httpx.post('http://ip-api.com/batch?fields=status,countryCode,isp,as,query', json=ips)
  83. answers = r.json()
  84. for i in range(len(current)):
  85. results[current[i]] = answers[i]
  86. if r.headers['X-Rl'] == 0:
  87. time.sleep(r.headers['X-Ttl'])
  88. current = []
  89. ips = []
  90. return results
  91. def parseText(f):
  92. char = f.read(1)
  93. while char:
  94. if char == "\"":
  95. domain = ""
  96. char = f.read(1)
  97. while char != "\"":
  98. domain = domain + char
  99. char = f.read(1)
  100. domains.append(domain)
  101. char = f.read(1)
  102. f.close()
  103. def outputMarkdown(outputs, unavailable):
  104. sys.stdout.write("# Jitsi Instanzen\n")
  105. outputListAsMarkdown(outputs)
  106. sys.stdout.write("\n## Aktuell nicht erreichbar\n")
  107. outputListAsMarkdown(unavailable)
  108. sys.stdout.write("\n# Legende\n")
  109. sys.stdout.write("\u2705: Die Instanz nutzt nicht den Google STUN Server\n\n")
  110. sys.stdout.write("\u274c: Die Instanz ist bei Google, Amazon, Cloudflare oder Microsoft gehostet\n\n")
  111. sys.stdout.write("\U0001F510: Die Instanz hat Authentifizierung aktiviert\n\n")
  112. sys.stdout.write("\U0001F50D: Die Instanz nutzt Analytics")
  113. def outputListAsMarkdown(outputs):
  114. for output in outputs:
  115. if (not output['available']):
  116. sys.stdout.write("[" + output['domain'] + "](https://" + output['domain'] + ") ")
  117. else:
  118. sys.stdout.write("[" + output['domain'] + "](https://" + output['domain'] + ") ")
  119. sys.stdout.write(output['countryCode'] + " ")
  120. sys.stdout.write("(Hoster: " + output['hoster'])
  121. if output['jitsiVersion']:
  122. sys.stdout.write(" , Version: " + output['jitsiVersion'])
  123. sys.stdout.write(") ")
  124. if not output['corporateStun']:
  125. sys.stdout.write("\u2705 ")
  126. if output['authEnabled']:
  127. sys.stdout.write("\U0001F510 ")
  128. if output['analyticsEnabled']:
  129. sys.stdout.write("\U0001F50D ")
  130. if output['corporateHoster']:
  131. sys.stdout.write("\u274c")
  132. sys.stdout.write("\\\n")
  133. def outputJSON(outputs, unavailable):
  134. outputList = outputs + unavailable
  135. sys.stdout.write(json.dumps({"data": outputList}));
  136. def parseFormat(format):
  137. if format == "json" or format == "md":
  138. return format
  139. else:
  140. msg = "%r is not valid, valid are md and json" % format
  141. raise argparse.ArgumentTypeError(msg)
  142. def main():
  143. parser = argparse.ArgumentParser()
  144. parser.add_argument("list", type=argparse.FileType("r"), help="File with instances, each in one line with the following format: \"meet.example.com\",")
  145. parser.add_argument("-f", "--format", help="Output format, can either be md or json", default="md", type=parseFormat)
  146. args = parser.parse_args()
  147. parseText(args.list)
  148. format = args.format
  149. sys.stderr.write(f"Output format: {format}\n")
  150. #Check all domains
  151. domains.sort()
  152. total = len(domains)
  153. i = 0
  154. outputs = []
  155. notAccessible = []
  156. sys.stderr.write("Doing API request now\n")
  157. apiResults = getDataFromAPI(domains)
  158. for domain, result in apiResults.items():
  159. sys.stderr.write("Doing page " + domain + " now\n")
  160. page = getPage(domain)
  161. available = True if page else False
  162. output = {}
  163. if available == True and result['status'] == "success":
  164. output = {
  165. "domain": domain,
  166. "available": True,
  167. "corporateHoster": checkHoster(result),
  168. "hoster": result['isp'],
  169. "countryCode": result['countryCode'],
  170. "corporateStun": checkActiveOptions(page,bad_stuns),
  171. "jitsiVersion": getJitsiVersion(page),
  172. "authEnabled": checkActiveOptions(page,authOptions),
  173. "analyticsEnabled": checkActiveOptions(page,analyticsOptions),
  174. }
  175. else:
  176. output = {
  177. "domain": domain,
  178. "available": False,
  179. "countryCode": "",
  180. }
  181. if result['status'] == "success":
  182. output['countryCode'] = result['countryCode']
  183. if available:
  184. outputs.append(output)
  185. else:
  186. sys.stderr.write(domain + " is unavailable\n")
  187. notAccessible.append(output)
  188. outputs = sorted(outputs, key=lambda output: output['countryCode'])
  189. notAccessible = sorted(notAccessible, key=lambda output: output['countryCode'])
  190. if format == "json":
  191. outputJSON(outputs, notAccessible)
  192. elif format == "md":
  193. outputMarkdown(outputs, notAccessible)
  194. else:
  195. sys.stderr.write("Unknown output format: " + format + "\n")
  196. if __name__ == '__main__':
  197. main()