From e2d8cd9d63c3ddcbf0e6e99b63525d584f334292 Mon Sep 17 00:00:00 2001 From: Vitalij Ryndin Date: Sun, 17 Mar 2024 03:22:52 +0800 Subject: [PATCH 1/9] feat: twir addon --- src/twir/index.js | 90 ++++++++++++++++++++++++++++++++++++++++++ src/twir/manifest.json | 12 ++++++ 2 files changed, 102 insertions(+) create mode 100644 src/twir/index.js create mode 100644 src/twir/manifest.json diff --git a/src/twir/index.js b/src/twir/index.js new file mode 100644 index 00000000..7151678f --- /dev/null +++ b/src/twir/index.js @@ -0,0 +1,90 @@ +// identify commands of Twir +const ZWE_SYMBOL = '​'; + +class Twir extends Addon { + tabCommands = [] + + constructor(...args) { + super(...args); + + this.inject('site'); + this.inject('chat'); + } + + async fetchRoomCommands(room) { + try { + const response = await fetch( + 'https://twir.app/api/v1/api.UnProtected/GetChannelCommands', + { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ channelId: room.id }), + } + ); + + const { commands } = await response.json(); + return commands; + } catch (err) { + this.log.error(err); + return []; + } + } + + onEnable() { + this.on('chat:room-add', this.registerRoomCommands); + this.on('chat:room-remove', this.unregisterRoomCommands); + + for (const room of this.chat.iterateRooms()) { + if (room) { + this.registerRoomCommands(room); + } + } + + this.on('chat:pre-send-message', this.preSendMessage); + + this.on('chat:get-tab-commands', event => { + event.commands.push(...this.tabCommands); + }) + } + + onDisable() { + this.unregisterRoomCommands(); + } + + async registerRoomCommands(room) { + const roomCommands = await this.fetchRoomCommands(room); + if (!roomCommands.length) return; + + this.tabCommands = roomCommands.map(command => { + const description = command.description + || command.responses.length > 0 + ? command.responses[0] + : ''; + + return { + name: command.name + ZWE_SYMBOL, + description, + // parse `command.permisions` + permissionLevel: 0, + ffz_group: `Twir (${command.module})`, + } + }) + } + + unregisterRoomCommands() { + this.tabCommands = []; + } + + preSendMessage(event) { + const message = event.message.trim(); + if (message.startsWith('!')) return; + + if (message.includes(ZWE_SYMBOL) && message.startsWith('/')) { + const command = message.replace(ZWE_SYMBOL, '').slice(1); + if (!command) return; + event.message = `!${command}`; + } + } +} + +Twir.register(); diff --git a/src/twir/manifest.json b/src/twir/manifest.json new file mode 100644 index 00000000..46c05150 --- /dev/null +++ b/src/twir/manifest.json @@ -0,0 +1,12 @@ +{ + "enabled": false, + "requires": [], + "version": "1.0.0", + "icon": "https://twir.app/twir.svg", + "short_name": "twir", + "name": "Twir", + "author": "crashmax", + "description": "Command suggestions for Twir chat bot.", + "settings": "add_ons.twir", + "website": "https://twir.app" +} From b9556e27af45136874bac8d006a34fa8f6e21f39 Mon Sep 17 00:00:00 2001 From: Vitalij Ryndin Date: Sun, 17 Mar 2024 03:49:12 +0800 Subject: [PATCH 2/9] chore: update manifest --- src/twir/index.js | 4 ++-- src/twir/manifest.json | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/twir/index.js b/src/twir/index.js index 7151678f..50065b1e 100644 --- a/src/twir/index.js +++ b/src/twir/index.js @@ -62,7 +62,7 @@ class Twir extends Addon { : ''; return { - name: command.name + ZWE_SYMBOL, + name: ZWE_SYMBOL + command.name, description, // parse `command.permisions` permissionLevel: 0, @@ -79,7 +79,7 @@ class Twir extends Addon { const message = event.message.trim(); if (message.startsWith('!')) return; - if (message.includes(ZWE_SYMBOL) && message.startsWith('/')) { + if (message.startsWith('/') && message.includes(ZWE_SYMBOL)) { const command = message.replace(ZWE_SYMBOL, '').slice(1); if (!command) return; event.message = `!${command}`; diff --git a/src/twir/manifest.json b/src/twir/manifest.json index 46c05150..b056a1c6 100644 --- a/src/twir/manifest.json +++ b/src/twir/manifest.json @@ -1,12 +1,14 @@ { "enabled": false, "requires": [], - "version": "1.0.0", + "version": "0.0.1", "icon": "https://twir.app/twir.svg", "short_name": "twir", "name": "Twir", "author": "crashmax", - "description": "Command suggestions for Twir chat bot.", + "description": "Twir command suggestions.", + "website": "https://twir.app", "settings": "add_ons.twir", - "website": "https://twir.app" + "created": "2024-03-16T00:00:00.000Z", + "updated": "2024-03-16T00:00:00.000Z" } From 95c2ed0da58990141b30e7119b5c435a92cf8bd5 Mon Sep 17 00:00:00 2001 From: Vitalij Ryndin Date: Sun, 17 Mar 2024 03:55:46 +0800 Subject: [PATCH 3/9] chore: update logo --- src/twir/logo.png | Bin 0 -> 26789 bytes src/twir/manifest.json | 1 - 2 files changed, 1 deletion(-) create mode 100644 src/twir/logo.png diff --git a/src/twir/logo.png b/src/twir/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..baaed43139b504632dd1b0787a9f7d9e91ea9667 GIT binary patch literal 26789 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

