Advertisement
Guest User

Untitled

a guest
Oct 22nd, 2016
99
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 6.62 KB | None | 0 0
  1. import _ from 'lodash';
  2. import irc from 'irc';
  3. import logger from 'winston';
  4. import discord from 'discord.js';
  5. import { ConfigurationError } from './errors';
  6. import { validateChannelMapping } from './validators';
  7.  
  8. const REQUIRED_FIELDS = ['server', 'nickname', 'channelMapping', 'discordToken'];
  9. const NICK_COLORS = ['light_blue', 'dark_blue', 'light_red', 'dark_red', 'light_green',
  10. 'dark_green', 'magenta', 'light_magenta', 'orange', 'yellow', 'cyan', 'light_cyan'];
  11.  
  12. /**
  13. * An IRC bot, works as a middleman for all communication
  14. * @param {object} options - server, nickname, channelMapping, outgoingToken, incomingURL
  15. */
  16. class Bot {
  17. constructor(options) {
  18. REQUIRED_FIELDS.forEach((field) => {
  19. if (!options[field]) {
  20. throw new ConfigurationError(`Missing configuration field ${field}`);
  21. }
  22. });
  23.  
  24. validateChannelMapping(options.channelMapping);
  25.  
  26. this.discord = new discord.Client({ autoReconnect: true });
  27.  
  28. this.server = options.server;
  29. this.nickname = options.nickname;
  30. this.ircOptions = options.ircOptions;
  31. this.discordToken = options.discordToken;
  32. this.commandCharacters = options.commandCharacters || [];
  33. this.ircNickColor = options.ircNickColor !== false; // default to true
  34. this.channels = _.values(options.channelMapping);
  35.  
  36. this.channelMapping = {};
  37.  
  38. // Remove channel passwords from the mapping and lowercase IRC channel names
  39. _.forOwn(options.channelMapping, (ircChan, discordChan) => {
  40. this.channelMapping[discordChan] = ircChan.split(' ')[0].toLowerCase();
  41. });
  42.  
  43. this.invertedMapping = _.invert(this.channelMapping);
  44. this.autoSendCommands = options.autoSendCommands || [];
  45. }
  46.  
  47. connect() {
  48. logger.debug('Connecting to IRC and Discord');
  49. this.discord.login(this.discordToken);
  50.  
  51. const ircOptions = {
  52. userName: this.nickname,
  53. realName: this.nickname,
  54. channels: this.channels,
  55. floodProtection: true,
  56. floodProtectionDelay: 500,
  57. retryCount: 10,
  58. // webirc stuff
  59. webirc = {
  60. ip: '255.255.255.255',
  61. host: 'services.',
  62. password: 'password',
  63. }
  64. ...this.ircOptions
  65. };
  66.  
  67. this.ircClient = new irc.Client(this.server, this.nickname, ircOptions);
  68. this.attachListeners();
  69. }
  70.  
  71. attachListeners() {
  72. this.discord.on('ready', () => {
  73. logger.debug('Connected to Discord');
  74. });
  75.  
  76. this.ircClient.on('registered', (message) => {
  77. logger.debug('Registered event: ', message);
  78. this.autoSendCommands.forEach((element) => {
  79. this.ircClient.send(...element);
  80. });
  81. });
  82.  
  83. this.ircClient.on('error', (error) => {
  84. logger.error('Received error event from IRC', error);
  85. });
  86.  
  87. this.discord.on('error', (error) => {
  88. logger.error('Received error event from Discord', error);
  89. });
  90.  
  91. this.discord.on('message', (message) => {
  92. // Ignore bot messages and people leaving/joining
  93. this.sendToIRC(message);
  94. });
  95.  
  96. this.ircClient.on('message', this.sendToDiscord.bind(this));
  97.  
  98. this.ircClient.on('notice', (author, to, text) => {
  99. this.sendToDiscord(author, to, `*${text}*`);
  100. });
  101.  
  102. this.ircClient.on('action', (author, to, text) => {
  103. this.sendToDiscord(author, to, `_${text}_`);
  104. });
  105.  
  106. this.ircClient.on('invite', (channel, from) => {
  107. logger.debug('Received invite:', channel, from);
  108. if (!this.invertedMapping[channel]) {
  109. logger.debug('Channel not found in config, not joining:', channel);
  110. } else {
  111. this.ircClient.join(channel);
  112. logger.debug('Joining channel:', channel);
  113. }
  114. });
  115. }
  116.  
  117. parseText(message) {
  118. const text = message.mentions.users.reduce((content, mention) => (
  119. content.replace(`<@${mention.id}>`, `@${mention.username}`)
  120. .replace(`<@!${mention.id}>`, `@${mention.username}`)
  121. ), message.content);
  122.  
  123. return text
  124. .replace(/\n|\r\n|\r/g, ' ')
  125. .replace(/<#(\d+)>/g, (match, channelId) => {
  126. const channel = this.discord.channels.get(channelId);
  127. return `#${channel.name}`;
  128. })
  129. .replace(/<(:\w+:)\d+>/g, (match, emoteName) => emoteName);
  130. }
  131.  
  132. isCommandMessage(message) {
  133. return this.commandCharacters.indexOf(message[0]) !== -1;
  134. }
  135.  
  136. sendToIRC(message) {
  137. const author = message.author;
  138. // Ignore messages sent by the bot itself:
  139. if (author.id === this.discord.user.id) return;
  140.  
  141. const channelName = `#${message.channel.name}`;
  142. const ircChannel = this.channelMapping[channelName];
  143.  
  144. logger.debug('Channel Mapping', channelName, this.channelMapping[channelName]);
  145. if (ircChannel) {
  146. const username = author.username;
  147. const userDetails = message.guild.members.find('id', author.id);
  148. const nickname = userDetails.nickname || author.username;
  149. let text = this.parseText(message);
  150. let displayUsername = nickname;
  151. if (this.ircNickColor) {
  152. const colorIndex = (nickname.charCodeAt(0) + nickname.length) % NICK_COLORS.length;
  153. displayUsername = irc.colors.wrap(NICK_COLORS[colorIndex], nickname);
  154. }
  155.  
  156. if (this.isCommandMessage(text)) {
  157. const prelude = `Command sent from Discord by ${username}:`;
  158. this.ircClient.say(ircChannel, prelude);
  159. this.ircClient.say(ircChannel, text);
  160. } else {
  161. if (text !== '') {
  162. text = `<${displayUsername}> ${text}`;
  163. logger.debug('Sending message to IRC', ircChannel, text);
  164. this.ircClient.say(ircChannel, text);
  165. }
  166.  
  167. if (message.attachments && message.attachments.size) {
  168. message.attachments.forEach((a) => {
  169. const urlMessage = `<${displayUsername}> ${a.url}`;
  170. logger.debug('Sending attachment URL to IRC', ircChannel, urlMessage);
  171. this.ircClient.say(ircChannel, urlMessage);
  172. });
  173. }
  174. }
  175. }
  176. }
  177.  
  178. sendToDiscord(author, channel, text) {
  179. const discordChannelName = this.invertedMapping[channel.toLowerCase()];
  180. if (discordChannelName) {
  181. // #channel -> channel before retrieving:
  182. const discordChannel = this.discord.channels.find('name', discordChannelName.slice(1));
  183.  
  184. if (!discordChannel) {
  185. logger.info('Tried to send a message to a channel the bot isn\'t in: ',
  186. discordChannelName);
  187. return;
  188. }
  189.  
  190. const withMentions = text.replace(/@[^\s]+\b/g, (match) => {
  191. const user = this.discord.users.find('username', match.substring(1));
  192. return user || match;
  193. });
  194.  
  195. // Add bold formatting:
  196. const withAuthor = `**<${author}>** ${withMentions}`;
  197. logger.debug('Sending message to Discord', withAuthor, channel, '->', discordChannelName);
  198. discordChannel.sendMessage(withAuthor);
  199. }
  200. }
  201. }
  202.  
  203. export default Bot;
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement