Well, you found me. Congratulations. Was it worth it?
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.

134 lines
3.8 KiB

3 months ago
  1. # Manage the IRC layer of GLaDOS
  2. import re, socket, ssl
  3. from functools import wraps
  4. from queue import Queue
  5. from threading import Thread
  6. class IRC(object):
  7. def __init__(self, host, port):
  8. """ Initialize an IRC wrapper """
  9. # Public attributes
  10. self.connected = False # Simple lock
  11. # Private attributes
  12. self._socket = ssl.create_default_context().wrap_socket(
  13. socket.create_connection((host, port)),
  14. server_hostname=host)
  15. self._inbox = Queue()
  16. self._handler = Thread(target=self._handle)
  17. self._callbacks = []
  18. # Public methods
  19. def start(self, nick, password):
  20. """ Start the IRC layer. Manage authentication as well """
  21. self._handler.start()
  22. self._send(f"USER {nick} * * :{nick}")
  23. self._send(f"NICK {nick}")
  24. self._waitfor(lambda m: "NOTICE" in m and "/AUTH" in m)
  25. self._send(f"AUTH {nick}:{password}")
  26. self._waitfor(lambda m: "You are now logged in" in m)
  27. self.connected = True
  28. def run(self):
  29. """ Handle new messages """
  30. while True:
  31. message = self.receive()
  32. for event, callback in self._callbacks:
  33. if event(message):
  34. callback(message)
  35. def send(self, target, message):
  36. """ Send a message to the specified target (channel or user) """
  37. self._send(f"PRIVMSG {target} :{message}")
  38. def receive(self):
  39. """ Receive a private message """
  40. while True:
  41. message = self._recv()
  42. print(f"receive: {message}")
  43. if " PRIVMSG " in message:
  44. return Message(message)
  45. def join(self, channel):
  46. """ Join a channel """
  47. self._send(f"JOIN {channel}")
  48. def on(self, event):
  49. """ Adds a callback to the IRC handler
  50. Event is a function taking in parameter a Message and returning
  51. True if the callback should be executed on the message """
  52. def callback(func):
  53. @wraps(func)
  54. def wrapper(message):
  55. func(message)
  56. self._callbacks.append((event, wrapper))
  57. return wrapper
  58. return callback
  59. # Private methods
  60. def _handle(self):
  61. """ Handle raw messages from irc and manage ping """
  62. while True:
  63. # Get incoming messages
  64. data = self._socket.recv(4096).decode()
  65. # Split multiple lines
  66. for m in data.split('\r\n'):
  67. # Manage ping
  68. if m.startswith("PING"):
  69. self._send(m.replace("PING", "PONG"))
  70. # Or add a new message to inbox
  71. elif len(m):
  72. self._inbox.put(m)
  73. print(f"_handle: <{m}>")
  74. def _send(self, raw):
  75. """ Wrap and encode raw message to send """
  76. self._socket.send(f"{raw}\r\n".encode())
  77. def _recv(self):
  78. m = self._inbox.get()
  79. return m
  80. def _waitfor(self, condition):
  81. """ Wait for a raw message that matches the condition """
  82. msg = self._recv()
  83. while not condition(msg):
  84. msg = self._recv()
  85. return msg
  86. class Message(object):
  87. r = re.compile("^:(?P<author>[\w.~]+)(?:!(?P<host>\S+))? PRIVMSG (?P<to>\S+) :(?P<text>.+)")
  88. def __init__(self, raw):
  89. match = re.search(Message.r, raw)
  90. self.author = match.group("author")
  91. self.to = match.group("to")
  92. self.text = match.group("text")
  93. print(self)
  94. def __str__(self):
  95. return f"<{self.author}> à <{self.to}> : {self.text}"
  96. if __name__ == "__main__":
  97. bot = IRC("irc.planet-casio.com", 6697)
  98. @bot.on(lambda m: "Hello" in m.text)
  99. def hello(msg):
  100. bot.send(msg.to, f"Nice to see you {msg.author}!")
  101. bot.start("glados", "abcdef123456")
  102. bot.join("#glados")
  103. bot.run()