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

пре 12 година
пре 12 година
пре 12 година
пре 12 година
пре 3 година
пре 12 година
пре 12 година
пре 12 година
пре 12 година
пре 12 година
пре 11 година
пре 12 година
пре 12 година
пре 12 година
пре 11 година
пре 12 година
пре 12 година
пре 3 година
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  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. import re
  23. __all__ = ["Data"]
  24. class Data:
  25. """Store data from an individual line received on IRC."""
  26. def __init__(self, my_nick, line, msgtype):
  27. self._my_nick = my_nick.lower()
  28. self._line = line
  29. self._msgtype = msgtype
  30. self._is_private = self._is_command = False
  31. self._msg = self._command = self._trigger = None
  32. self._args = []
  33. self._kwargs = {}
  34. self._parse()
  35. def __repr__(self):
  36. """Return the canonical string representation of the Data."""
  37. res = "Data(my_nick={0!r}, line={1!r})"
  38. return res.format(self.my_nick, self.line)
  39. def __str__(self):
  40. """Return a nice string representation of the Data."""
  41. return "<Data of {0!r}>".format(" ".join(self.line))
  42. def _parse(self):
  43. """Parse a line from IRC into its components as instance attributes."""
  44. self._chan = self.line[2]
  45. try:
  46. sender = re.findall(r":(.*?)!(.*?)@(.*?)\Z", self.line[0])[0]
  47. except IndexError:
  48. self._host = self.line[0][1:]
  49. self._nick = self._ident = self._reply_nick = "*"
  50. return
  51. self._nick, self._ident, self._host = sender
  52. self._reply_nick = self._nick
  53. if self._msgtype in ["PRIVMSG", "NOTICE"]:
  54. if self.chan.lower() == self.my_nick:
  55. # This is a privmsg to us, so set 'chan' as the nick of the
  56. # sender instead of the 'channel', which is ourselves:
  57. self._chan = self._nick
  58. self._is_private = True
  59. self._msg = " ".join(self.line[3:])[1:]
  60. if self._msgtype == "PRIVMSG":
  61. self._parse_args()
  62. self._parse_kwargs()
  63. def _parse_args(self):
  64. """Parse command arguments from the message.
  65. self.msg is converted into the string self.command and the argument
  66. list self.args if the message starts with a "trigger" ("!", ".", or the
  67. bot's name); self.is_command will be set to True, and self.trigger will
  68. store the trigger string. Otherwise, is_command will be set to False.
  69. """
  70. self._args = self.msg.strip().split()
  71. try:
  72. command_uc = self.args.pop(0)
  73. self._command = command_uc.lower()
  74. except IndexError:
  75. return
  76. # e.g. "!command>user arg1 arg2"
  77. if ">" in self.command:
  78. command_uc, self._reply_nick = command_uc.split(">", 1)
  79. self._command = command_uc.lower()
  80. if self.command.startswith("!") or self.command.startswith("."):
  81. # e.g. "!command arg1 arg2"
  82. self._is_command = True
  83. self._trigger = self.command[0]
  84. self._command = self.command[1:] # Strip the "!" or "."
  85. elif re.match(r"{0}\W*?$".format(re.escape(self.my_nick)),
  86. self.command, re.U):
  87. # e.g. "EarwigBot, command arg1 arg2"
  88. self._is_command = True
  89. self._trigger = self.my_nick
  90. try:
  91. self._command = self.args.pop(0).lower()
  92. except IndexError:
  93. self._command = ""
  94. else:
  95. try:
  96. if self.msg[-1] == "." and self.msg[-2] != ".":
  97. if self.args:
  98. self.args[-1] = self.args[-1][:-1]
  99. else:
  100. self._command = self.command[:-1]
  101. except IndexError:
  102. pass
  103. # e.g. "!command >user arg1 arg2"
  104. if self.args and self.args[0].startswith(">"):
  105. self._reply_nick = self.args.pop(0)[1:]
  106. def _parse_kwargs(self):
  107. """Parse keyword arguments embedded in self.args.
  108. Parse a command given as "!command key1=value1 key2=value2..." into a
  109. dict, self.kwargs, like {'key1': 'value2', 'key2': 'value2'...}.
  110. """
  111. for arg in self.args:
  112. try:
  113. key, value = re.findall(r"^(.*?)\=(.*?)$", arg)[0]
  114. except IndexError:
  115. continue
  116. if key and value:
  117. self.kwargs[key] = value
  118. @property
  119. def my_nick(self):
  120. """Our nickname, *not* the nickname of the sender."""
  121. return self._my_nick
  122. @property
  123. def line(self):
  124. """The full message received on IRC, including escape characters."""
  125. return self._line
  126. @property
  127. def chan(self):
  128. """Channel the message was sent from.
  129. This will be equal to :py:attr:`nick` if the message is a private
  130. message.
  131. """
  132. return self._chan
  133. @property
  134. def nick(self):
  135. """Nickname of the sender."""
  136. return self._nick
  137. @property
  138. def ident(self):
  139. """`Ident <https://en.wikipedia.org/wiki/Ident_protocol>`_ of the sender."""
  140. return self._ident
  141. @property
  142. def host(self):
  143. """Hostname of the sender."""
  144. return self._host
  145. @property
  146. def reply_nick(self):
  147. """Nickname of the person to reply to. Sender by default."""
  148. return self._reply_nick
  149. @property
  150. def msg(self):
  151. """Text of the sent message, if it is a message, else ``None``."""
  152. return self._msg
  153. @property
  154. def is_private(self):
  155. """``True`` if this message was sent to us *only*, else ``False``."""
  156. return self._is_private
  157. @property
  158. def is_command(self):
  159. """Boolean telling whether or not this message is a bot command.
  160. A message is considered a command if and only if it begins with the
  161. character ``"!"``, ``"."``, or the bot's name followed by optional
  162. punctuation and a space (so ``EarwigBot: do something``, ``EarwigBot,
  163. do something``, and ``EarwigBot do something`` are all valid).
  164. """
  165. return self._is_command
  166. @property
  167. def command(self):
  168. """If the message is a command, this is the name of the command used.
  169. See :py:attr:`is_command <self.is_command>` for when a message is
  170. considered a command. If it's not a command, this will be set to
  171. ``None``.
  172. """
  173. return self._command
  174. @property
  175. def trigger(self):
  176. """If this message is a command, this is what triggered it.
  177. It can be either "!" (``"!help"``), "." (``".help"``), or the bot's
  178. name (``"EarwigBot: help"``). Otherwise, it will be ``None``."""
  179. return self._trigger
  180. @property
  181. def args(self):
  182. """List of all arguments given to this command.
  183. For example, the message ``"!command arg1 arg2 arg3=val3"`` will
  184. produce the args ``["arg1", "arg2", "arg3=val3"]``. This is empty if
  185. the message was not a command or if it doesn't have arguments.
  186. """
  187. return self._args
  188. @property
  189. def kwargs(self):
  190. """Dictionary of keyword arguments given to this command.
  191. For example, the message ``"!command arg1=val1 arg2=val2"`` will
  192. produce the kwargs ``{"arg1": "val1", "arg2": "val2"}``. This is empty
  193. if the message was not a command or if it doesn't have keyword
  194. arguments.
  195. """
  196. return self._kwargs
  197. def serialize(self):
  198. """Serialize this object into a tuple and return it."""
  199. return (self._my_nick, self._line, self._msgtype)
  200. @classmethod
  201. def unserialize(cls, data):
  202. """Return a new Data object built from a serialized tuple."""
  203. return cls(*data)