forked from alveusgg/chatbot
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Closes alveusgg#32 Closes alveusgg#33 Closes alveusgg#34 Signed-off-by: flakey5 <[email protected]>
- Loading branch information
Showing
18 changed files
with
591 additions
and
21 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
// @ts-check | ||
|
||
const { userBlacklist, commandPrefix } = require('../config/config') | ||
const { groupMemberships, groups } = require('../config/config2.js') | ||
const { getAllFiles } = require('../utils/file.js') | ||
|
||
class CommandManager { | ||
/** | ||
* @type {import('../controller')} | ||
*/ | ||
#controller | ||
|
||
/** | ||
* @type {Record<string, import('./types.d.ts').Command>} | ||
*/ | ||
#commands = {} | ||
|
||
/** | ||
* @param {import('../controller')} controller | ||
*/ | ||
constructor(controller) { | ||
if (!controller.connections.twitch) { | ||
throw new TypeError('controller.connections.twitch not found') | ||
} | ||
|
||
this.#controller = controller | ||
|
||
controller.connections.twitch.onMessage(this.#handleTwitchMessage) | ||
} | ||
|
||
async loadCommands() { | ||
const files = (await getAllFiles(__dirname)).filter(path => path.endsWith('.js') && path !== 'index.js') | ||
|
||
for (const path of files) { | ||
const module = require(path) | ||
|
||
const commands = Array.isArray(module) ? module : [module] | ||
for (const command of commands) { | ||
assertCommand(path, command) | ||
|
||
this.#commands[command.name] = command | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* @param {string} channel | ||
* @param {string} user | ||
* @param {string} text | ||
* @param {object} msg TODO grab type | ||
* | ||
* @returns {Promise<void>} | ||
*/ | ||
async #handleTwitchMessage(channel, user, text, msg) { | ||
text = text.trim() | ||
|
||
if (text.length < 2) { | ||
// Not enough for their to be a command | ||
return; | ||
} | ||
|
||
if (userBlacklist.includes(user.toLowerCase())) { | ||
return; | ||
} | ||
|
||
const args = text.split(' ') | ||
if (!args[0].startsWith(commandPrefix)) { | ||
// Not a command (that we care about) | ||
return; | ||
} | ||
|
||
const commandName = args[0].substring(1) | ||
const command = this.#commands[commandName] | ||
if (!command || !command.enabled) { | ||
// Command doesn't exist or it's disabled | ||
return; | ||
} | ||
|
||
if (!canUserPerformCommand(user, command)) { | ||
return | ||
} | ||
|
||
const result = command.run({ | ||
controller: this.#controller, | ||
channel, | ||
user, | ||
args, | ||
msg | ||
}) | ||
|
||
if (typeof result?.then === 'function') { | ||
await result | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* @param {string} file | ||
* @param {any} command | ||
*/ | ||
function assertCommand(file, command) { | ||
if (typeof command !== 'object') { | ||
throw new TypeError(`${file}: expected command to be an object, got ${typeof command}`) | ||
} | ||
|
||
if (typeof command.name !== 'string') { | ||
throw new TypeError(`${file}: expected name to be a string, got ${typeof command.name}`) | ||
} | ||
|
||
if (typeof command.enabled !== 'boolean') { | ||
throw new TypeError(`${file}: expected enabled to be a boolean, got ${typeof command.enabled}`) | ||
} | ||
|
||
if (typeof command.permission !== 'undefined' && typeof command.permission === 'object') { | ||
throw new TypeError(`${file}: expected permission to be undefined or object, got ${typeof command.name}`) | ||
} | ||
|
||
if (typeof command.run !== 'function') { | ||
throw new TypeError(`${file}: expected run to be a function, got ${typeof command.run}`) | ||
} | ||
} | ||
|
||
/** | ||
* @param {string} user | ||
* @param {import('./types.d.ts').Command} arg1 | ||
* @returns {boolean} | ||
*/ | ||
function canUserPerformCommand(user, { permission }) { | ||
if (!permission) { | ||
// Command doesn't have permissions listed | ||
return true | ||
} | ||
|
||
if (permission.group && user in groupMemberships) { | ||
const userGroup = groupMemberships[user] | ||
if (permission.group === userGroup) { | ||
// User is in the exact group required | ||
return true; | ||
} | ||
|
||
// At this point, the user is in a group but it's not the one that's listed. | ||
// They can still run the command however if their group outranks the | ||
// group that's listed (i.e. admin can run mod commands, but operator | ||
// can't run mod commands) | ||
// | ||
// Noteworthy that the ranks are defined in reverse order, meaning a group | ||
// with a rank of 0 has a higher ranking than a group of 1. | ||
|
||
const minimumRank = groups[permission.group] | ||
const userRank = groups[userGroup] | ||
|
||
if (minimumRank === userRank) { | ||
// Different group, same rank. Shouldn't happen, but there's also nothing | ||
// preventing it from happening | ||
return false; | ||
} | ||
|
||
if (minimumRank > userRank) { | ||
// User has permission | ||
return true; | ||
} | ||
} | ||
|
||
if (permission.users && permission.users.includes(user.toLowerCase())) { | ||
return true | ||
} | ||
|
||
return false | ||
} | ||
|
||
module.exports = CommandManager |
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,59 @@ | ||
/** | ||
* @type {Record<string, number>} | ||
*/ | ||
const audioNameToIdMap = { | ||
alarm: 37, | ||
siren: 49, | ||
emergency: 40, | ||
trespassing: 43, | ||
camera: 33, | ||
hello: 1, | ||
despacito: 0, | ||
ringtone: 35, | ||
dog: 44 | ||
} | ||
|
||
const aliases = [ | ||
'ptzplayaudio', | ||
'playclip', | ||
'playaudio' | ||
] | ||
|
||
/** | ||
* @type {Array<import('./types.d.ts').Command>} | ||
*/ | ||
const commands = aliases.map(alias => ({ | ||
name: alias, | ||
enabled: true, | ||
permission: { | ||
group: 'operator' | ||
}, | ||
run: async ({ controller, args }) => { | ||
if (!controller.connections.cameras) { | ||
return; | ||
} | ||
|
||
const speaker = controller.connections.cameras.speaker | ||
|
||
const [, audioName] = args | ||
|
||
// Defaults to alarm | ||
let audioId = 37 // Default to alarm | ||
if (audioName) { | ||
if (audioName in audioNameToIdMap) { | ||
audioId = audioNameToIdMap[audioName] | ||
} else if (audioName !== '') { | ||
const result = Number.parseInt(audioName) | ||
if (isNaN(result)) { | ||
return; | ||
} | ||
|
||
audioId = result | ||
} | ||
} | ||
|
||
await speaker.playAudioClip(audioId) | ||
} | ||
})) | ||
|
||
module.exports = commands |
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,27 @@ | ||
const aliases = [ | ||
'ptzplayaudio', | ||
'playclip', | ||
'playaudio' | ||
] | ||
|
||
/** | ||
* @type {Array<import('./types.d.ts').Command>} | ||
*/ | ||
const commands = aliases.map(alias => ({ | ||
name: alias, | ||
enabled: true, | ||
permission: { | ||
group: 'operator' | ||
}, | ||
run: async ({ controller }) => { | ||
if (!controller.connections.cameras) { | ||
return; | ||
} | ||
|
||
const speaker = controller.connections.cameras.speaker | ||
|
||
await speaker.stopAudioClip() | ||
} | ||
})) | ||
|
||
module.exports = commands; |
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,36 @@ | ||
/** | ||
* Provides info on who's able to perform a command. | ||
* Note that each of these can be either or | ||
*/ | ||
export interface CommandPermissionInfo { | ||
/** | ||
* The minimum group needed to run the command | ||
*/ | ||
group: import('../config/types.d.ts').Group | ||
|
||
/** | ||
* The users allowed to perform this command regardless of the group they're om | ||
*/ | ||
users?: Array<string> | ||
} | ||
|
||
export interface CommandArgs { | ||
controller: import('../controller') | ||
channel: string; | ||
user: string; | ||
args: string[] | ||
msg: object | ||
} | ||
|
||
export interface Command { | ||
/** | ||
* The name used to run the command in chat | ||
*/ | ||
name: string | ||
|
||
enabled: boolean, | ||
|
||
permission?: CommandPermissionInfo | ||
|
||
run: (args: CommandArgs) => void | Promise<void> | ||
} |
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
Oops, something went wrong.