event.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. import re
  2. class Event:
  3. """
  4. Allows event type definition. The definition accepts a regex.
  5. Every event can be triggered by specific lines, messages, message_id or users.
  6. Eventually (see time_event branch for proof-of-concept implementation) time-sensitive events will be triggerable as well.
  7. Each line received by the bot is passed to each module in the modules_list. If the module determines the line matches what the event cares about,
  8. the event calls each of its subscribers itself, which contains all the information the module needs to respond appropriately.
  9. To use:
  10. .. code-block:: python
  11. e = Event("__my_type__")
  12. e.define("some_regex")
  13. bot.register_event(e, calling_module)
  14. """
  15. def __init__(self, _type):
  16. """
  17. Define your own type here. Make sure if you're making a broad event (all messages, for example) you use a sane type, as other modules that care about this kind of event can subscribe to it.
  18. Args:
  19. _type: string. like "__youtube__" or "__weather__". Underscores are a convention.
  20. """
  21. self._type = _type
  22. self.subscribers = list() # this is a list of subscribers to notify
  23. self.user = ""
  24. self.definition = ""
  25. self.msg_definition = ""
  26. self.user_definition = ""
  27. self.channel = ""
  28. self.line = ""
  29. self.msg = ""
  30. self.verb = ""
  31. self.is_pm = False
  32. self.message_id = -1
  33. self.time_event = False
  34. def subscribe(self, e):
  35. """
  36. Append passed-in event to our list of subscribing modules.
  37. Args:
  38. e: event.
  39. """
  40. self.subscribers.append(e)
  41. def define(self, definition=None, msg_definition=None, user_definition=None, message_id=None, time_event=False):
  42. """
  43. Define ourself by general line (definition), msg_definition (what someone says in a channel or PM), user_definition (the user who said the thing), or message_id (like 376 for MOTD or 422 for no MOTD)
  44. Currently, an event is defined by only one type of definition. If one were to remove the returns after each self. set, an event could be defined and triggered by any of several definitions.
  45. Args:
  46. definition: string. regex allowed.
  47. msg_definition: string. regex allowed. this is what someone would say in a channel. like "hello, pybot".
  48. user_definition: string. the user that said the thing. like 'hlmtre' or 'BoneKin'.
  49. message_id: the numerical ID of low-level IRC protocol stuff. 376, for example, tells clients 'hey, this is the MOTD.'
  50. time_event: boolean. if this is a timed event, the other definitions are irrelevant.
  51. """
  52. if time_event is not False:
  53. self.time_event = True
  54. return
  55. if definition is not None:
  56. self.definition = definition
  57. return
  58. if msg_definition is not None:
  59. self.msg_definition = msg_definition
  60. return
  61. if user_definition is not None:
  62. self.user_definition = user_definition
  63. return
  64. if message_id is not None:
  65. self.message_id = message_id
  66. return
  67. def matches(self, line):
  68. """
  69. Fills out the event object per line, and returns True or False if the line matches one of our definitions.
  70. Args:
  71. line: string. The entire incoming line.
  72. Return:
  73. boolean; True or False.
  74. """
  75. # perhaps TODO
  76. # first try very simply
  77. if len(self.definition) and self.definition in line:
  78. return True
  79. # grab message id. not always present
  80. try:
  81. temp = line.split(":")[1].split(" ")[1]
  82. except IndexError:
  83. pass
  84. try:
  85. message_id = int(temp)
  86. except (ValueError, UnboundLocalError):
  87. message_id = 0
  88. try:
  89. msg = line.split(":",2)[2]
  90. except IndexError:
  91. return
  92. if len(self.msg_definition):
  93. if re.search(self.msg_definition, msg):
  94. return True
  95. if len(self.definition):
  96. if re.search(self.definition, line):
  97. return True
  98. if len(self.user_definition):
  99. if len(line) and "PRIVMSG" in line > 0:
  100. line_array = line.split()
  101. user_and_mask = line_array[0][1:]
  102. user = user_and_mask.split("!")[0]
  103. if self.user_definition == user:
  104. return True
  105. if type(self.message_id) is int:
  106. if self.message_id == message_id:
  107. return True
  108. return False
  109. def time_notify_subscribers(self):
  110. for s in self.subscribers:
  111. # horrible kludge. this feels so dirty.
  112. if s.time_since >= int(round(s.time_delta/2) - 1):
  113. s.time_since = 0
  114. s.handle(self)
  115. else:
  116. s.time_since += 1
  117. def notifySubscribers(self, line):
  118. """
  119. Fills out the object with all necessary information, then notifies subscribers with itself (an event with all the line information parsed out) as an argument.
  120. Args:
  121. line: string
  122. """
  123. self.line = line
  124. self.user = line.split(":")[1].rsplit("!")[0] # nick is first thing on line
  125. if "JOIN" in line or "QUIT" in line:
  126. self.user = line.split("!")[0].replace(":","")
  127. try:
  128. temp = line.split(":")[1].split(" ")[1]
  129. except IndexError:
  130. pass
  131. try:
  132. self.msg = line.split(":",2)[2]
  133. except IndexError:
  134. self.msg = ""
  135. l = line.split()
  136. self.channel = ""
  137. self.verb = ""
  138. ind = 0
  139. privmsg_index = 0
  140. for e in l:
  141. ind+=1
  142. if e == "PRIVMSG":
  143. privmsg_index = ind
  144. if e.startswith("#"):
  145. self.channel = e
  146. break
  147. for v in l:
  148. if v in ["JOIN","PART","QUIT","NICK","KICK","PRIVMSG","TOPIC", "NOTICE", "PING", "PONG", "MODE"]:
  149. self.verb = v
  150. break
  151. # our channel is the next one from PRIVMSG
  152. if self.verb == "PRIVMSG" and not l[privmsg_index].startswith("#"):
  153. self.is_pm = True
  154. for s in self.subscribers:
  155. s.handle(self)