This repository has been archived by the owner on Jan 19, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 76
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
3.10.0: New connections scheme, new commands, clearer readme
- Loading branch information
Showing
41 changed files
with
863 additions
and
633 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,117 +1,121 @@ | ||
Object.fromMap = map => Object.assign(...map.map((v, k) => ({[k]: v}))) | ||
const yaml = require('js-yaml') | ||
const fs = require('fs') | ||
const log = require('npmlog') | ||
const { promisify } = require('util') | ||
const { Collection } = require('discord.js') | ||
const path = require('path') | ||
const { getThread, getChannelName } = require('./messenger') | ||
|
||
class ConnectionsManager { | ||
constructor () { | ||
this.path = path.join(config.path, 'channels.yml') | ||
if (fs.existsSync(path.join(config.path, 'channels.yml'))) { | ||
fs.renameSync(path.join(config.path, 'channels.yml'), path.join(config.path, 'connections.yml')) | ||
} | ||
|
||
this.path = path.join(config.path, 'connections.yml') | ||
} | ||
|
||
async load () { | ||
try { | ||
// load file and parse YAML | ||
let parsed = yaml.safeLoad(fs.readFileSync(this.path, 'utf8')) | ||
|
||
if (!parsed) throw new Error('not an error') | ||
|
||
// throw error when channels.yaml is empty (TODO) | ||
if (typeof parsed !== 'object') throw new Error('channels.yml\'s type is not "object"') | ||
// throw error when connections.yml is empty | ||
if (typeof parsed !== 'object') throw new Error('connections.yml\'s type is not "object"') | ||
|
||
// create Collection from these channels | ||
this.channels = new Collection() | ||
Object.entries(parsed).forEach( | ||
([discord, messenger]) => { | ||
if (discord.startsWith('_')) return | ||
const channel = this.findChannel(discord) | ||
if (typeof messenger === 'string') return this.channels.set(messenger, channel) | ||
messenger.forEach(threadID => this.channels.set(threadID, channel)) | ||
} | ||
) | ||
this.list = new Collection() | ||
await Promise.all(Object.entries(parsed).map(async ([key, value]) => { | ||
log.verbose('ConnectionsManager: init', 'key: %s, value: %s', key, value) | ||
if (typeof value === 'string' || value.some(el => typeof el === 'string')) return this.parseOldFormat(key, value) | ||
value = await Promise.all(value.map(async endpoint => { | ||
if (!endpoint.type || !endpoint.id) throw new Error('type or id missing on endpoint ' + JSON.stringify(endpoint)) | ||
if (endpoint.name) return endpoint | ||
if (endpoint.type === 'discord') endpoint.name = discord.client.channels.get(endpoint.id).name | ||
else endpoint.name = await getChannelName(await getThread(endpoint.id), false) | ||
return endpoint | ||
})) | ||
this.list.set(key, value) | ||
})) | ||
await this.save() | ||
} catch (err) { | ||
if (err.message !== 'not an error' && err.code !== 'ENOENT') throw err | ||
this.channels = new Collection() | ||
this.list = new Collection() | ||
await this.save() | ||
} | ||
} | ||
|
||
async parseOldFormat (key, value) { | ||
if (key.startsWith('_')) return log.verbose('CM: parseOldFormat', 'Ignoring ', key) | ||
if (value === '0') return log.verbose('CM: parseOldFormat', 'Ignoring ', key) | ||
|
||
this.save() | ||
const connection = [ { type: 'discord', id: key, name: discord.client.channels.get(key).name } ] | ||
if (typeof value === 'string') connection.push({ type: 'messenger', id: value, name: await getChannelName(await getThread(value), false) }) | ||
else await Promise.all(value.map(async threadID => connection.push({ type: 'messenger', id: threadID, name: await getChannelName(await getThread(threadID), false) }))) | ||
|
||
log.verbose('CM: parseOldFormat', 'Migrated connection:', JSON.stringify(connection)) | ||
return this.list.set(discord.client.channels.get(key).name, connection) | ||
} | ||
|
||
async getChannel (id, name) { | ||
async getChannels (id, name) { | ||
log.verbose('CM: getChannel', 'Looking up channel with name %s and ID %s', name, id) | ||
|
||
// try to get channel by id | ||
let channel = this.channels.get(id) | ||
log.silly('CM: getChannel: existing channel', channel) | ||
|
||
// if found, change name and return | ||
if (channel) return (config.discord.renameChannels && this.getThreadIDs(channel.id).length === 1) ? (channel.name === name ? channel : channel.edit({ name })) : channel | ||
// try to get channels by id | ||
const channels = this.getConnection(id) | ||
.filter(endpoint => endpoint.type === 'discord') | ||
.map(channel => { let c = discord.client.channels.get(channel.id); c.readonly = channel.readonly; return c }) | ||
log.silly('CM: getChannel: existing channels', channels) | ||
|
||
// if found | ||
if (channels.length) { | ||
// if: renaming is disabled/it's not one-to-one/name matches - return original channels | ||
if (!config.discord.renameChannels || this.getThreads(id).length > 1 || channels.length > 1 || channels[0].name === name) return channels.filter(el => !el.readonly) | ||
// rename and return as array | ||
return [await channels[0].edit({ name })].filter(el => !el.readonly) | ||
} | ||
|
||
if (!config.discord.createChannels) return log.verbose('CM: getChannel', 'Channel creation disabled, skipping.') | ||
if (!config.discord.createChannels) return log.verbose('CM: getChannel', 'Channel creation disabled, skipping. Name: %s, id: %s', name, id) || [] | ||
|
||
// if not found, create new channel with specified name and set its parent | ||
channel = await discord.guilds[0].createChannel(name, 'text') | ||
const channel = await discord.guilds[0].createChannel(name, 'text') | ||
await channel.overwritePermissions(discord.guilds[0].roles.find(role => role.name === '@everyone').id, { VIEW_CHANNEL: false }) | ||
log.verbose('CM: getChannel', 'Channel created, name: %s, id: %s', name, channel.id) | ||
// TODO: the code below could crash if category user provided is not in the same guild | ||
if (discord.category) await channel.setParent(discord.category) | ||
|
||
// save newly created channel in the channels map | ||
this.channels.set(id, channel) | ||
this.list.set(name, [ | ||
{ type: 'discord', id: channel.id, name }, | ||
{ type: 'messenger', id, name } | ||
]) | ||
await this.save() | ||
|
||
log.silly('getChannel: new channel', channel) | ||
return channel | ||
return [ channel ] | ||
} | ||
|
||
getThreads (id) { | ||
return this.getConnection(id).filter(endpoint => endpoint.type === 'messenger') | ||
} | ||
|
||
has (nameOrID) { | ||
return this.channels.find(discord => nameOrID === discord.name || nameOrID === discord.id) || this.channels.has(nameOrID) | ||
getConnection (id) { | ||
return this.list.find(connection => connection.some(endpoint => endpoint.id === id)) || [] | ||
} | ||
|
||
getThreadIDs (nameOrID) { | ||
return this.channels.filter(discord => discord.id === nameOrID || discord.name === nameOrID).map((value, key) => key) | ||
has (id) { | ||
return this.list.some(connection => connection.some(endpoint => endpoint.id === id)) | ||
} | ||
|
||
save () { | ||
let obj = { | ||
__comment: 'This is your channels.yml file. Lines beginning with "_" are created by Miscord just for readability, they\'re not needed if you\'re editing this file by hand. You can add your own comments by adding lines starting with "#"' | ||
} | ||
for (const [messenger, discord] of this.channels.entries()) { | ||
let otherChannels = this.channels.filter(another => another.id === discord.id).map((value, key) => key) | ||
obj[discord.id] = otherChannels.length > 1 ? otherChannels : messenger | ||
obj['_' + discord.id] = discord.name | ||
__comment: 'This is your connections.yml file. More info at https://github.com/miscord/miscord/wiki/Connections.yml' | ||
} | ||
if (this.list.size) obj = Object.assign(obj, Object.fromMap(this.list)) | ||
return promisify(fs.writeFile)(this.path, yaml.safeDump(obj), 'utf8') | ||
} | ||
|
||
findChannel (nameOrID) { | ||
return discord.client.channels.find(channel => channel.name === nameOrID || channel.id === nameOrID) || log.warn('CM', 'Discord channel %s has not been mapped properly', nameOrID) | ||
} | ||
|
||
add (discord, messenger) { | ||
const channel = this.findChannel(discord) | ||
if (!channel) return `Channel ${discord} not found!` | ||
if (typeof messenger === 'string') this.channels.set(messenger, channel) | ||
else messenger.forEach(threadID => this.channels.set(threadID, channel)) | ||
this.save() | ||
let plural = typeof messenger !== 'string' | ||
return `Thread${plural ? 's' : ''} ${plural ? messenger.join(', ') : messenger} ${plural ? 'were' : 'was'} linked to #${channel.name}!` | ||
} | ||
|
||
remove (messenger) { | ||
this.channels.delete(messenger) | ||
this.save() | ||
return `Thread ${messenger} was removed from channel map!` | ||
} | ||
|
||
getPrintable () { | ||
var getFBLink = id => `[\`${id}\`](https://facebook.com/messages/t/${id})` | ||
let arr = [] | ||
for (let [messenger, discord] of this.channels.entries()) { | ||
let otherChannels = this.channels.filter(another => another.id === discord.id).map((value, key) => key) | ||
|
||
messenger = otherChannels.length > 1 ? otherChannels.map(getFBLink) : getFBLink(messenger) | ||
discord = `<#${discord.id}>` | ||
arr.push(`${discord}: ${messenger}`) | ||
} | ||
return [ ...new Set(arr) ].join('\n') | ||
} | ||
} | ||
|
||
module.exports = ConnectionsManager |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
const Command = require('./Command') | ||
|
||
module.exports = new Command({ | ||
argc: 1, | ||
usage: `add <connection name>`, | ||
example: `add test-connection` | ||
}, (argv, reply) => { | ||
const name = argv[0] | ||
if (connections.list.has(name)) return `Connection \`${name}\` already exists!` | ||
connections.list.set(name, []) | ||
connections.save() | ||
reply(`Connection \`${name}\` was added successfully!`) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
const Command = require('./Command') | ||
// shamelessly stolen from takidelfin/8rniczka | ||
// full credits to @takidelfin | ||
|
||
module.exports = new Command({ | ||
argc: 1, | ||
usage: `eval <code>`, | ||
example: `eval config.messenger.whitelist = ['123']`, | ||
allowMoreArguments: true | ||
}, (argv, reply) => { | ||
try { | ||
const code = argv.join(' ') | ||
let evaled = eval(code) // eslint-disable-line | ||
|
||
if (typeof evaled !== 'string') evaled = require('util').inspect(evaled) | ||
if (evaled.length >= 2000) { | ||
for (let i = 0; i < evaled.length; i += 2000) { | ||
reply(evaled.substring(i, i + 2000), { code: 'xl' }) | ||
} | ||
return | ||
} | ||
reply(evaled, { code: 'xl' }) | ||
} catch (err) { | ||
const embed = { | ||
title: 'Miscord eval() error', | ||
description: 'Whoops, looks like eval() died :(\n```\n' + clean(err) + '\n```', | ||
color: 16741749 | ||
} | ||
reply({ embed }) | ||
} | ||
}) | ||
|
||
const clean = text => typeof text === 'string' ? text.replace(/`/g, '`' + String.fromCharCode(8203)).replace(/@/g, '@' + String.fromCharCode(8203)) : text |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
const Command = require('./Command') | ||
const getFBLink = id => `[\`${id}\`](https://facebook.com/messages/t/${id})` | ||
const e = endpoint => `\`${endpoint.type}\`: ${endpoint.type === 'messenger' ? getFBLink(endpoint.id) : `<#${endpoint.id}>`}${endpoint.readonly ? ' (readonly)' : ''}` | ||
|
||
module.exports = new Command({ | ||
argc: 1, | ||
usage: `info <connection name/Discord channel ID/Facebook thread ID>`, | ||
example: `info test-connection` | ||
}, async (argv, reply) => { | ||
let name = argv[0] | ||
let connection = connections.list.get(name) | ||
if (!connection) connection = connections.list.filter(connection => connection.some(endpoint => endpoint.id === name)).map((value, key) => { name = key; return value })[0] | ||
if (!connection) return reply(`Connection \`${name}\` not found!`) | ||
reply({ embed: { | ||
title: `Connection: ${name}`, | ||
description: `${connection.map(e).join('\n')}` | ||
}}) | ||
}) |
Oops, something went wrong.