Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand All @@ -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": {
Expand Down
56 changes: 56 additions & 0 deletions src/commands/prefixed/fun/typespeed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Command, CommandClient } from 'detritus-client';
import { Permissions } from 'detritus-client/lib/constants';

import { CommandCategories } from '../../../constants';
import { editOrReply, 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: '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) (-words)',
aliases: ['speedtype', 'typerace'],
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);
}
}
9 changes: 7 additions & 2 deletions src/stores/serverexecutions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, ServerExecutionsStored> {
Expand All @@ -17,7 +22,7 @@ class ServerExecutionsStore extends Store<string, ServerExecutionsStored> {
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;
Expand Down
147 changes: 147 additions & 0 deletions src/utils/formatter/commands/fun.typespeed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import { Command } from 'detritus-client';
import { Timers } from 'detritus-utils';

import { compareTwoStrings } from 'string-similarity';
import { randomInt } from 'mathjs';

import { utilitiesImagescriptV1 } from '../../../api';
import { editOrReply, randomMultipleFromArray } from '../..';
import { BooleanEmojis } from '../../../constants';
import ServerExecutionsStore from '../../../stores/serverexecutions';


export const COMMAND_ID = 'fun.typespeed';


export interface CommandArgs {
dates?: boolean,
words?: boolean,
}


interface Winner {
accuracy: string,
time: string,
wpm: string,
}


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();
} 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;
}

// 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 image = await utilitiesImagescriptV1(context, { code });
const filename: string = image.file.filename;
let data: Buffer | string = (
(image.file.value)
? Buffer.from(image.file.value, 'base64')
: Buffer.alloc(0)
);

const initial = await editOrReply(context, 'Race will begin in 5 seconds...');
await Timers.sleep(5000);
await initial.edit({
file: { filename: filename, value: data },
content: `Type the text below as fast and accurately as you can`,
allowedMentions: { repliedUser: false }
});

const start = Date.now();
const winners: Record<string, Winner> = {};

const sub = context.client.subscribe('messageCreate', async (msg) => {
const message = msg.message;

if (message.author.bot) return;
if (message.author.id in winners) return;
if (message.channelId !== context.channelId) return;

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) / 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 () => {
store.typespeed = false;
sub.remove();
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(', ');
}
2 changes: 2 additions & 0 deletions src/utils/formatter/commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -279,6 +280,7 @@ export {
FunReverseText,
FunTextwall,
FunTTS,
FunTypeSpeed,

InfoAvatar,
InfoUser,
Expand Down
14 changes: 14 additions & 0 deletions src/utils/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2195,6 +2195,20 @@ export function randomFromArray<T>(
}


export function randomMultipleFromArray<T>(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<T>(
size: number,
iterator: IterableIterator<T>,
Expand Down