So far so good!

This commit is contained in:
Atridad Lahiji 2023-12-26 18:39:21 -07:00
parent dc167b4292
commit b861029b4d
No known key found for this signature in database
33 changed files with 228 additions and 969 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

View file

@ -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

View file

@ -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 }}

32
.gitignore vendored
View file

@ -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
.env

4
.npmrc
View file

@ -1,4 +0,0 @@
# pnpm only
shamefully-hoist=true
auto-install-peers=true
public-hoist-pattern[]=@sapphire/*

View file

@ -1,4 +0,0 @@
dist/
node_modules/
.yarn/
examples/*/dist/

View file

@ -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": ""
}
}

View file

@ -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" ]

BIN
bun.lockb

Binary file not shown.

13
go.mod Normal file
View file

@ -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
)

40
go.sum Normal file
View file

@ -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=

174
main.go Normal file
View file

@ -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 */ },
}
}

View file

@ -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"
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,4 +0,0 @@
import { join } from 'path';
export const rootDir = join(__dirname, '..', '..');
export const srcDir = join(rootDir, 'src');

View file

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

View file

@ -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<typeof getSuccessLoggerData>;
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)}]`;
}

View file

@ -1,23 +0,0 @@
import type { ChatInputCommandDeniedPayload, Events } from '@sapphire/framework';
import { Listener, UserError } from '@sapphire/framework';
export class UserEvent extends Listener<typeof Events.ChatInputCommandDenied> {
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
});
}
}

View file

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

View file

@ -1,23 +0,0 @@
import type { ContextMenuCommandDeniedPayload, Events } from '@sapphire/framework';
import { Listener, UserError } from '@sapphire/framework';
export class UserEvent extends Listener<typeof Events.ContextMenuCommandDenied> {
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
});
}
}

View file

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

View file

@ -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<typeof Events.MentionPrefixOnly> {
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.');
}
}

View file

@ -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<Listener.Options>({ 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<any>) {
return gray(`${'└─'} Loaded ${this.style(store.size.toString().padEnd(3, ' '))} ${store.name}.`);
}
}
void container.stores.loadPiece({
piece: UserEvent,
name: 'ready',
store: 'listeners'
});

View file

@ -1,10 +0,0 @@
{
"extends": "@sapphire/ts-config",
"compilerOptions": {
"ignoreDeprecations": "5.0",
"rootDir": "src",
"outDir": "dist",
"tsBuildInfoFile": "dist/.tsbuildinfo"
},
"include": ["src"]
}