hV<@ci0=cJ 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 Date: Sun, 17 Mar 2024 15:18:06 +0800 Subject: [PATCH 4/9] refactor: api --- src/twir/api.js | 37 +++++++++++++++++++++++++++++++++++++ src/twir/index.js | 28 ++++++---------------------- 2 files changed, 43 insertions(+), 22 deletions(-) create mode 100644 src/twir/api.js diff --git a/src/twir/api.js b/src/twir/api.js new file mode 100644 index 00000000..56c15f01 --- /dev/null +++ b/src/twir/api.js @@ -0,0 +1,37 @@ +export class Api extends FrankerFaceZ.utilities.module.Module { + constructor(...args) { + super(...args); + + this.inject(Commands); + + this.apiBase = 'https://twir.app/api/v1/api.'; + } + + async request(path, body) { + try { + const response = await fetch(`${this.apiBase}${path}`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + }); + + if (response.ok) { + const json = await response.json(); + return json; + } + } catch (err) { + this.log.error(err); + } + + return null; + } +} + +export class Commands extends FrankerFaceZ.utilities.module.Module { + // https://twir.app/api/v1/api.UnProtected/GetChannelCommands + getChannelCommands(channelId) { + return this.parent.request('UnProtected/GetChannelCommands', { + channelId, + }); + } +} diff --git a/src/twir/index.js b/src/twir/index.js index 50065b1e..25124b60 100644 --- a/src/twir/index.js +++ b/src/twir/index.js @@ -1,3 +1,5 @@ +import { Api } from './api.js'; + // identify commands of Twir const ZWE_SYMBOL = '​'; @@ -7,29 +9,11 @@ class Twir extends Addon { constructor(...args) { super(...args); + this.inject(Api); this.inject('site'); this.inject('chat'); } - async fetchRoomCommands(room) { - try { - const response = await fetch( - 'https://twir.app/api/v1/api.UnProtected/GetChannelCommands', - { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ channelId: room.id }), - } - ); - - const { commands } = await response.json(); - return commands; - } catch (err) { - this.log.error(err); - return []; - } - } - onEnable() { this.on('chat:room-add', this.registerRoomCommands); this.on('chat:room-remove', this.unregisterRoomCommands); @@ -52,10 +36,10 @@ class Twir extends Addon { } async registerRoomCommands(room) { - const roomCommands = await this.fetchRoomCommands(room); - if (!roomCommands.length) return; + const commandsResponse = await this.api.commands.getChannelCommands(room.id); + if (!commandsResponse) return; - this.tabCommands = roomCommands.map(command => { + this.tabCommands = commandsResponse.commands.map(command => { const description = command.description || command.responses.length > 0 ? command.responses[0] From 3d22a05e5a2210368adf71cd60aa660b3e98361a Mon Sep 17 00:00:00 2001 From: Vitalij Ryndin Date: Sun, 17 Mar 2024 16:18:57 +0800 Subject: [PATCH 5/9] refactor --- src/twir/index.js | 60 +++++++++++++++++++++++------------------------ 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/src/twir/index.js b/src/twir/index.js index 25124b60..66a84fb4 100644 --- a/src/twir/index.js +++ b/src/twir/index.js @@ -1,16 +1,12 @@ import { Api } from './api.js'; -// identify commands of Twir -const ZWE_SYMBOL = '​'; - class Twir extends Addon { - tabCommands = [] + roomCommands = new Map(); constructor(...args) { super(...args); this.inject(Api); - this.inject('site'); this.inject('chat'); } @@ -24,50 +20,52 @@ class Twir extends Addon { } } - this.on('chat:pre-send-message', this.preSendMessage); - - this.on('chat:get-tab-commands', event => { - event.commands.push(...this.tabCommands); - }) + this.on('chat:get-tab-commands', this.getTabCommands); } onDisable() { - this.unregisterRoomCommands(); + this.off('chat:room-add', this.registerRoomCommands); + this.off('chat:room-remove', this.unregisterRoomCommands); + + for (const roomId of this.roomCommands.keys()) { + this.unregisterRoomCommands({ id: roomId }); + } + + this.off('chat:get-tab-commands', this.getTabCommands); + } + + getTabCommands(event) { + for (const room of this.chat.iterateRooms()) { + if (room) { + const commands = this.roomCommands.get(room.id); + if (commands) { + event.commands.push(...commands); + } + } + } } async registerRoomCommands(room) { const commandsResponse = await this.api.commands.getChannelCommands(room.id); if (!commandsResponse) return; - this.tabCommands = commandsResponse.commands.map(command => { - const description = command.description - || command.responses.length > 0 - ? command.responses[0] - : ''; + const commands = commandsResponse.commands.map(command => { + const description = command.description || command.responses.join(' | '); return { - name: ZWE_SYMBOL + command.name, + prefix: '!', + name: command.name, description, - // parse `command.permisions` permissionLevel: 0, - ffz_group: `Twir (${command.module})`, + ffz_group: `Twir (${command.group ?? command.module})`, } }) - } - unregisterRoomCommands() { - this.tabCommands = []; + this.roomCommands.set(room.id, commands); } - preSendMessage(event) { - const message = event.message.trim(); - if (message.startsWith('!')) return; - - if (message.startsWith('/') && message.includes(ZWE_SYMBOL)) { - const command = message.replace(ZWE_SYMBOL, '').slice(1); - if (!command) return; - event.message = `!${command}`; - } + unregisterRoomCommands(room) { + this.roomCommands.delete(room.id); } } From cf1d9e853c68dfee79504d5e0d66939fe9e5ef91 Mon Sep 17 00:00:00 2001 From: Vitalij Ryndin Date: Sun, 17 Mar 2024 16:19:13 +0800 Subject: [PATCH 6/9] enable add-on --- src/twir/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/twir/manifest.json b/src/twir/manifest.json index d2de13f5..73266401 100644 --- a/src/twir/manifest.json +++ b/src/twir/manifest.json @@ -1,5 +1,5 @@ { - "enabled": false, + "enabled": true, "requires": [], "version": "0.0.1", "short_name": "twir", From 891b221daafdbd2cc5f4965484024cb699b28121 Mon Sep 17 00:00:00 2001 From: Vitalij Ryndin Date: Sun, 17 Mar 2024 16:56:36 +0800 Subject: [PATCH 7/9] feat: add settings command description --- src/twir/index.js | 40 +++++++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/src/twir/index.js b/src/twir/index.js index 66a84fb4..31a8bf54 100644 --- a/src/twir/index.js +++ b/src/twir/index.js @@ -1,13 +1,25 @@ import { Api } from './api.js'; class Twir extends Addon { - roomCommands = new Map(); - constructor(...args) { super(...args); this.inject(Api); + this.inject('chat'); + this.inject('settings'); + + this.roomCommands = new Map(); + + this.settings.add('addon.twir.command_description', { + default: true, + ui: { + path: 'Add-Ons > Twir >> Chat', + title: 'Command description', + description: 'Show command description or responses.', + component: 'setting-check-box', + } + }); } onEnable() { @@ -37,7 +49,7 @@ class Twir extends Addon { getTabCommands(event) { for (const room of this.chat.iterateRooms()) { if (room) { - const commands = this.roomCommands.get(room.id); + const commands = this.getRoomCommands(room); if (commands) { event.commands.push(...commands); } @@ -48,24 +60,30 @@ class Twir extends Addon { async registerRoomCommands(room) { const commandsResponse = await this.api.commands.getChannelCommands(room.id); if (!commandsResponse) return; + this.roomCommands.set(room.id, commandsResponse.commands); + } - const commands = commandsResponse.commands.map(command => { + unregisterRoomCommands(room) { + this.roomCommands.delete(room.id); + } + + getRoomCommands(room) { + const commands = this.roomCommands.get(room.id); + if (!commands) return; + + const showCommandDescription = this.settings.get('addon.twir.command_description'); + + return commands.map(command => { const description = command.description || command.responses.join(' | '); return { prefix: '!', name: command.name, - description, + description: showCommandDescription ? description : '', permissionLevel: 0, ffz_group: `Twir (${command.group ?? command.module})`, } }) - - this.roomCommands.set(room.id, commands); - } - - unregisterRoomCommands(room) { - this.roomCommands.delete(room.id); } } From 039054479e0a37b0122fab0ffd96b122351ad9f8 Mon Sep 17 00:00:00 2001 From: Vitalij Ryndin Date: Sun, 17 Mar 2024 18:20:12 +0800 Subject: [PATCH 8/9] feat: badges --- src/twir/index.js | 63 ++++++++++++++++++++++++++++++++++++++++-- src/twir/manifest.json | 4 +-- 2 files changed, 62 insertions(+), 5 deletions(-) diff --git a/src/twir/index.js b/src/twir/index.js index 31a8bf54..fcbed88b 100644 --- a/src/twir/index.js +++ b/src/twir/index.js @@ -7,6 +7,7 @@ class Twir extends Addon { this.inject(Api); this.inject('chat'); + this.inject('chat.badges'); this.inject('settings'); this.roomCommands = new Map(); @@ -14,12 +15,24 @@ class Twir extends Addon { this.settings.add('addon.twir.command_description', { default: true, ui: { - path: 'Add-Ons > Twir >> Chat', - title: 'Command description', + path: 'Add-Ons > Twir >> Commands', + title: 'Description', description: 'Show command description or responses.', component: 'setting-check-box', } }); + + this.settings.add('addon.twir.user_badges', { + default: true, + ui: { + path: 'Add-Ons > Twir >> User Cosmetics', + title: 'Badges', + description: 'Show user badges.\n\n(Per-badge visibilty can be set in [Chat >> Badges > Visibilty > Add-Ons](~chat.badges.tabs.visibility))', + component: 'setting-check-box', + } + }); + + this.loadBadges(); } onEnable() { @@ -33,6 +46,7 @@ class Twir extends Addon { } this.on('chat:get-tab-commands', this.getTabCommands); + this.settings.getChanges('addon.twir.user_badges', this.updateBadges, this); } onDisable() { @@ -43,7 +57,7 @@ class Twir extends Addon { this.unregisterRoomCommands({ id: roomId }); } - this.off('chat:get-tab-commands', this.getTabCommands); + this.unloadBadges(); } getTabCommands(event) { @@ -85,6 +99,49 @@ class Twir extends Addon { } }) } + + updateBadges(enabled) { + if (!enabled) { + this.unloadBadges(); + } else { + this.loadBadges(); + } + } + + unloadBadges() { + this.badges.removeBadge('addon.twir.badge_contributor'); + this.emit('chat:update-lines'); + } + + async loadBadges() { + const showUserBadges = this.settings.get('addon.twir.user_badges'); + if (!showUserBadges) return; + + this.badges.loadBadgeData('addon.twir.badge_contributor', { + id: 'contributor', + name: 'Twir Contributor', + title: 'Twir Contributor', + click_url: 'https://twir.app', + image: 'https://twir.app/twir.svg', + slot: 100, + svg: true, + }); + + try { + const response = await fetch('https://raw.githubusercontent.com/twirapp/.github/main/contributors.json'); + if (!response.ok) return; + + const contributors = await response.json(); + for (const contributor of contributors) { + const user = this.chat.getUser(contributor.id); + user.addBadge('addon.twir', 'addon.twir.badge_contributor'); + } + } catch (err) { + this.log.error(err); + } + + this.emit('chat:update-lines'); + } } Twir.register(); diff --git a/src/twir/manifest.json b/src/twir/manifest.json index 73266401..f3a6db5a 100644 --- a/src/twir/manifest.json +++ b/src/twir/manifest.json @@ -2,10 +2,10 @@ "enabled": true, "requires": [], "version": "0.0.1", - "short_name": "twir", + "short_name": "Twir", "name": "Twir", "author": "crashmax", - "description": "Twir command suggestions.", + "description": "Twir command suggestions and badges.", "website": "https://twir.app", "settings": "add_ons.twir", "created": "2024-03-16T00:00:00.000Z", From 4f22af8ee800a0b2883bcc1d7b97bae7266af50b Mon Sep 17 00:00:00 2001 From: Vitalij Ryndin Date: Sun, 17 Mar 2024 18:40:56 +0800 Subject: [PATCH 9/9] chore: version 1.0.0 --- src/twir/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/twir/manifest.json b/src/twir/manifest.json index f3a6db5a..679815a7 100644 --- a/src/twir/manifest.json +++ b/src/twir/manifest.json @@ -1,7 +1,7 @@ { "enabled": true, "requires": [], - "version": "0.0.1", + "version": "1.0.0", "short_name": "Twir", "name": "Twir", "author": "crashmax",