__init__.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. #! /usr/bin/python3
  2. import json
  3. import threading
  4. import queue
  5. import time
  6. import os
  7. import pdb
  8. import re
  9. import select
  10. import signal
  11. import socket
  12. import sys
  13. import traceback
  14. import logging, logging.handlers
  15. import myLogger
  16. import unixsocket
  17. def signal_handler(signum,frame):
  18. if signum == signal.SIGUSR1:
  19. pdb.set_trace()
  20. signal.signal(signal.SIGUSR1,signal_handler)
  21. PDB = False
  22. # PDB = True
  23. # from .irc_numeric_name import *
  24. # https://tools.ietf.org/html/rfc2812#section-2.3.1
  25. p_chanstring = r"[^\x00\x07\r\n ,:]"
  26. p_channelid = r"[a-z0-9]{5}"
  27. p_user = r"[^\x00\r\n @]+"
  28. p_key = r"[\x01-05\x07-08\x0C\x0E-F1\x21-7F]{1,23}"
  29. p_letter = r"[a-zA-Z0-9]"
  30. p_digit = r"[0-9]"
  31. p_hexdigit = r"("+p_digit+r")|[A-F]"
  32. # p_special = r"[\x5B\x60]"#\x7B-7D]"
  33. # p_special = r"[\[\],{}`\\_^{|}]"
  34. p_special = r"[]{}|,`_^\\]|\["
  35. p_nospcrlfcl = r"[^\x00\x07\r\n :]"
  36. # p_nospcrlfcl = r"[^\x00\x07\r\n]"
  37. p_wildone = r"\x3F"
  38. p_wildmany = r"\x2A"
  39. p_nowild = r"[\x01-29\x2B-3E\x40-FF]"
  40. p_noesc = r"[\x01-5B\x5D-FF]"
  41. p_nowild = r"[^\x3F\x2A]"
  42. p_noesc = r"[^\x00\\]"
  43. p_mask = r"(("+p_nowild+r")|("+p_noesc+p_wildone+r")|("+p_noesc+p_wildmany+r"))*"
  44. p_targetmask = r"\$|#"+p_mask
  45. p_channel = r"(#|\+|(!"+p_channelid+r")|&)("+p_chanstring+r")+(:("+p_chanstring+r")+)?"
  46. # p_nickname = r"("+p_letter+r")|("+p_special+r")(("+p_letter+r")|("+p_digit+r")|("+p_special+r")){0,8}"
  47. # p_nickname = r"(("+p_letter+r")|("+p_special+r"))(("+p_letter+r")|("+p_digit+r")|("+p_special+r")){1,8}"
  48. p_nickname = r"(("+p_letter+r")|("+p_special+r"))(("+p_letter+r")|("+p_digit+r")|("+p_special+r"))+"
  49. p_shortname = r"(("+p_letter+r")|("+p_digit+r"))"\
  50. + r"(("+p_letter+r")|("+p_digit+r")|(-))*"\
  51. + r"(("+p_letter+r")|("+p_digit+r"))*"
  52. p_shortname = r"("+p_letter+r"|"+p_digit+r")"\
  53. + r"("+p_letter+r"|"+p_digit+r"|-)*"\
  54. + r"("+p_letter+r"|"+p_digit+r")*"
  55. p_shortname = r"("+p_letter+r"|"+p_digit+r")"\
  56. + r"(("+p_letter+r")|("+p_digit+r")|(-))*"\
  57. + r"(("+p_letter+r")|("+p_digit+r"))*"
  58. p_hostname = r"("+p_shortname+r")(\."+p_shortname+r")*" # dot
  59. p_servername = p_hostname
  60. p_ip4addr = r"("+p_digit+r"){1,3}\."+r"("+p_digit+r"){1,3}\."+r"("+p_digit+r"){1,3}\."+r"("+p_digit+r"){1,3}"
  61. p_ip6addr = r"(("+p_hexdigit+r")+(:("+p_hexdigit+r")+){7})"\
  62. + r"|(0:0:0:0:0:(0|(FFFF)):"+p_ip4addr+r")"
  63. p_hostaddr = r"("+p_ip4addr+r")|("+p_ip6addr+r")"
  64. p_host = r"("+p_hostname+r")|("+p_hostaddr+r")"
  65. p_middle = p_nospcrlfcl+r"((:)|("+p_nospcrlfcl+r"))*"
  66. p_trailing = r"((:)|( )|("+p_nospcrlfcl+r"))*"
  67. # p_trailing = r"((:)|( )|("+p_nospcrlfcl+r"))+"
  68. p_params = r"(( "+p_middle+r"){0,13}( :"+p_trailing+r"))|(( "+p_middle+r"){14}( :?"+p_trailing+r"))"
  69. # p_params = r"(( "+p_middle+r"){0,13}( :"+p_trailing+r")?)|(( "+p_middle+r"){14}( :?"+p_trailing+r")?)"
  70. p_command = r"("+p_letter+r")+|(\d){3}"
  71. p_prefix_client = p_nickname+r"((!"+p_user+r")?@"+p_host+r")?"
  72. p_prefix = r"("+p_servername+r")|("+p_prefix_client+r")"
  73. p_message = r"(:"+p_prefix+r" )?"+p_command+r"("+p_params+r")?"
  74. p_target = r"("+p_nickname+r")|("+p_servername+r")" #p_server
  75. p_msgto = r"("+p_channel+r")"\
  76. +r"|("+p_user+r"(%"+p_host+r")?@"+p_servername+r")"\
  77. +r"|("+p_user+r"%"+p_host+r")"\
  78. +r"|("+p_targetmask+r")"\
  79. +r"|("+p_nickname+r")"\
  80. +r"|("+p_nickname+r"!"+p_user+r"@"+p_host+r")"
  81. p_msgtarget = p_msgto+r"(,"+p_msgto+r")*"
  82. # "server":re.compile(
  83. # r":(?P<servername>"+p_servername+r")"+
  84. # r" "+
  85. # r"(?P<command>"+p_command+r")"+
  86. # r"(?P<params>"+p_params+r")"
  87. # ),
  88. # "client":re.compile(
  89. # r":(?P<identity>"+
  90. # r"(?P<nick>"+p_nickname+r")"+
  91. # r"!(?P<user>"+p_user+r")"+
  92. # r"@(?P<host>"+p_host+r")"+
  93. # r")"+
  94. # r" "+
  95. # r"(?P<command>"+p_command+r")"+
  96. # # r"(\s?:?)"+
  97. # r"(?P<params>"+p_params+r")"
  98. # ),
  99. class IRCClient(unixsocket.Client):
  100. def __init__(self, *args, **kwargs):
  101. self.super_object = super(IRCClient, self)
  102. self.super_object.__init__(*args, block_size = 2048, **kwargs)
  103. # all item in this dict are searched for in every message
  104. # until the item is found the value defaults to None. when found, the message is the value
  105. self.search_items = {}
  106. self.pattern = {
  107. "full_message":re.compile(
  108. r"(:"+
  109. r"(?P<identity>"+
  110. r"(?P<nick>"+p_nickname+r")"+
  111. r"("+
  112. r"(!(?P<user>"+p_user+r"))?"+
  113. r"@(?P<host>"+p_host+r") ?"+
  114. r")?"+
  115. r")"+
  116. r"|(?P<servername>"+p_servername+r")"+
  117. r" )?"+
  118. r"(?P<command>"+p_command+r")"+
  119. r"(?P<params>"+p_params+r")?"
  120. ),
  121. "chat":re.compile(
  122. # r" ("+p_nickname+r")|("+p_channel+r") :("+p_nickname+r": )?("+p_trailing+r")"
  123. r"(?P<target>("+p_channel+r")|("+p_nickname+r")) :"+
  124. r"("+
  125. r"(?P<highlight>"+p_nickname+r"): ?"+
  126. r"(?P<msg_command>[:,][a-zA-Z0-9_-]+)? ?"
  127. r")?"+
  128. r"(?P<rest>.*)"
  129. ),
  130. }
  131. # this dict holds client information
  132. self.client_info = {
  133. "user":None,
  134. "hostname":None, #!!!
  135. "servername":None, #!!!
  136. "realname":"irc bot",
  137. "identity":None,
  138. "pass_auth":False,
  139. "nickserv_auth":False,
  140. "current_nick":None,
  141. "last_nick":None,
  142. "server_name":None, #!!
  143. "connection_address":None,
  144. }
  145. self.client_info.update({
  146. "user":"baum",
  147. "hostname":"digital",
  148. "servername":"omnimaga",
  149. "realname":"irc bot",
  150. "pass_auth":False,
  151. "nickserv_auth":False,
  152. "startup_chans":["#thebot", "#digital"],
  153. "chan_hello":"heyho, I'm a bot",
  154. "server_name":self.host,
  155. "connection_address":self.host
  156. })
  157. self.config = {
  158. "shown_message_types":["numeric","private","client","PING","unknown"],
  159. "nicks":["kevin","clemens","kevin_","kevin__"],
  160. }
  161. self.__private_config = {
  162. "pass_password":"pass_pwd",
  163. "nickserv_password":"nickserv_pwd"
  164. }
  165. self.server_info = {
  166. "names":{#"#chan":[".user1",".user2"]
  167. },
  168. }
  169. ## Events
  170. self.registered_event = threading.Event()
  171. def handshake(self):
  172. nickname = self.config["nicks"][0]
  173. nick_not_accepted_msg = " 433 * "+nickname+" :Nickname is already in use."
  174. # welcome message, no MOTD, MOTD end, nickname already taken
  175. self.add_search_item(" 001 "," 422 "," 376 ", nick_not_accepted_msg)
  176. time.sleep(0.5)
  177. if self.client_info["pass_auth"]:
  178. auth_pass(self)
  179. self.send("NICK "+nickname)
  180. self.send("USER {identity} {hostname} {servername} {realname}\r\n".format(**self.client_info))
  181. self.add_search_item(nick_not_accepted_msg," 001 ")
  182. nicks = iter(self.config["nicks"][1:])
  183. while True:
  184. if self.search_items[nick_not_accepted_msg]:
  185. del self.search_items[nick_not_accepted_msg]
  186. try:
  187. nickname = next(nicks)
  188. except StopIteration:
  189. self.stop()
  190. nick_not_accepted_msg = " 433 * "+nickname+" :Nickname is already in use."
  191. self.search_items[nick_not_accepted_msg] = None
  192. self.send("NICK "+nickname)
  193. elif self.search_items[" 001 "]:
  194. self.client_info["current_nick"] = nickname
  195. self.client_info["server_name"] = self.search_items[" 001 "].split()[0].lstrip(":")
  196. break
  197. self.logger.debug(self.client_info["server_name"])
  198. self.registered_event.set()
  199. # self.wait_for_item(" 422 "," 376 ")
  200. if self.client_info["nickserv_auth"]:
  201. auth_nickserv(self)
  202. self.join_chans(*self.client_info["startup_chans"],message=self.client_info["chan_hello"])
  203. time.sleep(0.1)
  204. self.send("MODE kevin +R")
  205. def connect(self):
  206. handshake_thread = threading.Thread(target=self.handshake, name="handshake", daemon=True)
  207. # calling connect function of the superclass
  208. self.super_object.connect()
  209. # start handshake thread
  210. handshake_thread.start()
  211. def handle_message(self, message, message_data, *args, **kwargs):
  212. pass
  213. def handle_data(self, data):
  214. for message in data.split("\r\n"):
  215. # skip the message if it is empty
  216. message = message.strip()
  217. if not message:
  218. continue
  219. # search for every item in self.search_items
  220. self.search_in(message)
  221. # extract information
  222. message_data = self.extract_message(message)
  223. # log message
  224. if message_data["known"]:
  225. self.logger.info(message)
  226. print(json.dumps(message_data,indent=4))
  227. else:
  228. self.logger.info(message)
  229. print("\033[31;1m"+json.dumps(message_data,indent=4)+"\033[0m")
  230. # answer PINGs
  231. if message.find("PING ") == 0:
  232. self.send("PONG :"+"".join(message.split(":")[1:]))
  233. self.handle_message(message,message_data)
  234. # if self.registered_event.is_set():
  235. # pass
  236. def extract_message(self, message):
  237. message_data = {}
  238. # message_data["type"]="\033[31;1m"+"unknown"+"\033[0m"
  239. try:
  240. full_msg_match = self.pattern["full_message"].search(message)
  241. message_data["full_msg_match"] = str(full_msg_match)
  242. if not full_msg_match:
  243. message_data["known"]=False
  244. return message_data
  245. message_data.update(full_msg_match.groupdict())
  246. message_data["known"] = True
  247. # message_data["split"] = message.split()
  248. message_data["params"] = message_data["params"].lstrip()
  249. # command specific information
  250. # if message_data["command"] in ["PRIVMSG", "NOTICE"]:
  251. # chat_match=self.pattern["chat"].search(message_data["params"])
  252. # message_data.update(chat_match.groupdict())
  253. # message_data["private_msg"] = message_data["target"] == self.client_info["current_nick"]
  254. # elif message_data["command"] in ["PART","QUIT"]:
  255. # message_data["target"],message_data["msg"] = message_data["split"]
  256. # chat_match=self.pattern["chat"].search(message_data["params"])
  257. # self.logger.info(chat_match.groups())
  258. # message_data.update(chat_match.groupdict())
  259. except Exception as e:
  260. self.logger.error(message)
  261. self.logger.error(e, exc_info=True)
  262. finally:
  263. return message_data
  264. # search functions:
  265. def search_in(self, text):
  266. for item in self.search_items:
  267. if text.find(item) != -1:
  268. self.search_items[item] = text
  269. def add_search_item(self, *args):
  270. for item in args:
  271. self.search_items[item] = self.search_items.get(item, None)
  272. def remove_search_item(self, *args):
  273. for item in args:
  274. del self.search_items[item]
  275. def wait_for_item(self, *args,timeout=0):
  276. args = list(args)
  277. new_items = set(args) - set(self.search_items)
  278. self.add_search_item(*new_items)
  279. end_time = time.time() + timeout
  280. while not self.exit_event.is_set():
  281. for item in args:
  282. if self.search_items[item] != None:
  283. self.logger.debug("item found: " + self.search_items[item])
  284. return item
  285. time.sleep(0.1)
  286. if timeout:
  287. if time.time() >= end_time:
  288. return False
  289. # irc functions
  290. def change_nick(self, nickname):
  291. self.send("NICK "+nickname)
  292. # self.client_info["last_nick"] = self.client_info["current_nick"]
  293. # self.client_info["current_nick"] = nickname
  294. def join_chans(self, *chans, message=None):
  295. for c in chans:
  296. self.send("JOIN " + c)
  297. if message:
  298. self.chat(c, message)
  299. def chat(self, target, message):
  300. self.send("PRIVMSG {target} {message}".format(target=target,message=message))
  301. class PluginHandler(object):
  302. """docstring for IRCPluginHandler"""
  303. def __init__(self, client_object, config_str=None, config_file=None):
  304. self.super_object = super(PluginHandler,self)
  305. self.super_object.__init__(client_object)
  306. self.client_object = client_object
  307. self.plugin_objects = {}
  308. self.config_dict = {}
  309. def handle_message(self,message,message_data):
  310. print(message)
  311. class PluginBase(object):
  312. def __init__(self):
  313. self.super_object = super(PluginBase, self)
  314. self.super_object.__init()
  315. def handle():
  316. pass
  317. class IRCPluginHandler(PluginHandler):
  318. def __init__(self,irc_client):
  319. self.super_object = super(IRCPluginHandler,self)
  320. self.super_object.__init__(irc_client)
  321. self.irc_client = irc_client