From 892a311eba975e880976e963f1f1a15e350b8301 Mon Sep 17 00:00:00 2001 From: sledgehammer Date: Mon, 14 Jul 2025 13:44:36 -0700 Subject: [PATCH 1/7] add typespeed command --- package-lock.json | 16 ++++ package.json | 2 + src/commands/prefixed/fun/typerace.ts | 29 +++++++ src/utils/formatter/commands/fun.typespeed.ts | 82 +++++++++++++++++++ src/utils/formatter/commands/index.ts | 2 + 5 files changed, 131 insertions(+) create mode 100644 src/commands/prefixed/fun/typerace.ts create mode 100644 src/utils/formatter/commands/fun.typespeed.ts diff --git a/package-lock.json b/package-lock.json index a2f072d..65045b7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,12 +23,14 @@ "openai": "^4.77.0", "opusscript": "^0.1.1", "redis": "^4.7.0", + "string-similarity": "^4.0.4", "typescript": "^5.7.2", "wav": "^1.0.2" }, "devDependencies": { "@types/moment-duration-format": "^2.2.6", "@types/node": "^22.10.2", + "@types/string-similarity": "^4.0.2", "@types/wav": "^1.0.4" } }, @@ -851,6 +853,13 @@ "integrity": "sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg==", "license": "MIT" }, + "node_modules/@types/string-similarity": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/string-similarity/-/string-similarity-4.0.2.tgz", + "integrity": "sha512-LkJQ/jsXtCVMK+sKYAmX/8zEq+/46f1PTQw7YtmQwb74jemS1SlNLmARM2Zml9DgdDTWKAtc5L13WorpHPDjDA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/tedious": { "version": "4.0.14", "resolved": "https://registry.npmjs.org/@types/tedious/-/tedious-4.0.14.tgz", @@ -1686,6 +1695,13 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" }, + "node_modules/string-similarity": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/string-similarity/-/string-similarity-4.0.4.tgz", + "integrity": "sha512-/q/8Q4Bl4ZKAPjj8WerIBJWALKkaPRfrvhfF8k/B23i4nzrlRj2/go1m90In7nG/3XDSbOo0+pu6RvCTM9RGMQ==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "license": "ISC" + }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", diff --git a/package.json b/package.json index 8b046fb..1c2c996 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "openai": "^4.77.0", "opusscript": "^0.1.1", "redis": "^4.7.0", + "string-similarity": "^4.0.4", "typescript": "^5.7.2", "wav": "^1.0.2" }, @@ -31,6 +32,7 @@ "devDependencies": { "@types/moment-duration-format": "^2.2.6", "@types/node": "^22.10.2", + "@types/string-similarity": "^4.0.2", "@types/wav": "^1.0.4" }, "scripts": { diff --git a/src/commands/prefixed/fun/typerace.ts b/src/commands/prefixed/fun/typerace.ts new file mode 100644 index 0000000..c24ce3b --- /dev/null +++ b/src/commands/prefixed/fun/typerace.ts @@ -0,0 +1,29 @@ +import { Command, CommandClient } from 'detritus-client'; + +import { CommandCategories } from '../../../constants'; +import { Formatter } from '../../../utils'; + +import { BaseCommand } from '../basecommand'; + + +export const COMMAND_NAME = 'typespeed'; + + +export default class TypeSpeed extends BaseCommand { + constructor (client: CommandClient) { + super(client, { + name: COMMAND_NAME, + metadata: { + category: CommandCategories.FUN, + description: 'Measure how fast you type', + examples: [COMMAND_NAME], + id: Formatter.Commands.FunTypeSpeed.COMMAND_ID, + usage: '', + }, + }); + } + + run(context: Command.Context, args: Formatter.Commands.FunTypeSpeed.CommandArgs) { + return Formatter.Commands.FunTypeSpeed.createMessage(context, args); + } +} \ No newline at end of file diff --git a/src/utils/formatter/commands/fun.typespeed.ts b/src/utils/formatter/commands/fun.typespeed.ts new file mode 100644 index 0000000..28da790 --- /dev/null +++ b/src/utils/formatter/commands/fun.typespeed.ts @@ -0,0 +1,82 @@ +import { Command, Interaction } from 'detritus-client'; +import { Components } from 'detritus-client/lib/utils'; + +import { compareTwoStrings } from 'string-similarity'; + +import { utilitiesImagescriptV1 } from '../../../api'; +import { editOrReply } from '../..'; + + +export const COMMAND_ID = 'fun.typerace'; + + +export interface CommandArgs { + +} + + +export async function createMessage( + context: Command.Context | Interaction.InteractionContext, + args: CommandArgs, +) { + const q: Response = await fetch('https://dummyjson.com/quotes/random'); + const text: string = (await q.json()).quote; + + // didn't see a text-to-image endpoint anywhere, + // so i'm using mscript instead + const code: string = ` + create bg 1024 512 0 0 0 + text text 50 1000 #FFFFFF ${text} + contain text 1024 512 + if texth > 512 resize bg bgh 768 + overlay bg text + render bg + `; + const resp = await utilitiesImagescriptV1(context, { code }); + const filename: string = resp.file.filename; + let data: Buffer | string = ( + (resp.file.value) + ? Buffer.from(resp.file.value, 'base64') + : Buffer.alloc(0) + ); + + const components = new Components(); + const container = components.createContainer(); + container.addTextDisplay({ content: 'Type the text below as fast as you can!' }); + container.addSeparator(); + container.createMediaGallery() + .addItem({ media: { url: `attachment://${filename}` } }); + + const initial = await editOrReply(context, { + file: { filename: filename, value: data }, + components: components + }); + + const start = Date.now(); + const sub = context.client.subscribe('messageCreate', async (msg) => { + const message = msg.message; + if (message.author.id !== context.user.id) return; + if (message.channelId !== context.channelId) return; + + clearTimeout(timeout); + sub.remove(); + + const end: number = (Date.now() - start) / 1000; + const percent: number = compareTwoStrings(message.content, text) * 100; + const words: number = message.content.trim().split(/\s+/).length; + const wpm: number = (words * 60) / end; + + await message.reply({ + content: `${percent.toFixed(1)}%, ${wpm.toFixed(1)} wpm, in ${end.toFixed(2)} seconds`, + messageReference: { messageId: message.id } + }); + }); + + const timeout = setTimeout(async () => { + sub.remove(); + return await initial?.reply({ + content: 'Timed out waiting for a message.', + messageReference: { messageId: initial.id } + }); + }, 45000); +} \ No newline at end of file diff --git a/src/utils/formatter/commands/index.ts b/src/utils/formatter/commands/index.ts index 156fa5c..65a29d8 100644 --- a/src/utils/formatter/commands/index.ts +++ b/src/utils/formatter/commands/index.ts @@ -8,6 +8,7 @@ import * as FunRegional from './fun.regional'; import * as FunReverseText from './fun.reversetext'; import * as FunTextwall from './fun.textwall'; import * as FunTTS from './fun.tts'; +import * as FunTypeSpeed from './fun.typespeed'; import * as InfoAvatar from './info.avatar'; import * as InfoUser from './info.user'; @@ -279,6 +280,7 @@ export { FunReverseText, FunTextwall, FunTTS, + FunTypeSpeed, InfoAvatar, InfoUser, From 0759e805026ae4352a463e2d31d897df012cf24f Mon Sep 17 00:00:00 2001 From: sledgehammer Date: Mon, 14 Jul 2025 14:15:29 -0700 Subject: [PATCH 2/7] rename --- .../fun/{typerace.ts => typespeed.ts} | 0 src/utils/formatter/commands/fun.typespeed.ts | 29 ++++++++++--------- 2 files changed, 16 insertions(+), 13 deletions(-) rename src/commands/prefixed/fun/{typerace.ts => typespeed.ts} (100%) diff --git a/src/commands/prefixed/fun/typerace.ts b/src/commands/prefixed/fun/typespeed.ts similarity index 100% rename from src/commands/prefixed/fun/typerace.ts rename to src/commands/prefixed/fun/typespeed.ts diff --git a/src/utils/formatter/commands/fun.typespeed.ts b/src/utils/formatter/commands/fun.typespeed.ts index 28da790..1e2b933 100644 --- a/src/utils/formatter/commands/fun.typespeed.ts +++ b/src/utils/formatter/commands/fun.typespeed.ts @@ -1,4 +1,4 @@ -import { Command, Interaction } from 'detritus-client'; +import { Command } from 'detritus-client'; import { Components } from 'detritus-client/lib/utils'; import { compareTwoStrings } from 'string-similarity'; @@ -16,11 +16,11 @@ export interface CommandArgs { export async function createMessage( - context: Command.Context | Interaction.InteractionContext, + context: Command.Context, args: CommandArgs, ) { - const q: Response = await fetch('https://dummyjson.com/quotes/random'); - const text: string = (await q.json()).quote; + const response: Response = await fetch('https://dummyjson.com/quotes/random'); + const text: string = (await response.json()).quote; // didn't see a text-to-image endpoint anywhere, // so i'm using mscript instead @@ -32,11 +32,12 @@ export async function createMessage( overlay bg text render bg `; - const resp = await utilitiesImagescriptV1(context, { code }); - const filename: string = resp.file.filename; + + const image = await utilitiesImagescriptV1(context, { code }); + const filename: string = image.file.filename; let data: Buffer | string = ( - (resp.file.value) - ? Buffer.from(resp.file.value, 'base64') + (image.file.value) + ? Buffer.from(image.file.value, 'base64') : Buffer.alloc(0) ); @@ -67,16 +68,18 @@ export async function createMessage( const wpm: number = (words * 60) / end; await message.reply({ - content: `${percent.toFixed(1)}%, ${wpm.toFixed(1)} wpm, in ${end.toFixed(2)} seconds`, - messageReference: { messageId: message.id } + content: `${percent.toFixed(1)}% accuracy, ${wpm.toFixed(1)} wpm, in ${end.toFixed(2)} seconds`, + messageReference: { messageId: message.id }, + allowedMentions: { repliedUser: false } }); }); const timeout = setTimeout(async () => { sub.remove(); - return await initial?.reply({ + await initial?.reply({ content: 'Timed out waiting for a message.', - messageReference: { messageId: initial.id } + messageReference: { messageId: initial.id }, + allowedMentions: { repliedUser: false } }); - }, 45000); + }, 60000); } \ No newline at end of file From cb7b1f1bd2c09d523df65f1a01496bd4d7f45d0a Mon Sep 17 00:00:00 2001 From: sledgehammer Date: Wed, 16 Jul 2025 22:08:48 -0700 Subject: [PATCH 3/7] add dates arg --- src/commands/prefixed/fun/typespeed.ts | 9 +++-- src/run.ts | 2 +- src/utils/formatter/commands/fun.typespeed.ts | 34 ++++++++++++++++--- 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/src/commands/prefixed/fun/typespeed.ts b/src/commands/prefixed/fun/typespeed.ts index c24ce3b..ff4cbf5 100644 --- a/src/commands/prefixed/fun/typespeed.ts +++ b/src/commands/prefixed/fun/typespeed.ts @@ -16,9 +16,14 @@ export default class TypeSpeed extends BaseCommand { metadata: { category: CommandCategories.FUN, description: 'Measure how fast you type', - examples: [COMMAND_NAME], + examples: [ + COMMAND_NAME, + `${COMMAND_NAME} -dates`, + `${COMMAND_NAME} -words` + ], id: Formatter.Commands.FunTypeSpeed.COMMAND_ID, - usage: '', + usage: '(-dates)', + aliases: ['speedtype'] }, }); } diff --git a/src/run.ts b/src/run.ts index a195db5..219497b 100644 --- a/src/run.ts +++ b/src/run.ts @@ -22,7 +22,7 @@ if (SENTRY_DSN) { // since we're on the thicc bot system, we need to have the shard count divisible by 16 const manager = new ClusterManager('./bot', NOTSOBOT_DISCORD_TOKEN, { - shardCount: 64 * 16, + shardCount: 64, shardsPerCluster: 8, // shards: [0, 0], // shards: [209, 209], diff --git a/src/utils/formatter/commands/fun.typespeed.ts b/src/utils/formatter/commands/fun.typespeed.ts index 1e2b933..1f4da69 100644 --- a/src/utils/formatter/commands/fun.typespeed.ts +++ b/src/utils/formatter/commands/fun.typespeed.ts @@ -2,16 +2,36 @@ import { Command } from 'detritus-client'; import { Components } from 'detritus-client/lib/utils'; import { compareTwoStrings } from 'string-similarity'; +import { randomInt } from 'mathjs'; import { utilitiesImagescriptV1 } from '../../../api'; import { editOrReply } from '../..'; +import { BooleanEmojis } from '../../../constants'; -export const COMMAND_ID = 'fun.typerace'; +export const COMMAND_ID = 'fun.typespeed'; export interface CommandArgs { + dates?: boolean, + words?: boolean, +} + + +function dates(): string { + const amount: number = randomInt(10, 15); + const dates: string[] = []; + + for (let i = 0; i < amount; i++) { + const time = new Date(+new Date() - Math.floor(Math.random() * 10000000000)); + const mm = String(time.getMonth() + 1).padStart(2, '0'); + const dd = String(time.getDate()).padStart(2, '0'); + const yyyy = time.getFullYear(); + + dates.push(`${mm}/${dd}/${yyyy}`); + } + return dates.join(', '); } @@ -19,8 +39,13 @@ export async function createMessage( context: Command.Context, args: CommandArgs, ) { - const response: Response = await fetch('https://dummyjson.com/quotes/random'); - const text: string = (await response.json()).quote; + let text: string; + if (args.dates) { + text = dates(); + } else { + const response = await fetch('https://dummyjson.com/quotes/random'); + text = (await response.json()).quote; + } // didn't see a text-to-image endpoint anywhere, // so i'm using mscript instead @@ -45,6 +70,7 @@ export async function createMessage( const container = components.createContainer(); container.addTextDisplay({ content: 'Type the text below as fast as you can!' }); container.addSeparator(); + container.addTextDisplay({ content: text }) container.createMediaGallery() .addItem({ media: { url: `attachment://${filename}` } }); @@ -77,7 +103,7 @@ export async function createMessage( const timeout = setTimeout(async () => { sub.remove(); await initial?.reply({ - content: 'Timed out waiting for a message.', + content: `${BooleanEmojis.WARNING} Didn't get a message from you in time`, messageReference: { messageId: initial.id }, allowedMentions: { repliedUser: false } }); From ad6dc949905c6022d99b5033afd0e5f148e8edf9 Mon Sep 17 00:00:00 2001 From: sledgehammer Date: Wed, 16 Jul 2025 22:09:09 -0700 Subject: [PATCH 4/7] remove unused arg --- src/utils/formatter/commands/fun.typespeed.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/utils/formatter/commands/fun.typespeed.ts b/src/utils/formatter/commands/fun.typespeed.ts index 1f4da69..a40423a 100644 --- a/src/utils/formatter/commands/fun.typespeed.ts +++ b/src/utils/formatter/commands/fun.typespeed.ts @@ -14,7 +14,6 @@ export const COMMAND_ID = 'fun.typespeed'; export interface CommandArgs { dates?: boolean, - words?: boolean, } From 861783161256371351ae5cdab7e6c96bff28a26d Mon Sep 17 00:00:00 2001 From: sledgehammer Date: Thu, 17 Jul 2025 17:53:17 -0700 Subject: [PATCH 5/7] multiplayer support --- src/commands/prefixed/fun/typespeed.ts | 9 +- src/run.ts | 2 +- src/utils/formatter/commands/fun.typespeed.ts | 101 +++++++++++------- 3 files changed, 67 insertions(+), 45 deletions(-) diff --git a/src/commands/prefixed/fun/typespeed.ts b/src/commands/prefixed/fun/typespeed.ts index ff4cbf5..be81066 100644 --- a/src/commands/prefixed/fun/typespeed.ts +++ b/src/commands/prefixed/fun/typespeed.ts @@ -1,4 +1,5 @@ import { Command, CommandClient } from 'detritus-client'; +import { Permissions } from 'detritus-client/lib/constants'; import { CommandCategories } from '../../../constants'; import { Formatter } from '../../../utils'; @@ -15,20 +16,20 @@ export default class TypeSpeed extends BaseCommand { name: COMMAND_NAME, metadata: { category: CommandCategories.FUN, - description: 'Measure how fast you type', + description: 'See who can type the fastest and accurately in 60 seconds', examples: [ COMMAND_NAME, `${COMMAND_NAME} -dates`, - `${COMMAND_NAME} -words` ], id: Formatter.Commands.FunTypeSpeed.COMMAND_ID, usage: '(-dates)', - aliases: ['speedtype'] + aliases: ['speedtype', 'typerace'], + permissionsClient: [Permissions.READ_MESSAGE_HISTORY] }, }); } - run(context: Command.Context, args: Formatter.Commands.FunTypeSpeed.CommandArgs) { + async run(context: Command.Context, args: Formatter.Commands.FunTypeSpeed.CommandArgs) { return Formatter.Commands.FunTypeSpeed.createMessage(context, args); } } \ No newline at end of file diff --git a/src/run.ts b/src/run.ts index 219497b..a195db5 100644 --- a/src/run.ts +++ b/src/run.ts @@ -22,7 +22,7 @@ if (SENTRY_DSN) { // since we're on the thicc bot system, we need to have the shard count divisible by 16 const manager = new ClusterManager('./bot', NOTSOBOT_DISCORD_TOKEN, { - shardCount: 64, + shardCount: 64 * 16, shardsPerCluster: 8, // shards: [0, 0], // shards: [209, 209], diff --git a/src/utils/formatter/commands/fun.typespeed.ts b/src/utils/formatter/commands/fun.typespeed.ts index a40423a..3997235 100644 --- a/src/utils/formatter/commands/fun.typespeed.ts +++ b/src/utils/formatter/commands/fun.typespeed.ts @@ -1,5 +1,6 @@ import { Command } from 'detritus-client'; import { Components } from 'detritus-client/lib/utils'; +import { Timers } from 'detritus-utils'; import { compareTwoStrings } from 'string-similarity'; import { randomInt } from 'mathjs'; @@ -17,20 +18,10 @@ export interface CommandArgs { } -function dates(): string { - const amount: number = randomInt(10, 15); - const dates: string[] = []; - - for (let i = 0; i < amount; i++) { - const time = new Date(+new Date() - Math.floor(Math.random() * 10000000000)); - const mm = String(time.getMonth() + 1).padStart(2, '0'); - const dd = String(time.getDate()).padStart(2, '0'); - const yyyy = time.getFullYear(); - - dates.push(`${mm}/${dd}/${yyyy}`); - } - - return dates.join(', '); +interface Winner { + accuracy: string, + time: string, + wpm: string, } @@ -65,46 +56,76 @@ export async function createMessage( : Buffer.alloc(0) ); - const components = new Components(); - const container = components.createContainer(); - container.addTextDisplay({ content: 'Type the text below as fast as you can!' }); - container.addSeparator(); - container.addTextDisplay({ content: text }) - container.createMediaGallery() - .addItem({ media: { url: `attachment://${filename}` } }); - - const initial = await editOrReply(context, { + const initial = await editOrReply(context, 'Race will begin in 5 seconds...'); + await Timers.sleep(5000); + await initial.edit({ file: { filename: filename, value: data }, - components: components + content: `Type the text below as fast and accurately as you can`, + allowedMentions: { repliedUser: false } }); const start = Date.now(); + const winners: Record = {}; + const sub = context.client.subscribe('messageCreate', async (msg) => { const message = msg.message; - if (message.author.id !== context.user.id) return; + + if (message.author.bot) return; + if (message.author.id in winners) return; if (message.channelId !== context.channelId) return; - clearTimeout(timeout); - sub.remove(); - - const end: number = (Date.now() - start) / 1000; - const percent: number = compareTwoStrings(message.content, text) * 100; + const time: number = (Date.now() - start) / 1000; + const accuracy: number = compareTwoStrings(message.content, text) * 100; const words: number = message.content.trim().split(/\s+/).length; - const wpm: number = (words * 60) / end; - - await message.reply({ - content: `${percent.toFixed(1)}% accuracy, ${wpm.toFixed(1)} wpm, in ${end.toFixed(2)} seconds`, - messageReference: { messageId: message.id }, - allowedMentions: { repliedUser: false } - }); + const wpm: number = (words * 60) / time; + winners[message.author.id] = { + accuracy: accuracy.toFixed(1), + time: time.toFixed(2), + wpm: wpm.toFixed(1) + }; + + if (message.canReact) await message.react(BooleanEmojis.YES); }); const timeout = setTimeout(async () => { sub.remove(); - await initial?.reply({ - content: `${BooleanEmojis.WARNING} Didn't get a message from you in time`, + const options = { messageReference: { messageId: initial.id }, allowedMentions: { repliedUser: false } - }); + }; + + if (Object.keys(winners).length === 0) { + return await initial.reply({ + content: `${BooleanEmojis.WARNING} Didn't get a message from anyone`, + ...options + }); + } + + const content: string[] = []; + for (const [id, stats] of Object.entries(winners)) { + content.push(`<@${id}>: ${stats.accuracy}% accuracy, ${stats.wpm} wpm, in ${stats.time}s`); + } + + await initial.reply({ + content: content.join('\n'), + ...options + }) }, 60000); +} + + +function dates(): string { + const amount: number = randomInt(10, 15); + const dates: string[] = []; + + for (let i = 0; i < amount; i++) { + const time = new Date(+new Date() - Math.floor(Math.random() * 10000000000)); + const mm = String(time.getMonth() + 1).padStart(2, '0'); + const dd = String(time.getDate()).padStart(2, '0'); + const yyyy = time.getFullYear(); + + dates.push(`${mm}/${dd}/${yyyy}`); + } + + return dates.join(', '); } \ No newline at end of file From 9d48c7eaf9df2c83e872cabc05a0ff5c09aa4b8b Mon Sep 17 00:00:00 2001 From: sledgehammer Date: Thu, 17 Jul 2025 18:15:35 -0700 Subject: [PATCH 6/7] add ongoing race lock --- src/stores/serverexecutions.ts | 9 +++++++-- src/utils/formatter/commands/fun.typespeed.ts | 9 +++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/stores/serverexecutions.ts b/src/stores/serverexecutions.ts index f71c92c..9301c20 100644 --- a/src/stores/serverexecutions.ts +++ b/src/stores/serverexecutions.ts @@ -4,7 +4,12 @@ import { EventSubscription } from 'detritus-utils'; import { Store } from './store'; -export type ServerExecutionsStored = {nick: boolean, prune: boolean, wordcloud: boolean}; +export type ServerExecutionsStored = { + nick: boolean, + prune: boolean, + wordcloud: boolean, + typespeed: boolean, +}; // Stores a server's command execution, for nick mass/pruning/wordcloud class ServerExecutionsStore extends Store { @@ -17,7 +22,7 @@ class ServerExecutionsStore extends Store { if (this.has(key)) { value = this.get(key) as ServerExecutionsStored; } else { - value = {nick: false, prune: false, wordcloud: false}; + value = {nick: false, prune: false, wordcloud: false, typespeed: false}; this.insert(key, value); } return value; diff --git a/src/utils/formatter/commands/fun.typespeed.ts b/src/utils/formatter/commands/fun.typespeed.ts index 3997235..7c0e0e2 100644 --- a/src/utils/formatter/commands/fun.typespeed.ts +++ b/src/utils/formatter/commands/fun.typespeed.ts @@ -8,6 +8,7 @@ import { randomInt } from 'mathjs'; import { utilitiesImagescriptV1 } from '../../../api'; import { editOrReply } from '../..'; import { BooleanEmojis } from '../../../constants'; +import ServerExecutionsStore from '../../../stores/serverexecutions'; export const COMMAND_ID = 'fun.typespeed'; @@ -29,6 +30,13 @@ export async function createMessage( context: Command.Context, args: CommandArgs, ) { + const store = ServerExecutionsStore.getOrCreate(context.channelId); + if (store.typespeed) { + return editOrReply(context, 'This channel has an ongoing race, wait for it to finish'); + } + + store.typespeed = true; + let text: string; if (args.dates) { text = dates(); @@ -88,6 +96,7 @@ export async function createMessage( }); const timeout = setTimeout(async () => { + store.typespeed = false; sub.remove(); const options = { messageReference: { messageId: initial.id }, From 5b0b8823ecbff5ea28ddb09adf2ac4780ba9174c Mon Sep 17 00:00:00 2001 From: sledgehammer Date: Thu, 17 Jul 2025 23:36:56 -0700 Subject: [PATCH 7/7] add words argument --- src/commands/prefixed/fun/typespeed.ts | 27 ++++++++++++++++--- src/utils/formatter/commands/fun.typespeed.ts | 11 ++++++-- src/utils/tools.ts | 14 ++++++++++ 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/src/commands/prefixed/fun/typespeed.ts b/src/commands/prefixed/fun/typespeed.ts index be81066..c93a58f 100644 --- a/src/commands/prefixed/fun/typespeed.ts +++ b/src/commands/prefixed/fun/typespeed.ts @@ -2,7 +2,7 @@ import { Command, CommandClient } from 'detritus-client'; import { Permissions } from 'detritus-client/lib/constants'; import { CommandCategories } from '../../../constants'; -import { Formatter } from '../../../utils'; +import { editOrReply, Formatter } from '../../../utils'; import { BaseCommand } from '../basecommand'; @@ -20,15 +20,36 @@ export default class TypeSpeed extends BaseCommand { examples: [ COMMAND_NAME, `${COMMAND_NAME} -dates`, + `${COMMAND_NAME} -words`, ], id: Formatter.Commands.FunTypeSpeed.COMMAND_ID, - usage: '(-dates)', + usage: '(-dates) (-words)', aliases: ['speedtype', 'typerace'], - permissionsClient: [Permissions.READ_MESSAGE_HISTORY] + permissionsClient: [Permissions.READ_MESSAGE_HISTORY], + args: [ + { name: 'dates', aliases: ['t'], type: Boolean }, + { name: 'words', aliases: ['w'], type: Boolean } + ] }, }); } + onBeforeRun(context: Command.Context, args: Formatter.Commands.FunTypeSpeed.CommandArgs) { + if (args.dates && args.words) { + return false; + } + + return true; + } + + onCancelRun(context: Command.Context, args: Formatter.Commands.FunTypeSpeed.CommandArgs) { + if (args.dates && args.words) { + return editOrReply(context, '⚠ Cannot mix in both words and dates'); + } + + return super.onCancelRun(context, args) + } + async run(context: Command.Context, args: Formatter.Commands.FunTypeSpeed.CommandArgs) { return Formatter.Commands.FunTypeSpeed.createMessage(context, args); } diff --git a/src/utils/formatter/commands/fun.typespeed.ts b/src/utils/formatter/commands/fun.typespeed.ts index 7c0e0e2..84cd0ac 100644 --- a/src/utils/formatter/commands/fun.typespeed.ts +++ b/src/utils/formatter/commands/fun.typespeed.ts @@ -1,12 +1,11 @@ import { Command } from 'detritus-client'; -import { Components } from 'detritus-client/lib/utils'; import { Timers } from 'detritus-utils'; import { compareTwoStrings } from 'string-similarity'; import { randomInt } from 'mathjs'; import { utilitiesImagescriptV1 } from '../../../api'; -import { editOrReply } from '../..'; +import { editOrReply, randomMultipleFromArray } from '../..'; import { BooleanEmojis } from '../../../constants'; import ServerExecutionsStore from '../../../stores/serverexecutions'; @@ -16,6 +15,7 @@ export const COMMAND_ID = 'fun.typespeed'; export interface CommandArgs { dates?: boolean, + words?: boolean, } @@ -40,7 +40,14 @@ export async function createMessage( let text: string; if (args.dates) { text = dates(); + } else if (args.words) { + // cakedan pls upload the stuff below to the cdn + const response = await fetch( + 'https://raw.githubusercontent.com/RazorSh4rk/random-word-api/refs/heads/master/words.json' + ); + text = (randomMultipleFromArray(await response.json(), 20)).join(' ').toLowerCase(); } else { + // maybe put this in the api? const response = await fetch('https://dummyjson.com/quotes/random'); text = (await response.json()).quote; } diff --git a/src/utils/tools.ts b/src/utils/tools.ts index adf62c0..2129e8f 100644 --- a/src/utils/tools.ts +++ b/src/utils/tools.ts @@ -2195,6 +2195,20 @@ export function randomFromArray( } +export function randomMultipleFromArray(array: T[], amount: number): T[] { + const arr = [...array]; + const result = []; + + let n = Math.min(amount, arr.length); + for (let i = 0; i < n; i++) { + const index = Math.floor(Math.random() * arr.length); + result.push(arr.splice(index, 1)[0]); + } + + return result; +} + + export function randomFromIterator( size: number, iterator: IterableIterator,