A Python robot that edits Wikipedia and interacts with people over IRC https://en.wikipedia.org/wiki/User:EarwigBot
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ů.

154 řádky
6.2 KiB

  1. # -*- coding: utf-8 -*-
  2. #
  3. # Copyright (C) 2009-2016 Ben Kurtovic <ben.kurtovic@gmail.com>
  4. #
  5. # Permission is hereby granted, free of charge, to any person obtaining a copy
  6. # of this software and associated documentation files (the "Software"), to deal
  7. # in the Software without restriction, including without limitation the rights
  8. # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9. # copies of the Software, and to permit persons to whom the Software is
  10. # furnished to do so, subject to the following conditions:
  11. #
  12. # The above copyright notice and this permission notice shall be included in
  13. # all copies or substantial portions of the Software.
  14. #
  15. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16. # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17. # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18. # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19. # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  20. # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  21. # SOFTWARE.
  22. from collections import namedtuple
  23. import re
  24. import socket
  25. from socket import AF_INET, AF_INET6
  26. from earwigbot.commands import Command
  27. _Range = namedtuple("_Range", [
  28. "family", "range", "low", "high", "size", "addresses"])
  29. class CIDR(Command):
  30. """Calculates the smallest CIDR range that encompasses a list of IP
  31. addresses. Used to make range blocks."""
  32. name = "cidr"
  33. commands = ["cidr", "range", "rangeblock", "rangecalc", "blockcalc",
  34. "iprange", "cdir"]
  35. # https://www.mediawiki.org/wiki/Manual:$wgBlockCIDRLimit
  36. LIMIT_IPv4 = 16
  37. LIMIT_IPv6 = 19
  38. def process(self, data):
  39. if not data.args:
  40. msg = ("Specify a list of IP addresses to calculate a CIDR range "
  41. "for. For example, \x0306!{0} 192.168.0.3 192.168.0.15 "
  42. "192.168.1.4\x0F or \x0306!{0} 2500:1:2:3:: "
  43. "2500:1:2:3:dead:beef::\x0F.")
  44. self.reply(data, msg.format(data.command))
  45. return
  46. try:
  47. ips = [self._parse_arg(arg) for arg in data.args]
  48. except ValueError as exc:
  49. msg = "Can't parse IP address \x0302{0}\x0F."
  50. self.reply(data, msg.format(exc.message))
  51. return
  52. if any(ip[0] == AF_INET for ip in ips) and any(ip[0] == AF_INET6 for ip in ips):
  53. msg = "Can't calculate a range for both IPv4 and IPv6 addresses."
  54. self.reply(data, msg)
  55. return
  56. cidr = self._calculate_range(ips[0][0], [ip[1] for ip in ips])
  57. descr = self._describe(cidr.family, cidr.size)
  58. msg = ("Smallest CIDR range is \x02{0}\x0F, covering {1} from "
  59. "\x0305{2}\x0F to \x0305{3}\x0F{4}.")
  60. self.reply(data, msg.format(
  61. cidr.range, cidr.addresses, cidr.low, cidr.high,
  62. " (\x0304{0}\x0F)".format(descr) if descr else ""))
  63. def _parse_arg(self, arg):
  64. """Converts an argument into an IP address."""
  65. if "[[" in arg and "]]" in arg:
  66. regex = r"\[\[\s*(?:User(?:\stalk)?:)?(.*?)(?:\|.*?)?\s*\]\]"
  67. match = re.search(regex, arg, re.I)
  68. if not match:
  69. raise ValueError(arg)
  70. arg = match.group(1)
  71. if re.match(r"https?://", arg):
  72. if "target=" in arg:
  73. regex = r"target=(.*?)(?:&|$)"
  74. elif "page=" in arg:
  75. regex = r"page=(?:User(?:(?:\s|_)talk)?(?::|%3A))?(.*?)(?:&|$)"
  76. elif re.search(r"Special(:|%3A)Contributions/", arg, re.I):
  77. regex = r"Special(?:\:|%3A)Contributions/(.*?)(?:\&|\?|$)"
  78. elif re.search(r"User((\s|_)talk)?(:|%3A)", arg, re.I):
  79. regex = r"User(?:(?:\s|_)talk)?(?:\:|%3A)(.*?)(?:\&|\?|$)"
  80. else:
  81. raise ValueError(arg)
  82. match = re.search(regex, arg, re.I)
  83. if not match:
  84. raise ValueError(arg)
  85. arg = match.group(1)
  86. try:
  87. return (AF_INET, socket.inet_pton(AF_INET, arg))
  88. except socket.error:
  89. try:
  90. return (AF_INET6, socket.inet_pton(AF_INET6, arg))
  91. except socket.error:
  92. raise ValueError(arg)
  93. def _calculate_range(self, family, ips):
  94. """Calculate the smallest CIDR range encompassing a list of IPs."""
  95. bin_ips = ["".join(
  96. bin(ord(octet))[2:].zfill(8) for octet in ip) for ip in ips]
  97. size = len(bin_ips[0])
  98. for i in xrange(len(bin_ips[0])):
  99. if any(ip[i] == "0" for ip in bin_ips) and any(
  100. ip[i] == "1" for ip in bin_ips):
  101. size = i
  102. break
  103. bin_low = bin_ips[0][:size].ljust(len(bin_ips[0]), "0")
  104. bin_high = bin_ips[0][:size].ljust(len(bin_ips[0]), "1")
  105. low = self._format_bin(family, bin_low)
  106. high = self._format_bin(family, bin_high)
  107. return _Range(
  108. family, low + "/" + str(size), low, high, size,
  109. self._format_count(2 ** (len(bin_ips[0]) - size)))
  110. @staticmethod
  111. def _format_bin(family, binary):
  112. """Convert an IP's binary representation to presentation format."""
  113. return socket.inet_ntop(family, "".join(
  114. chr(int(binary[i:i + 8], 2)) for i in xrange(0, len(binary), 8)))
  115. @staticmethod
  116. def _format_count(count):
  117. """Nicely format a number of addresses affected by a range block."""
  118. if count == 1:
  119. return "1 address"
  120. if count > 2 ** 32:
  121. base = "{0:.2E} addresses".format(count)
  122. if count == 2 ** 64:
  123. return base + " (1 /64 subnet)"
  124. if count > 2 ** 96:
  125. return base + " ({0:.2E} /64 subnets)".format(count >> 64)
  126. if count > 2 ** 63:
  127. return base + " ({0:,} /64 subnets)".format(count >> 64)
  128. return base
  129. return "{0:,} addresses".format(count)
  130. def _describe(self, family, size):
  131. """Return an optional English description of a range."""
  132. if (family == AF_INET and size < self.LIMIT_IPv4) or (
  133. family == AF_INET6 and size < self.LIMIT_IPv6):
  134. return "too large to block"