A Python robot that edits Wikipedia and interacts with people over IRC https://en.wikipedia.org/wiki/User:EarwigBot
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

159 строки
5.3 KiB

  1. # -*- coding: utf-8 -*-
  2. #
  3. # Copyright (C) 2009-2012 by Ben Kurtovic <ben.kurtovic@verizon.net>
  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 socket
  23. from threading import Lock
  24. from time import sleep
  25. __all__ = ["BrokenSocketException", "IRCConnection"]
  26. class BrokenSocketException(Exception):
  27. """A socket has broken, because it is not sending data.
  28. Raised by IRCConnection()._get().
  29. """
  30. pass
  31. class IRCConnection(object):
  32. """A class to interface with IRC."""
  33. def __init__(self, host, port, nick, ident, realname):
  34. self.host = host
  35. self.port = port
  36. self.nick = nick
  37. self.ident = ident
  38. self.realname = realname
  39. self._is_running = False
  40. # A lock to prevent us from sending two messages at once:
  41. self._send_lock = Lock()
  42. def _connect(self):
  43. """Connect to our IRC server."""
  44. self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  45. try:
  46. self._sock.connect((self.host, self.port))
  47. except socket.error:
  48. self.logger.exception("Couldn't connect to IRC server")
  49. sleep(8)
  50. self._connect()
  51. self._send("NICK {0}".format(self.nick))
  52. self._send("USER {0} {1} * :{2}".format(self.ident, self.host, self.realname))
  53. def _close(self):
  54. """Close our connection with the IRC server."""
  55. try:
  56. self._sock.shutdown(socket.SHUT_RDWR) # Shut down connection first
  57. except socket.error:
  58. pass # Ignore if the socket is already down
  59. self._sock.close()
  60. def _get(self, size=4096):
  61. """Receive (i.e. get) data from the server."""
  62. data = self._sock.recv(size)
  63. if not data:
  64. # Socket isn't giving us any data, so it is dead or broken:
  65. raise BrokenSocketException()
  66. return data
  67. def _send(self, msg):
  68. """Send data to the server."""
  69. with self._send_lock:
  70. self._sock.sendall(msg + "\r\n")
  71. self.logger.debug(msg)
  72. def say(self, target, msg):
  73. """Send a private message to a target on the server."""
  74. msg = "PRIVMSG {0} :{1}".format(target, msg)
  75. self._send(msg)
  76. def reply(self, data, msg):
  77. """Send a private message as a reply to a user on the server."""
  78. msg = "\x02{0}\x0f: {1}".format(data.nick, msg)
  79. self.say(data.chan, msg)
  80. def action(self, target, msg):
  81. """Send a private message to a target on the server as an action."""
  82. msg = "\x01ACTION {0}\x01".format(msg)
  83. self.say(target, msg)
  84. def notice(self, target, msg):
  85. """Send a notice to a target on the server."""
  86. msg = "NOTICE {0} :{1}".format(target, msg)
  87. self._send(msg)
  88. def join(self, chan):
  89. """Join a channel on the server."""
  90. msg = "JOIN {0}".format(chan)
  91. self._send(msg)
  92. def part(self, chan):
  93. """Part from a channel on the server."""
  94. msg = "PART {0}".format(chan)
  95. self._send(msg)
  96. def mode(self, chan, level, msg):
  97. """Send a mode message to the server."""
  98. msg = "MODE {0} {1} {2}".format(chan, level, msg)
  99. self._send(msg)
  100. def pong(self, target):
  101. """Pong another entity on the server."""
  102. msg = "PONG {0}".format(target)
  103. self._send(msg)
  104. def quit(self, msg=None):
  105. """Issue a quit message to the server."""
  106. if msg:
  107. self._send("QUIT :{0}".format(msg))
  108. else:
  109. self._send("QUIT")
  110. def loop(self):
  111. """Main loop for the IRC connection."""
  112. self._is_running = True
  113. read_buffer = ""
  114. while 1:
  115. try:
  116. read_buffer += self._get()
  117. except BrokenSocketException:
  118. self._is_running = False
  119. break
  120. lines = read_buffer.split("\n")
  121. read_buffer = lines.pop()
  122. for line in lines:
  123. self._process_message(line)
  124. if self.is_stopped():
  125. self._close()
  126. break
  127. def stop(self, msg=None):
  128. """Request the IRC connection to close at earliest convenience."""
  129. if self._is_running:
  130. self.quit(msg)
  131. self._is_running = False
  132. def is_stopped(self):
  133. """Return whether the IRC connection has been (or is to be) closed."""
  134. return not self._is_running