diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..05d6da1 Binary files /dev/null and b/.DS_Store differ diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 602bf0a..0000000 --- a/.dockerignore +++ /dev/null @@ -1,31 +0,0 @@ -# Ignore a blackhole and the folder for development -node_modules/ -.vs/ -.idea/ -*.iml - -# Yarn files -.yarn/install-state.gz -.yarn/build-state.yml - -# Environment variables -.DS_Store - -dist/ - -# Ignore the config file (contains sensitive information such as tokens) -config.ts - -# Ignore heapsnapshot and log files -*.heapsnapshot -*.log - -# Ignore npm lockfiles file -package-lock.json - -# Environment variables -.env -.env.local -.env.development.local -.env.test.local -.env.production.local \ No newline at end of file diff --git a/src/.env.example b/.env.example similarity index 100% rename from src/.env.example rename to .env.example diff --git a/.github/workflows/fly.yml b/.github/workflows/fly.yml deleted file mode 100644 index c2caa68..0000000 --- a/.github/workflows/fly.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: Fly Deploy -on: - push: - branches: - - main -jobs: - deploy: - name: Deploy app - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: superfly/flyctl-actions/setup-flyctl@master - - run: flyctl deploy --remote-only - env: - FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} diff --git a/.gitignore b/.gitignore index 602bf0a..2eea525 100644 --- a/.gitignore +++ b/.gitignore @@ -1,31 +1 @@ -# Ignore a blackhole and the folder for development -node_modules/ -.vs/ -.idea/ -*.iml - -# Yarn files -.yarn/install-state.gz -.yarn/build-state.yml - -# Environment variables -.DS_Store - -dist/ - -# Ignore the config file (contains sensitive information such as tokens) -config.ts - -# Ignore heapsnapshot and log files -*.heapsnapshot -*.log - -# Ignore npm lockfiles file -package-lock.json - -# Environment variables -.env -.env.local -.env.development.local -.env.test.local -.env.production.local \ No newline at end of file +.env \ No newline at end of file diff --git a/.npmrc b/.npmrc deleted file mode 100644 index 4dde0ab..0000000 --- a/.npmrc +++ /dev/null @@ -1,4 +0,0 @@ -# pnpm only -shamefully-hoist=true -auto-install-peers=true -public-hoist-pattern[]=@sapphire/* \ No newline at end of file diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index 24ea8ac..0000000 --- a/.prettierignore +++ /dev/null @@ -1,4 +0,0 @@ -dist/ -node_modules/ -.yarn/ -examples/*/dist/ diff --git a/.sapphirerc.json b/.sapphirerc.json deleted file mode 100644 index 3a901e3..0000000 --- a/.sapphirerc.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "projectLanguage": "ts", - "locations": { - "base": "src", - "arguments": "arguments", - "commands": "commands", - "listeners": "listeners", - "preconditions": "preconditions", - "interaction-handlers": "interaction-handlers", - "routes": "routes" - }, - "customFileTemplates": { - "enabled": false, - "location": "" - } -} diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index c664ef2..0000000 --- a/Dockerfile +++ /dev/null @@ -1,37 +0,0 @@ -# syntax = docker/dockerfile:1 - -# Adjust NODE_VERSION as desired -ARG BUN_VERSION=1.0.20 -FROM oven/bun:${BUN_VERSION} as base - -LABEL fly_launch_runtime="Bun" - -# Node.js app lives here -WORKDIR /app - -# Set production environment -ENV NODE_ENV="production" - -# Throw-away build stage to reduce size of final image -FROM base as build - -# Install packages needed to build node modules -RUN apt-get update -qq && \ - apt-get install -y build-essential pkg-config python-is-python3 - -# Install node modules -COPY --link .npmrc package.json bun.lockb ./ -RUN bun install --ci - -# Copy application code -COPY --link . . - -# Final stage for app image -FROM base - -# Copy built application -COPY --from=build /app /app - -# Start the server by default, this can be overwritten at runtime -EXPOSE 3000 -CMD [ "bun", "start" ] diff --git a/bun.lockb b/bun.lockb deleted file mode 100755 index 64484fa..0000000 Binary files a/bun.lockb and /dev/null differ diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..d726ae7 --- /dev/null +++ b/go.mod @@ -0,0 +1,13 @@ +module himbot + +go 1.21.5 + +require github.com/diamondburned/arikawa/v3 v3.3.4 + +require ( + github.com/gorilla/schema v1.2.0 // indirect + github.com/gorilla/websocket v1.4.2 // indirect + github.com/joho/godotenv v1.5.1 + github.com/sashabaranov/go-openai v1.17.9 + golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..faeba57 --- /dev/null +++ b/go.sum @@ -0,0 +1,40 @@ +github.com/diamondburned/arikawa/v3 v3.3.4 h1:UXOjM7PRlWLJ8kVAydX/VetqV7W4/d4xU92JRy3SpU4= +github.com/diamondburned/arikawa/v3 v3.3.4/go.mod h1:5KMSeB9R2Kzi6K4EcqMz7mwAFpAi5jglX/Veq0+MPOo= +github.com/gorilla/schema v1.2.0 h1:YufUaxZYCKGFuAq3c96BOhjgd5nmXiOY9NGzF247Tsc= +github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/sashabaranov/go-openai v1.17.9 h1:QEoBiGKWW68W79YIfXWEFZ7l5cEgZBV4/Ow3uy+5hNY= +github.com/sashabaranov/go-openai v1.17.9/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/main.go b/main.go new file mode 100644 index 0000000..24a02eb --- /dev/null +++ b/main.go @@ -0,0 +1,174 @@ +package main + +import ( + "context" + "fmt" + "log" + "math/rand" + "os" + "os/signal" + "time" + + "github.com/diamondburned/arikawa/v3/api" + "github.com/diamondburned/arikawa/v3/api/cmdroute" + "github.com/diamondburned/arikawa/v3/discord" + "github.com/diamondburned/arikawa/v3/gateway" + "github.com/diamondburned/arikawa/v3/state" + "github.com/diamondburned/arikawa/v3/utils/json/option" + "github.com/joho/godotenv" + openai "github.com/sashabaranov/go-openai" +) + +var commands = []api.CreateCommandData{ + { + Name: "ping", + Description: "ping pong!", + }, + { + Name: "echo", + Description: "echo back the argument", + Options: []discord.CommandOption{ + &discord.StringOption{ + OptionName: "argument", + Description: "what's echoed back", + Required: true, + }, + }, + }, + { + Name: "thonk", + Description: "biiiig thonk", + }, + { + Name: "ask", + Description: "Ask Himbot!", + Options: []discord.CommandOption{ + &discord.StringOption{ + OptionName: "argument", + Description: "the prompt", + Required: true, + }, + }, + }, +} + +func main() { + godotenv.Load(".env") + + token := os.Getenv("DISCORD_TOKEN") + if token == "" { + log.Fatalln("No $DISCORD_TOKEN given.") + } + + h := newHandler(state.New("Bot " + token)) + h.s.AddInteractionHandler(h) + h.s.AddIntents(gateway.IntentGuilds) + h.s.AddHandler(func(*gateway.ReadyEvent) { + me, _ := h.s.Me() + log.Println("connected to the gateway as", me.Tag()) + }) + + if err := cmdroute.OverwriteCommands(h.s, commands); err != nil { + log.Fatalln("cannot update commands:", err) + } + + ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) + defer cancel() + + if err := h.s.Connect(ctx); err != nil { + log.Fatalln("cannot connect:", err) + } +} + +type handler struct { + *cmdroute.Router + s *state.State +} + +func newHandler(s *state.State) *handler { + h := &handler{s: s} + + h.Router = cmdroute.NewRouter() + // Automatically defer handles if they're slow. + h.Use(cmdroute.Deferrable(s, cmdroute.DeferOpts{})) + h.AddFunc("ping", h.cmdPing) + h.AddFunc("echo", h.cmdEcho) + h.AddFunc("thonk", h.cmdThonk) + h.AddFunc("ask", h.cmdAsk) + + return h +} + +func (h *handler) cmdPing(ctx context.Context, cmd cmdroute.CommandData) *api.InteractionResponseData { + return &api.InteractionResponseData{ + Content: option.NewNullableString("Pong!"), + } +} + +func (h *handler) cmdEcho(ctx context.Context, data cmdroute.CommandData) *api.InteractionResponseData { + var options struct { + Arg string `discord:"argument"` + } + + if err := data.Options.Unmarshal(&options); err != nil { + return errorResponse(err) + } + + return &api.InteractionResponseData{ + Content: option.NewNullableString(options.Arg), + AllowedMentions: &api.AllowedMentions{}, // don't mention anyone + } +} + +func (h *handler) cmdAsk(ctx context.Context, data cmdroute.CommandData) *api.InteractionResponseData { + var options struct { + Arg string `discord:"argument"` + } + + if err := data.Options.Unmarshal(&options); err != nil { + return errorResponse(err) + } + + client := openai.NewClient(os.Getenv("OPENAI_API_KEY")) + + resp, err := client.CreateChatCompletion( + context.Background(), + openai.ChatCompletionRequest{ + Model: openai.GPT4TurboPreview, + Messages: []openai.ChatCompletionMessage{ + { + Role: openai.ChatMessageRoleUser, + Content: options.Arg, + }, + }, + }, + ) + + if err != nil { + fmt.Printf("ChatCompletion error: %v\n", err) + return &api.InteractionResponseData{ + Content: option.NewNullableString("ChatCompletion Error!"), + AllowedMentions: &api.AllowedMentions{}, // don't mention anyone + } + } + + return &api.InteractionResponseData{ + Content: option.NewNullableString(resp.Choices[0].Message.Content), + AllowedMentions: &api.AllowedMentions{}, // don't mention anyone + } +} + +func (h *handler) cmdThonk(ctx context.Context, data cmdroute.CommandData) *api.InteractionResponseData { + time.Sleep(time.Duration(3+rand.Intn(5)) * time.Second) + return &api.InteractionResponseData{ + Content: option.NewNullableString("https://tenor.com/view/thonk-thinking-sun-thonk-sun-thinking-sun-gif-14999983"), + } +} + +func errorResponse(err error) *api.InteractionResponseData { + return &api.InteractionResponseData{ + Content: option.NewNullableString("**Error:** " + err.Error()), + Flags: discord.EphemeralMessage, + AllowedMentions: &api.AllowedMentions{ /* none */ }, + } +} diff --git a/package.json b/package.json deleted file mode 100644 index c85c057..0000000 --- a/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "himbot", - "version": "4.2.0", - "engines": { - "node": ">=18.12.1" - }, - "main": "dist/index.js", - "author": "@sapphire", - "license": "UNLICENSE", - "type": "commonjs", - "dependencies": { - "@sapphire/decorators": "^6.0.3", - "@sapphire/discord.js-utilities": "7.1.4", - "@sapphire/framework": "^5.0.4", - "@sapphire/plugin-logger": "^4.0.1", - "@sapphire/utilities": "^3.14.0", - "@skyra/env-utilities": "^1.2.2", - "colorette": "^2.0.20", - "discord.js": "^14.14.1", - "openai": "^4.20.1", - "replicate": "^0.25.0" - }, - "devDependencies": { - "@flydotio/dockerfile": "0.5.0", - "@sapphire/cli": "^1.9.1", - "@sapphire/prettier-config": "^2.0.0", - "@sapphire/ts-config": "^5.0.0", - "@types/node": "^20.10.4", - "@types/ws": "^8.5.10", - "prettier": "^3.1.1", - "tsc-watch": "^6.0.4", - "typescript": "^5.3.3" - }, - "scripts": { - "sapphire": "sapphire", - "generate": "sapphire generate", - "watch": "tsc -w", - "start": "bun run src/index.ts", - "dev": "bun run src/index.ts", - "watch:start": "tsc-watch --onSuccess \"npm run start\"", - "format": "prettier --write \"src/\"" - }, - "prettier": "@sapphire/prettier-config" -} diff --git a/src/commands/ask.ts b/src/commands/ask.ts deleted file mode 100644 index 19fb2b7..0000000 --- a/src/commands/ask.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { Command, container } from '@sapphire/framework'; -import { AttachmentBuilder, blockQuote, codeBlock } from 'discord.js'; -import OpenAI from 'openai'; - -const openai = new OpenAI({ - apiKey: process.env.OPENAI_API_KEY -}); - -export class AskCommand extends Command { - public constructor(context: Command.LoaderContext) { - super(context, { - description: 'You can ACTUALLY ask Himbot something! So cool!', - options: ['prompt'] - }); - } - - public override registerApplicationCommands(registry: Command.Registry) { - registry.registerChatInputCommand((builder) => - builder - .setName(this.name) - .setDescription(this.description) - .addStringOption((option) => - option.setName('prompt').setDescription('You can ACTUALLY ask Himbot something! So cool!').setRequired(true) - ) - ); - } - - public async chatInputRun(interaction: Command.ChatInputCommandInteraction) { - const prompt = interaction.options.getString('prompt'); - - await interaction.reply({ content: '🤔 Thinking... 🤔', fetchReply: true }); - - const chatCompletion = await openai.chat.completions.create({ - model: 'gpt-4-1106-preview', - messages: [ - { - role: 'user', - content: prompt - } - ] - }); - - const content = blockQuote(`> ${prompt}\n${codeBlock(`${chatCompletion.choices[0].message?.content}`)}`); - - const messageAttachment: AttachmentBuilder[] = []; - - if (content.length > 2000) { - messageAttachment.push( - new AttachmentBuilder(Buffer.from(`> ${prompt}\n${`${chatCompletion.choices[0].message?.content}`}`, 'utf-8'), { - name: 'response.txt', - description: "Himbot's Response" - }) - ); - } - - return interaction.editReply({ - content: - content.length < 2000 - ? content - : `Discord only allows messages with 2000 characters or less. Please see your response in the attached file!`, - files: messageAttachment - }); - } -} - -void container.stores.loadPiece({ - store: 'commands', - name: 'ask', - piece: AskCommand -}); diff --git a/src/commands/borf.ts b/src/commands/borf.ts deleted file mode 100644 index c97705f..0000000 --- a/src/commands/borf.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Command, container } from '@sapphire/framework'; - -export class BorfCommand extends Command { - public constructor(context: Command.LoaderContext) { - super(context, { - description: 'Borf! Borf!' - }); - } - - // Register Chat Input and Context Menu command - public override registerApplicationCommands(registry: Command.Registry) { - // Register Chat Input command - registry.registerChatInputCommand({ - name: this.name, - description: this.description - }); - } - - // Chat Input (slash) command - public async chatInputRun(interaction: Command.ChatInputCommandInteraction) { - const dogResponse = await fetch('https://dog.ceo/api/breeds/image/random'); - const dogData = (await dogResponse.json()) as { message: string; status: string }; - - await interaction.reply({ - content: dogData.status === 'success' ? dogData.message : 'Error: I had troubles fetching perfect puppies for you... :(', - fetchReply: true - }); - } -} - -void container.stores.loadPiece({ - store: 'commands', - name: 'borf', - piece: BorfCommand -}); diff --git a/src/commands/hdpic.ts b/src/commands/hdpic.ts deleted file mode 100644 index 7fa7e79..0000000 --- a/src/commands/hdpic.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { BucketScope, Command, container } from '@sapphire/framework'; -import { AttachmentBuilder } from 'discord.js'; -import OpenAI from 'openai'; - -const openai = new OpenAI({ - apiKey: process.env.OPENAI_API_KEY -}); - -export class HDPicCommand extends Command { - public constructor(context: Command.LoaderContext) { - super(context, { - description: 'Generate an image using OpenAI! Cooldown of 10 Minutes due to cost!', - options: ['prompt'], - cooldownDelay: 480_000, - cooldownLimit: 1, - cooldownFilteredUsers: ['83679718401904640'], - cooldownScope: BucketScope.User - }); - } - - // Register Chat Input and Context Menu command - public override registerApplicationCommands(registry: Command.Registry) { - registry.registerChatInputCommand((builder) => - builder - .setName(this.name) - .setDescription(this.description) - .addStringOption((option) => - option.setName('prompt').setDescription('The prompt you will use to generate an image!').setRequired(true) - ) - ); - } - - // Chat Input (slash) command - public async chatInputRun(interaction: Command.ChatInputCommandInteraction) { - const prompt = interaction.options.getString('prompt') || ''; - - await interaction.reply({ content: '🤔 Thinking... 🤔', fetchReply: true }); - - try { - const response = await openai.images.generate({ - model: 'dall-e-3', - prompt, - n: 1, - size: '1024x1024', - quality: 'standard' - }); - - const imageUrl = response.data[0].url || ''; - // get an array buffer - const imageBuffer = await fetch(imageUrl).then((r) => r.arrayBuffer()); - - const imageAttachment: AttachmentBuilder[] = []; - - imageAttachment.push( - new AttachmentBuilder(Buffer.from(new Uint8Array(imageBuffer)), { - name: 'himbot_response.jpg', - description: `An image generated by Himbot using the prompt: ${prompt}` - }) - ); - - const content = `Prompt: ${prompt}:`; - - return interaction.editReply({ - content, - files: imageAttachment - }); - } catch (error) { - const content = "Sorry, I can't complete the prompt for: " + prompt + '\n' + error; - - return interaction.editReply({ content }); - } - } -} - -void container.stores.loadPiece({ - store: 'commands', - name: 'hdpic', - piece: HDPicCommand -}); diff --git a/src/commands/hdtts.ts b/src/commands/hdtts.ts deleted file mode 100644 index 10cc4f7..0000000 --- a/src/commands/hdtts.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { BucketScope, Command, container } from '@sapphire/framework'; -import { AttachmentBuilder, MessageFlags } from 'discord.js'; -import OpenAI from 'openai'; - -const openai = new OpenAI({ - apiKey: process.env.OPENAI_API_KEY -}); - -export class HDTTSCommand extends Command { - public constructor(context: Command.LoaderContext) { - super(context, { - description: 'Generate "HD" TTS every 2 minutes!', - options: ['prompt'], - cooldownDelay: 200_000, - cooldownLimit: 1, - // Yes... I did hardcode myself. - cooldownFilteredUsers: ['83679718401904640'], - cooldownScope: BucketScope.User - }); - } - - // Register Chat Input and Context Menu command - public override registerApplicationCommands(registry: Command.Registry) { - registry.registerChatInputCommand((builder) => - builder - .setName(this.name) - .setDescription(this.description) - .addStringOption((option) => option.setName('prompt').setDescription('The prompt you will use to generate audio!').setRequired(true)) - ); - } - - // Chat Input (slash) command - public async chatInputRun(interaction: Command.ChatInputCommandInteraction) { - const prompt = interaction.options.getString('prompt') || 'NOTHING'; - - await interaction.reply({ content: '🤔 Thinking... 🤔', fetchReply: true }); - try { - enum voice { - alloy = 'alloy', - echo = 'echo', - fable = 'fable', - onyx = 'onyx', - nova = 'nova', - shimmer = 'shimmer' - } - - const voices = [voice.alloy, voice.echo, voice.fable, voice.onyx, voice.nova, voice.shimmer]; - const mp3 = await openai.audio.speech.create({ - model: 'tts-1-hd', - voice: voices[Math.floor(Math.random() * voices.length)], - input: prompt - }); - const mp3Buffer = Buffer.from(await mp3.arrayBuffer()); - - const mp3Attachment: AttachmentBuilder[] = []; - - mp3Attachment.push( - new AttachmentBuilder(Buffer.from(new Uint8Array(mp3Buffer)), { - name: 'himbot_response.mp3', - description: `An TTS message generated by Himbot using the prompt: ${prompt}` - }) - ); - - const content = `Prompt: ${prompt}:`; - - return interaction.editReply({ - content, - files: mp3Attachment, - options: { - flags: MessageFlags.IsVoiceMessage.valueOf() - } - }); - } catch (error) { - const content = "Sorry, I can't complete the prompt for: " + prompt + '\n' + error; - - return interaction.editReply({ - content - }); - } - } -} - -void container.stores.loadPiece({ - store: 'commands', - name: 'hdtts', - piece: HDTTSCommand -}); diff --git a/src/commands/hs.ts b/src/commands/hs.ts deleted file mode 100644 index 4c3cc8a..0000000 --- a/src/commands/hs.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Command, container } from '@sapphire/framework'; - -export class HighSchoolCommand extends Command { - public constructor(context: Command.LoaderContext) { - super(context, { - description: 'This command was your nickname in highschool!', - options: ['nickname'] - }); - } - - // Register Chat Input and Context Menu command - public override registerApplicationCommands(registry: Command.Registry) { - registry.registerChatInputCommand((builder) => - builder // - .setName(this.name) - .setDescription(this.description) - .addStringOption((option) => option.setName('nickname').setDescription('Your nickname in highschool.').setRequired(true)) - ); - } - - // Chat Input (slash) command - public async chatInputRun(interaction: Command.ChatInputCommandInteraction) { - const nickname = interaction.options.getString('nickname') || 'NOTHING'; - await interaction.reply({ - content: `${nickname} was ${interaction.user.username}'s nickname in highschool!`, - fetchReply: true - }); - } -} - -void container.stores.loadPiece({ - store: 'commands', - name: 'hs', - piece: HighSchoolCommand -}); diff --git a/src/commands/pic.ts b/src/commands/pic.ts deleted file mode 100644 index dacd611..0000000 --- a/src/commands/pic.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { BucketScope, Command, container } from '@sapphire/framework'; -import { AttachmentBuilder } from 'discord.js'; -import Replicate from 'replicate'; - -const replicate = new Replicate({ - auth: process.env.REPLICATE_API_TOKEN -}); - -export class PicCommand extends Command { - public constructor(context: Command.LoaderContext) { - super(context, { - description: 'Generate an image using Stability AI! Cooldown 1 Minute to prevent spam!', - options: ['prompt'], - cooldownDelay: 100_000, - cooldownLimit: 1, - // Yes... I did hardcode myself. - cooldownFilteredUsers: ['83679718401904640'], - cooldownScope: BucketScope.User - }); - } - - // Register Chat Input and Context Menu command - public override registerApplicationCommands(registry: Command.Registry) { - registry.registerChatInputCommand((builder) => - builder - .setName(this.name) - .setDescription(this.description) - .addStringOption((option) => - option.setName('prompt').setDescription('The prompt you will use to generate an image!').setRequired(true) - ) - ); - } - - // Chat Input (slash) command - public async chatInputRun(interaction: Command.ChatInputCommandInteraction) { - const prompt = interaction.options.getString('prompt') || 'NOTHING'; - - await interaction.reply({ content: '🤔 Thinking... 🤔', fetchReply: true }); - - let result = (await replicate.run('stability-ai/sdxl:39ed52f2a78e934b3ba6e2a89f5b1c712de7dfea535525255b1aa35c5565e08b', { - input: { - width: 1024, - height: 1024, - prompt, - negative_prompt: - 'out of frame, lowres, text, error, cropped, worst quality, low quality, jpeg artifacts, duplicate, morbid, mutilated, out of frame, extra fingers, mutated hands, poorly drawn hands, poorly drawn face, mutation, deformed, blurry, bad anatomy, bad proportions, extra limbs, cloned face, disfigured, malformed limbs, missing arms, missing legs, extra arms, extra legs, fused fingers, too many fingers, long neck, username, watermark, signature.', - disable_safety_checker: true, - refine: 'expert_ensemble_refiner', - scheduler: 'KarrasDPM', - num_outputs: 1, - guidance_scale: 7.5, - high_noise_frac: 0.8, - prompt_strength: 0.8, - num_inference_steps: 50 - } - })) as string[]; - - if (result.length <= 0) { - const content = `Sorry, I can't complete the prompt for: ${prompt}`; - - return interaction.editReply({ - content: content - }); - } else { - const imageUrl = result[0] || ''; - // get an array buffer - const imageBuffer = await fetch(imageUrl).then((r) => r.arrayBuffer()); - - const imageAttachment: AttachmentBuilder[] = []; - - imageAttachment.push( - new AttachmentBuilder(Buffer.from(new Uint8Array(imageBuffer)), { - name: 'himbot_response.jpg', - description: `An image generated by Himbot using the prompt: ${prompt}` - }) - ); - - const content = `Prompt: ${prompt}`; - - return interaction.editReply({ - content, - files: imageAttachment - }); - } - } -} - -void container.stores.loadPiece({ - store: 'commands', - name: 'pic', - piece: PicCommand -}); diff --git a/src/commands/title.ts b/src/commands/title.ts deleted file mode 100644 index c344dab..0000000 --- a/src/commands/title.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Command, container } from '@sapphire/framework'; - -export class TitleCommand extends Command { - public constructor(context: Command.LoaderContext) { - super(context, { - description: 'This command is the title of your sextape.', - options: ['title'] - }); - } - - // Register Chat Input and Context Menu command - public override registerApplicationCommands(registry: Command.Registry) { - registry.registerChatInputCommand((builder) => - builder // - .setName(this.name) - .setDescription(this.description) - .addStringOption((option) => option.setName('title').setDescription('The title of your sextape.').setRequired(true)) - ); - } - - // Chat Input (slash) command - public async chatInputRun(interaction: Command.ChatInputCommandInteraction) { - const title = interaction.options.getString('title') || 'NOTHING'; - - await interaction.reply({ - content: `${title}: Title of ${interaction.user.username}'s sex tape!`, - fetchReply: true - }); - } -} - -void container.stores.loadPiece({ - store: 'commands', - name: 'title', - piece: TitleCommand -}); diff --git a/src/commands/tts.ts b/src/commands/tts.ts deleted file mode 100644 index 8968fd2..0000000 --- a/src/commands/tts.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { BucketScope, Command, container } from '@sapphire/framework'; -import { AttachmentBuilder, MessageFlags } from 'discord.js'; -import OpenAI from 'openai'; - -const openai = new OpenAI({ - apiKey: process.env.OPENAI_API_KEY -}); - -export class TTSCommand extends Command { - public constructor(context: Command.LoaderContext) { - super(context, { - description: 'Generate TTS every minute!', - options: ['prompt'], - cooldownDelay: 100_000, - cooldownLimit: 1, - // Yes... I did hardcode myself. - cooldownFilteredUsers: ['83679718401904640'], - cooldownScope: BucketScope.User - }); - } - - // Register Chat Input and Context Menu command - public override registerApplicationCommands(registry: Command.Registry) { - registry.registerChatInputCommand((builder) => - builder - .setName(this.name) - .setDescription(this.description) - .addStringOption((option) => option.setName('prompt').setDescription('The prompt you will use to generate audio!').setRequired(true)) - ); - } - - // Chat Input (slash) command - public async chatInputRun(interaction: Command.ChatInputCommandInteraction) { - const prompt = interaction.options.getString('prompt') || 'NOTHING'; - - await interaction.reply({ content: '🤔 Thinking... 🤔', fetchReply: true }); - - try { - enum voice { - alloy = 'alloy', - echo = 'echo', - fable = 'fable', - onyx = 'onyx', - nova = 'nova', - shimmer = 'shimmer' - } - - const voices = [voice.alloy, voice.echo, voice.fable, voice.onyx, voice.nova, voice.shimmer]; - const mp3 = await openai.audio.speech.create({ - model: 'tts-1', - voice: voices[Math.floor(Math.random() * voices.length)], - input: prompt - }); - const mp3Buffer = Buffer.from(await mp3.arrayBuffer()); - - const mp3Attachment: AttachmentBuilder[] = []; - - mp3Attachment.push( - new AttachmentBuilder(Buffer.from(new Uint8Array(mp3Buffer)), { - name: 'himbot_response.mp3', - description: `An TTS message generated by Himbot using the prompt: ${prompt}` - }) - ); - - const content = `Prompt: ${prompt}:`; - - return interaction.editReply({ - content, - files: mp3Attachment, - options: { - flags: MessageFlags.IsVoiceMessage.valueOf() - } - }); - } catch (error) { - const content = "Sorry, I can't complete the prompt for: " + prompt + '\n' + error; - - return interaction.editReply({ - content - }); - } - } -} - -void container.stores.loadPiece({ - store: 'commands', - name: 'tts', - piece: TTSCommand -}); diff --git a/src/index.ts b/src/index.ts deleted file mode 100644 index d7c38da..0000000 --- a/src/index.ts +++ /dev/null @@ -1,55 +0,0 @@ -import './lib/setup'; -import './listeners/ready'; -import './commands/ask'; -import './commands/borf'; -import './commands/hdpic'; -import './commands/hdtts'; -import './commands/hs'; -import './commands/pic'; -import './commands/title'; -import './commands/tts'; -import './listeners/commands/chatInputCommands/chatInputCommandSuccess'; -import { LogLevel, SapphireClient, BucketScope } from '@sapphire/framework'; -import { ActivityType, GatewayIntentBits } from 'discord.js'; - -const client = new SapphireClient({ - defaultPrefix: '!', - presence: { - status: 'online', - activities: [ - { - name: 'idk', - type: ActivityType.Custom - } - ] - }, - caseInsensitiveCommands: true, - logger: { - level: LogLevel.Debug - }, - intents: [GatewayIntentBits.DirectMessages, GatewayIntentBits.GuildMessages, GatewayIntentBits.Guilds, GatewayIntentBits.MessageContent], - defaultCooldown: { - // 10s - delay: 10_000, - filteredCommands: ['support', 'ping', 'wryna'], - limit: 2, - // Yes... I did hardcode myself. - filteredUsers: ['83679718401904640'], - scope: BucketScope.User - }, - baseUserDirectory: null -}); - -const main = async () => { - try { - client.logger.info('Logging in'); - await client.login(); - client.logger.info('logged in'); - } catch (error) { - client.logger.fatal(error); - client.destroy(); - process.exit(1); - } -}; - -main(); diff --git a/src/lib/constants.ts b/src/lib/constants.ts deleted file mode 100644 index 97147f0..0000000 --- a/src/lib/constants.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { join } from 'path'; - -export const rootDir = join(__dirname, '..', '..'); -export const srcDir = join(rootDir, 'src'); diff --git a/src/lib/setup.ts b/src/lib/setup.ts deleted file mode 100644 index d4124b3..0000000 --- a/src/lib/setup.ts +++ /dev/null @@ -1,18 +0,0 @@ -// Unless explicitly defined, set NODE_ENV as development: -process.env.NODE_ENV ??= 'development'; - -import { ApplicationCommandRegistries, RegisterBehavior } from '@sapphire/framework'; -import '@sapphire/plugin-logger/register'; -import { setup } from '@skyra/env-utilities'; -import * as colorette from 'colorette'; -import { join } from 'node:path'; -import { srcDir } from './constants'; - -// Set default behavior to bulk overwrite -ApplicationCommandRegistries.setDefaultBehaviorWhenNotIdentical(RegisterBehavior.BulkOverwrite); - -// Read env var -setup({ path: join(srcDir, '.env') }); - -// Enable colorette -colorette.createColors({ useColor: true }); diff --git a/src/lib/utils.ts b/src/lib/utils.ts deleted file mode 100644 index eeba90c..0000000 --- a/src/lib/utils.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { container, type ChatInputCommandSuccessPayload, type Command, type ContextMenuCommandSuccessPayload } from '@sapphire/framework'; -import { cyan } from 'colorette'; -import type { APIUser, Guild, User } from 'discord.js'; - -export function logSuccessCommand(payload: ContextMenuCommandSuccessPayload | ChatInputCommandSuccessPayload): void { - let successLoggerData: ReturnType; - - successLoggerData = getSuccessLoggerData(payload.interaction.guild, payload.interaction.user, payload.command); - - container.logger.debug(`${successLoggerData.shard} - ${successLoggerData.commandName} ${successLoggerData.author} ${successLoggerData.sentAt}`); -} - -export function getSuccessLoggerData(guild: Guild | null, user: User, command: Command) { - const shard = getShardInfo(guild?.shardId ?? 0); - const commandName = getCommandInfo(command); - const author = getAuthorInfo(user); - const sentAt = getGuildInfo(guild); - - return { shard, commandName, author, sentAt }; -} - -function getShardInfo(id: number) { - return `[${cyan(id.toString())}]`; -} - -function getCommandInfo(command: Command) { - return cyan(command.name); -} - -function getAuthorInfo(author: User | APIUser) { - return `${author.username}[${cyan(author.id)}]`; -} - -function getGuildInfo(guild: Guild | null) { - if (guild === null) return 'Direct Messages'; - return `${guild.name}[${cyan(guild.id)}]`; -} diff --git a/src/listeners/commands/chatInputCommands/chatInputCommandDenied.ts b/src/listeners/commands/chatInputCommands/chatInputCommandDenied.ts deleted file mode 100644 index 7c5f96a..0000000 --- a/src/listeners/commands/chatInputCommands/chatInputCommandDenied.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { ChatInputCommandDeniedPayload, Events } from '@sapphire/framework'; -import { Listener, UserError } from '@sapphire/framework'; - -export class UserEvent extends Listener { - public async run({ context, message: content }: UserError, { interaction }: ChatInputCommandDeniedPayload) { - // `context: { silent: true }` should make UserError silent: - // Use cases for this are for example permissions error when running the `eval` command. - if (Reflect.get(Object(context), 'silent')) return; - - if (interaction.deferred || interaction.replied) { - return interaction.editReply({ - content, - allowedMentions: { users: [interaction.user.id], roles: [] } - }); - } - - return interaction.reply({ - content, - allowedMentions: { users: [interaction.user.id], roles: [] }, - ephemeral: true - }); - } -} diff --git a/src/listeners/commands/chatInputCommands/chatInputCommandSuccess.ts b/src/listeners/commands/chatInputCommands/chatInputCommandSuccess.ts deleted file mode 100644 index 039cd9a..0000000 --- a/src/listeners/commands/chatInputCommands/chatInputCommandSuccess.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Listener, LogLevel, type ChatInputCommandSuccessPayload } from '@sapphire/framework'; -import type { Logger } from '@sapphire/plugin-logger'; -import { logSuccessCommand } from '../../../lib/utils'; - -export class UserListener extends Listener { - public run(payload: ChatInputCommandSuccessPayload) { - logSuccessCommand(payload); - } - - public onLoad() { - this.enabled = (this.container.logger as Logger).level <= LogLevel.Debug; - return super.onLoad(); - } -} diff --git a/src/listeners/commands/contextMenuCommands/contextMenuCommandDenied.ts b/src/listeners/commands/contextMenuCommands/contextMenuCommandDenied.ts deleted file mode 100644 index 6ec16a7..0000000 --- a/src/listeners/commands/contextMenuCommands/contextMenuCommandDenied.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { ContextMenuCommandDeniedPayload, Events } from '@sapphire/framework'; -import { Listener, UserError } from '@sapphire/framework'; - -export class UserEvent extends Listener { - public async run({ context, message: content }: UserError, { interaction }: ContextMenuCommandDeniedPayload) { - // `context: { silent: true }` should make UserError silent: - // Use cases for this are for example permissions error when running the `eval` command. - if (Reflect.get(Object(context), 'silent')) return; - - if (interaction.deferred || interaction.replied) { - return interaction.editReply({ - content, - allowedMentions: { users: [interaction.user.id], roles: [] } - }); - } - - return interaction.reply({ - content, - allowedMentions: { users: [interaction.user.id], roles: [] }, - ephemeral: true - }); - } -} diff --git a/src/listeners/commands/contextMenuCommands/contextMenuCommandSuccess.ts b/src/listeners/commands/contextMenuCommands/contextMenuCommandSuccess.ts deleted file mode 100644 index 8253a74..0000000 --- a/src/listeners/commands/contextMenuCommands/contextMenuCommandSuccess.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Listener, LogLevel, type ContextMenuCommandSuccessPayload } from '@sapphire/framework'; -import type { Logger } from '@sapphire/plugin-logger'; -import { logSuccessCommand } from '../../../lib/utils'; - -export class UserListener extends Listener { - public run(payload: ContextMenuCommandSuccessPayload) { - logSuccessCommand(payload); - } - - public onLoad() { - this.enabled = (this.container.logger as Logger).level <= LogLevel.Debug; - return super.onLoad(); - } -} diff --git a/src/listeners/mentionPrefixOnly.ts b/src/listeners/mentionPrefixOnly.ts deleted file mode 100644 index 88974a6..0000000 --- a/src/listeners/mentionPrefixOnly.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { Events } from '@sapphire/framework'; -import { Listener } from '@sapphire/framework'; -import type { Message } from 'discord.js'; - -export class UserEvent extends Listener { - public async run(message: Message) { - const prefix = this.container.client.options.defaultPrefix; - return message.channel.send(prefix ? `My prefix in this guild is: \`${prefix}\`` : 'Cannot find any Prefix for Message Commands.'); - } -} diff --git a/src/listeners/ready.ts b/src/listeners/ready.ts deleted file mode 100644 index f7ab85a..0000000 --- a/src/listeners/ready.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { ApplyOptions } from '@sapphire/decorators'; -import { Listener, Store, container } from '@sapphire/framework'; -import { blue, gray, green, magenta, magentaBright, white, yellow } from 'colorette'; - -const dev = process.env.NODE_ENV !== 'production'; - -// @ts-ignore -@ApplyOptions({ once: true }) -export class UserEvent extends Listener { - private readonly style = dev ? yellow : blue; - - public run() { - this.printBanner(); - this.printStoreDebugInformation(); - } - - private printBanner() { - const success = green('+'); - - const llc = dev ? magentaBright : white; - const blc = dev ? magenta : blue; - - const line01 = llc(''); - const line02 = llc(''); - const line03 = llc(''); - - // Offset Pad - const pad = ' '.repeat(7); - - console.log( - String.raw` -${line01} ${pad}${blc('1.0.0')} -${line02} ${pad}[${success}] Gateway -${line03}${dev ? ` ${pad}${blc('<')}${llc('/')}${blc('>')} ${llc('DEVELOPMENT MODE')}` : ''} - `.trim() - ); - } - - private printStoreDebugInformation() { - const { client, logger } = this.container; - const stores = [...client.stores.values()]; - const last = stores.pop()!; - - for (const store of stores) logger.info(this.styleStore(store)); - logger.info(this.styleStore(last)); - } - - private styleStore(store: Store) { - return gray(`${'└─'} Loaded ${this.style(store.size.toString().padEnd(3, ' '))} ${store.name}.`); - } -} - -void container.stores.loadPiece({ - piece: UserEvent, - name: 'ready', - store: 'listeners' -}); diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index a7a61d0..0000000 --- a/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "@sapphire/ts-config", - "compilerOptions": { - "ignoreDeprecations": "5.0", - "rootDir": "src", - "outDir": "dist", - "tsBuildInfoFile": "dist/.tsbuildinfo" - }, - "include": ["src"] -}