Guest User

Untitled

a guest
Dec 9th, 2017
80
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 14.04 KB | None | 0 0
  1. """
  2. StarryPy IRC Plugin
  3.  
  4. Provides a mock IRC user that echos conversations between the game server and
  5. an IRC channel.
  6.  
  7. Original authors: AMorporkian
  8. Updated for release: kharidiron
  9. """
  10.  
  11. import re
  12. import asyncio
  13.  
  14. import irc3
  15.  
  16. from base_plugin import BasePlugin
  17. from utilities import ChatSendMode, ChatReceiveMode, link_plugin_if_available
  18.  
  19.  
  20. # Mock Objects
  21.  
  22. class MockPlayer:
  23. """
  24. A mock player object for command passing.
  25.  
  26. We have to make it 'Mock' because there are all sorts of things in the
  27. real Player object that don't map correctly, and would cause all sorts
  28. of headaches.
  29. """
  30. name = "IRCBot"
  31. logged_in = True
  32.  
  33. def __init__(self):
  34. self.granted_perms = set()
  35. self.revoked_perms = set()
  36. self.permissions = set()
  37. self.priority = 0
  38. self.name = "MockPlayer"
  39. self.alias = "MockPlayer"
  40.  
  41. def perm_check(self, perm):
  42. if not perm:
  43. return True
  44. elif "special.allperms" in self.permissions:
  45. return True
  46. elif perm.lower() in self.revoked_perms:
  47. return False
  48. elif perm.lower() in self.permissions:
  49. return True
  50. else:
  51. return False
  52.  
  53. class MockConnection:
  54. """
  55. A mock connection object for command passing.
  56. """
  57. def __init__(self, owner):
  58. self.owner = owner
  59. self.player = MockPlayer()
  60.  
  61. @asyncio.coroutine
  62. def send_message(self, *messages):
  63. for message in messages:
  64. color_strip = re.compile("\^(.*?);")
  65. message = color_strip.sub("", message)
  66. yield from self.owner.bot_write(message)
  67. return None
  68.  
  69.  
  70. # IRC Formatting Functions
  71.  
  72. def _base_cc(text, start_code, end_code=15):
  73. if end_code is None:
  74. end_code = start_code
  75. return chr(start_code) + text + chr(end_code)
  76.  
  77.  
  78. def _color(text, color="00", background=""):
  79. return _base_cc(color + background + text, 3)
  80.  
  81.  
  82. def _bold(text):
  83. return _base_cc(text, 2)
  84.  
  85.  
  86. def _italic(text):
  87. return _base_cc(text, 29)
  88.  
  89.  
  90. def _underline(text):
  91. return _base_cc(text, 21)
  92.  
  93.  
  94. def _strikethrough(text):
  95. return _base_cc(text, 19)
  96.  
  97.  
  98. def _underline2(text):
  99. return _base_cc(text, 31)
  100.  
  101.  
  102. def _reverse(text):
  103. return _base_cc(text, 21)
  104.  
  105.  
  106. ###
  107.  
  108. class IRCPlugin(BasePlugin):
  109. name = "irc_bot"
  110. depends = ['command_dispatcher']
  111. default_config = {
  112. "server": "irc.freenode.net",
  113. "channel": "#starrypy",
  114. "username": "starrypy3k_bot",
  115. "strip_colors": True,
  116. "log_irc": False,
  117. "announce_join_leave": True,
  118. "command_prefix": "!",
  119. "user_rank": "Guest",
  120. "operator_rank": "Admin"
  121. }
  122.  
  123. def __init__(self):
  124. super().__init__()
  125. self.server = None
  126. self.channel = None
  127. self.username = None
  128. self.connection = None
  129. self.prefix = None
  130. self.command_prefix = None
  131. self.dispatcher = None
  132. self.bot = None
  133. self.ops = None
  134. self.color_strip = re.compile("\^(.*?);")
  135. self.sc = None
  136. self.discord_active = False
  137. self.discord = None
  138. self.chat_manager = None
  139. self.allowed_commands = ('who', 'help', 'uptime', 'motd', 'show_spawn',
  140. 'ban', 'unban', 'kick', 'list_bans', 'mute',
  141. 'unmute', 'set_motd', 'whois', 'broadcast',
  142. 'user', 'del_player', 'maintenance_mode',
  143. 'shutdown', 'save')
  144. def activate(self):
  145. super().activate()
  146. self.dispatcher = self.plugins.command_dispatcher
  147. self.prefix = self.config.get_plugin_config("command_dispatcher")[
  148. "command_prefix"]
  149. self.command_prefix = self.config.get_plugin_config(self.name)[
  150. "command_prefix"]
  151. self.server = self.config.get_plugin_config(self.name)["server"]
  152. self.channel = self.config.get_plugin_config(self.name)["channel"]
  153. self.username = self.config.get_plugin_config(self.name)["username"]
  154. self.password = self.config.get_plugin_config(self.name)["password"]
  155. self.port = self.config.get_plugin_config(self.name)["port"]
  156. self.sc = self.config.get_plugin_config(self.name)["strip_colors"]
  157.  
  158. self.bot = irc3.IrcBot(nick=self.username,
  159. autojoins=[self.channel],
  160. host=self.server,
  161. password=self.password,
  162. port=self.port)
  163. self.bot.log = self.logger
  164.  
  165. self.bot.include("irc3.plugins.core")
  166. self.bot.include("irc3.plugins.userlist")
  167.  
  168. x = irc3.event(irc3.rfc.PRIVMSG, self.forward)
  169. x.compile(None)
  170. y = irc3.event(r"^:\S+ 353 [^&#]+(?P<channel>\S+) :(?P<nicknames>.*)",
  171. self.name_check)
  172. y.compile(None)
  173. z = irc3.event(irc3.rfc.JOIN_PART_QUIT, self.announce_irc_join)
  174. z.compile(None)
  175. self.bot.attach_events(x)
  176. self.bot.attach_events(y)
  177. self.bot.attach_events(z)
  178. self.bot.create_connection()
  179.  
  180. self.discord_active = link_plugin_if_available(self, 'discord_bot')
  181. if self.discord_active:
  182. self.discord = self.plugins['discord_bot']
  183. if link_plugin_if_available(self, "chat_manager"):
  184. self.chat_manager = self.plugins['chat_manager']
  185.  
  186. self.ops = set()
  187. self.connection = MockConnection(self)
  188. asyncio.ensure_future(self.update_ops())
  189.  
  190. # Packet hooks - look for these packets and act on them
  191.  
  192. def on_connect_success(self, data, connection):
  193. """
  194. Hook on bot successfully connecting to server.
  195.  
  196. :param data:
  197. :param connection:
  198. :return: Boolean: True. Must be true, so packet moves on.
  199. """
  200. asyncio.ensure_future(self.announce_join(connection))
  201. return True
  202.  
  203. def on_client_disconnect_request(self, data, connection):
  204. """
  205. Hook on bot disconnecting from the server.
  206.  
  207. :param data:
  208. :param connection:
  209. :return: Boolean: True. Must be true, so packet moves on.
  210. """
  211. asyncio.ensure_future(self.announce_leave(connection.player))
  212. return True
  213.  
  214. def on_chat_sent(self, data, connection):
  215. """
  216. Hook on message being broadcast on server. Display it in IRC.
  217.  
  218. If 'sc' is True, colors are stripped from game text. e.g. -
  219.  
  220. ^red;Red^reset; Text -> Red Text.
  221.  
  222. :param data:
  223. :param connection:
  224. :return: Boolean: True. Must be true, so packet moves on.
  225. """
  226. if not data["parsed"]["message"].startswith(self.prefix):
  227. msg = data["parsed"]["message"]
  228. if self.sc:
  229. msg = self.color_strip.sub("", msg)
  230.  
  231. if data["parsed"]["send_mode"] == ChatSendMode.UNIVERSE:
  232. if self.chat_manager:
  233. if not self.chat_manager.mute_check(connection.player):
  234. asyncio.ensure_future(self.bot_write("<{}> {}".format(
  235. connection.player.alias, msg)))
  236. return True
  237.  
  238. # Helper functions - Used by commands
  239.  
  240. def forward(self, mask, event, target, data):
  241. """
  242. Catch a message that is sent as a private message to the bot. If the
  243. message begins with a '.', treat it as a server command. Otherwise,
  244. treat the message as normal text, and pass it along to the server.
  245.  
  246. :param mask: Chat mask.
  247. :param event: Unused.
  248. :param target: Chat target.
  249. :param data: Message body.
  250. :return: None
  251. """
  252. if data[0] == self.command_prefix:
  253. asyncio.ensure_future(self.handle_command(target, data[1:], mask))
  254. elif target.lower() == self.channel.lower():
  255. nick = mask.split("!")[0]
  256. asyncio.ensure_future(self.send_message(data, nick))
  257. return None
  258.  
  259. @asyncio.coroutine
  260. def announce_irc_join(self, mask, event, channel, data):
  261. if self.config.get_plugin_config(self.name)["announce_join_leave"]:
  262. nick = mask.split("!")[0]
  263. if event == "JOIN":
  264. move = "has joined"
  265. else:
  266. move = "has left"
  267. if self.config.get_plugin_config(self.name)["log_irc"]:
  268. self.logger.info("{} has {} the channel.".format(nick, move))
  269. if self.discord_active:
  270. asyncio.ensure_future(self.discord.bot_write("[IRC] **{}** has"
  271. " {} the IRC "
  272. "channel."
  273. .format(nick,
  274. move)))
  275. yield from self.factory.broadcast("[^orange;IRC^reset;] {} has "
  276. "{} the channel.".format(nick,
  277. move),
  278. mode=ChatReceiveMode.BROADCAST)
  279. return None
  280.  
  281. def name_check(self, channel=None, nicknames=None):
  282. """
  283. Build a list of the Ops currently in the IRC channel... I think?
  284.  
  285. :param channel: Unused.
  286. :param nicknames:
  287. :return: Null
  288. """
  289. self.ops = set(
  290. [nick[1:] for nick in nicknames.split() if nick[0] == "@"])
  291.  
  292. @asyncio.coroutine
  293. def send_message(self, data, nick):
  294. """
  295. Broadcast a message on the server.
  296.  
  297. :param data: The message to be broadcast.
  298. :param nick: The person sending the message from IRC.
  299. :return: Null
  300. """
  301. message = data
  302. if message[0] == "\x01":
  303. # CTCP Event
  304. if message.split()[0] == "\x01ACTION":
  305. # CTCP Action - e. g. '/me does a little dance'
  306. message = " ".join(message.split()[1:])[:-1]
  307. # Strip the CTCP metadata from the beginning and end
  308. # Format it like a /me is in IRC
  309.  
  310. if self.config.get_plugin_config(self.name)["log_irc"]:
  311. self.logger.info(" -*- " + nick + " " + message)
  312. if self.discord_active:
  313. asyncio.ensure_future(self.discord.bot_write(
  314. "-*- {} {}".format(nick, message)))
  315. yield from self.factory.broadcast(
  316. "< ^orange;IRC^reset; > ^green;-*- {} {}".format(nick,
  317. message),
  318. mode=ChatReceiveMode.BROADCAST)
  319. else:
  320. if self.config.get_plugin_config(self.name)["log_irc"]:
  321. self.logger.info("<" + nick + "> " + message)
  322. if self.discord_active:
  323. asyncio.ensure_future(self.discord.bot_write(
  324. "[IRC] **<{}>** {}".format(nick, message)))
  325. yield from self.factory.broadcast("[^orange;IRC^reset;] <{}> "
  326. "{}".format(nick, message),
  327. mode=ChatReceiveMode.BROADCAST)
  328.  
  329. @asyncio.coroutine
  330. def announce_join(self, connection):
  331. """
  332. Send a message to IRC when someone joins the server.
  333.  
  334. :param connection: Connection of connecting player on server.
  335. :return: Null.
  336. """
  337. yield from asyncio.sleep(1)
  338. yield from self.bot_write(
  339. "{} has joined the server.".format(_color(_bold(
  340. connection.player.alias), "10")))
  341.  
  342. @asyncio.coroutine
  343. def announce_leave(self, player):
  344. """
  345. Send a message to IRC when someone leaves the server.
  346.  
  347. :param player: Player leaving server.
  348. :return: Null.
  349. """
  350. yield from self.bot_write(
  351. "{} has left the server.".format(_color(_bold(
  352. player.alias), "10")))
  353.  
  354. @asyncio.coroutine
  355. def bot_write(self, msg, target=None):
  356. """
  357. Method for writing messages to IRC channel.
  358.  
  359. :param msg: Message to be posted.
  360. :param target: Channel where message should be posted.
  361. :return: Null.
  362. """
  363. if target is None:
  364. target = self.channel
  365. self.bot.privmsg(target, msg)
  366.  
  367. @asyncio.coroutine
  368. def handle_command(self, target, data, mask):
  369. """
  370. Handle commands that have been sent in via IRC.
  371.  
  372. :param target: Channel where command should be posted.
  373. :param data: Packet containing the command data.
  374. :param mask:
  375. :return: Null.
  376. """
  377. split = data.split()
  378. command = split[0]
  379. to_parse = split[1:]
  380. user = mask.split("!")[0]
  381. usr_rank = self.plugin_config.user_rank
  382. op_rank = self.plugin_config.operator_rank
  383. self.connection.player.permissions = \
  384. self.plugins.player_manager.ranks[usr_rank.lower()]["permissions"]
  385. self.connection.player.priority = self.plugins.player_manager.ranks[
  386. usr_rank.lower()]["priority"]
  387. if user in self.ops:
  388. self.connection.player.permissions = \
  389. self.plugins.player_manager.ranks[op_rank.lower()][
  390. "permissions"]
  391. self.connection.player.priority = \
  392. self.plugins.player_manager.ranks[op_rank.lower()]["priority"]
  393. self.connection.player.alias = user
  394. self.connection.player.name = user
  395. if command in self.dispatcher.commands:
  396. # Only handle commands that work from IRC
  397. if command in self.allowed_commands:
  398. yield from self.dispatcher.run_command(command,
  399. self.connection,
  400. to_parse)
  401. else:
  402. yield from self.bot_write("Command not handled by IRC.")
  403. else:
  404. yield from self.bot_write("Command not found.")
  405.  
  406. @asyncio.coroutine
  407. def update_ops(self):
  408. """
  409. Update the list of Ops. Maybe? Really not sure...
  410.  
  411. :return: Null.
  412. """
  413. while True:
  414. yield from asyncio.sleep(6)
  415. self.bot.send("NAMES {}".format(self.channel))
Add Comment
Please, Sign In to add comment