2023-12-26 18:39:21 -07:00
package main
import (
2023-12-27 10:52:24 -07:00
"bytes"
2023-12-26 18:39:21 -07:00
"context"
2023-12-27 21:48:55 -07:00
"errors"
2023-12-26 18:39:21 -07:00
"fmt"
2023-12-27 15:02:23 -07:00
"himbot/lib"
2023-12-27 10:52:24 -07:00
"io"
2023-12-26 18:39:21 -07:00
"log"
2023-12-27 10:52:24 -07:00
"net/http"
2023-12-26 18:39:21 -07:00
"os"
"os/signal"
2024-01-03 14:32:37 -07:00
"time"
2023-12-26 18:39:21 -07:00
"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"
2023-12-27 10:52:24 -07:00
"github.com/diamondburned/arikawa/v3/utils/sendpart"
2023-12-26 18:39:21 -07:00
"github.com/joho/godotenv"
2023-12-27 10:52:24 -07:00
"github.com/replicate/replicate-go"
2023-12-26 18:39:21 -07:00
openai "github.com/sashabaranov/go-openai"
)
var commands = [ ] api . CreateCommandData {
{
Name : "ping" ,
Description : "ping pong!" ,
} ,
{
2023-12-27 10:52:24 -07:00
Name : "ask" ,
2023-12-27 15:02:23 -07:00
Description : "Ask Himbot! Cooldown: 1 Minute." ,
2023-12-26 18:39:21 -07:00
Options : [ ] discord . CommandOption {
& discord . StringOption {
2023-12-27 10:52:24 -07:00
OptionName : "prompt" ,
Description : "The prompt to send to Himbot." ,
2023-12-26 18:39:21 -07:00
Required : true ,
} ,
} ,
} ,
{
2023-12-27 10:52:24 -07:00
Name : "pic" ,
2023-12-27 15:02:23 -07:00
Description : "Generate an image using Stable Diffusion! Cooldown: 1 Minute." ,
2023-12-27 10:52:24 -07:00
Options : [ ] discord . CommandOption {
& discord . StringOption {
OptionName : "prompt" ,
Description : "The prompt for the image generation." ,
Required : true ,
} ,
} ,
2023-12-26 18:39:21 -07:00
} ,
{
2023-12-27 10:52:24 -07:00
Name : "hdpic" ,
2023-12-27 15:02:23 -07:00
Description : "Generate an image using DALL·E 3! Cooldown: 10 Minutes." ,
2023-12-26 18:39:21 -07:00
Options : [ ] discord . CommandOption {
& discord . StringOption {
2023-12-27 10:52:24 -07:00
OptionName : "prompt" ,
Description : "The prompt for the image generation." ,
Required : true ,
} ,
} ,
} ,
{
Name : "hs" ,
Description : "This command was your nickname in highschool!" ,
Options : [ ] discord . CommandOption {
& discord . StringOption {
OptionName : "nickname" ,
Description : "Your nickname in highschool." ,
2023-12-26 18:39:21 -07:00
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 ( "ask" , h . cmdAsk )
2023-12-27 10:52:24 -07:00
h . AddFunc ( "pic" , h . cmdPic )
h . AddFunc ( "hdpic" , h . cmdHDPic )
h . AddFunc ( "hs" , h . cmdHS )
2023-12-26 18:39:21 -07:00
return h
}
2023-12-27 10:52:24 -07:00
func ( h * handler ) cmdPing ( ctx context . Context , data cmdroute . CommandData ) * api . InteractionResponseData {
2023-12-27 12:47:14 -07:00
// Command Logic
2023-12-26 18:39:21 -07:00
return & api . InteractionResponseData {
Content : option . NewNullableString ( "Pong!" ) ,
}
}
func ( h * handler ) cmdAsk ( ctx context . Context , data cmdroute . CommandData ) * api . InteractionResponseData {
2023-12-27 12:47:14 -07:00
// Cooldown Logic
2024-01-03 14:32:37 -07:00
allowed := lib . CooldownHandler ( * data . Event , "ask" , time . Minute )
2023-12-27 15:29:37 -07:00
2023-12-29 01:31:36 -07:00
if ! allowed {
2023-12-27 21:48:55 -07:00
return errorResponse ( errors . New ( "please wait for the cooldown" ) )
2023-12-27 15:02:23 -07:00
}
2023-12-27 12:47:14 -07:00
// Command Logic
2023-12-26 18:39:21 -07:00
var options struct {
2023-12-29 14:12:11 -07:00
Prompt string ` discord:"prompt" `
2023-12-26 18:39:21 -07:00
}
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 ,
2023-12-29 14:12:11 -07:00
Content : options . Prompt ,
2023-12-26 18:39:21 -07:00
} ,
} ,
} ,
)
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
}
}
2023-12-29 14:12:11 -07:00
respString := resp . Choices [ 0 ] . Message . Content
if len ( respString ) > 1800 {
textFile := bytes . NewBuffer ( [ ] byte ( respString ) )
file := sendpart . File {
Name : "himbot_response.txt" ,
Reader : textFile ,
}
return & api . InteractionResponseData {
Content : option . NewNullableString ( "Prompt: " + options . Prompt + "\n" + "Response:\n" ) ,
AllowedMentions : & api . AllowedMentions { } , // don't mention anyone
Files : [ ] sendpart . File { file } ,
}
}
2023-12-26 18:39:21 -07:00
return & api . InteractionResponseData {
2023-12-29 14:12:11 -07:00
Content : option . NewNullableString ( "Prompt: " + options . Prompt + "\n" + "Response: " + respString ) ,
2023-12-26 18:39:21 -07:00
AllowedMentions : & api . AllowedMentions { } , // don't mention anyone
}
}
2023-12-27 10:52:24 -07:00
func ( h * handler ) cmdPic ( ctx context . Context , data cmdroute . CommandData ) * api . InteractionResponseData {
2023-12-27 12:47:14 -07:00
// Cooldown Logic
2024-01-03 14:32:37 -07:00
allowed := lib . CooldownHandler ( * data . Event , "pic" , time . Minute )
2023-12-27 15:29:37 -07:00
2023-12-29 01:31:36 -07:00
if ! allowed {
2023-12-27 21:48:55 -07:00
return errorResponse ( errors . New ( "please wait for the cooldown" ) )
2023-12-27 15:02:23 -07:00
}
2023-12-27 12:47:14 -07:00
// Command Logic
2023-12-27 10:52:24 -07:00
var options struct {
Prompt string ` discord:"prompt" `
}
if err := data . Options . Unmarshal ( & options ) ; err != nil {
return errorResponse ( err )
}
client , clientError := replicate . NewClient ( replicate . WithTokenFromEnv ( ) )
if clientError != nil {
return errorResponse ( clientError )
}
if err := data . Options . Unmarshal ( & options ) ; err != nil {
return errorResponse ( err )
}
input := replicate . PredictionInput {
"prompt" : options . Prompt ,
}
webhook := replicate . Webhook {
URL : "https://example.com/webhook" ,
Events : [ ] replicate . WebhookEventType { "start" , "completed" } ,
}
prediction , predictionError := client . Run ( context . Background ( ) , "stability-ai/sdxl:39ed52f2a78e934b3ba6e2a89f5b1c712de7dfea535525255b1aa35c5565e08b" , input , & webhook )
if predictionError != nil {
return errorResponse ( predictionError )
}
test , ok := prediction . ( [ ] interface { } )
if ! ok {
2024-01-07 14:36:15 -07:00
return errorResponse ( errors . New ( "there was an error generating the image based on this prompt... this usually happens when the generated image violates safety requirements" ) )
2023-12-27 10:52:24 -07:00
}
imgUrl , ok := test [ 0 ] . ( string )
if ! ok {
2024-01-07 14:36:15 -07:00
return errorResponse ( errors . New ( "there was an error generating the image based on this prompt... this usually happens when the generated image violates safety requirements" ) )
2023-12-27 10:52:24 -07:00
}
imageRes , imageGetErr := http . Get ( imgUrl )
if imageGetErr != nil {
2023-12-27 21:48:55 -07:00
return errorResponse ( imageGetErr )
2023-12-27 10:52:24 -07:00
}
defer imageRes . Body . Close ( )
imageBytes , imgReadErr := io . ReadAll ( imageRes . Body )
if imgReadErr != nil {
2023-12-27 21:48:55 -07:00
return errorResponse ( imgReadErr )
2023-12-27 10:52:24 -07:00
}
imageFile := bytes . NewBuffer ( imageBytes )
file := sendpart . File {
2023-12-27 11:58:46 -07:00
Name : "himbot_response.png" ,
2023-12-27 10:52:24 -07:00
Reader : imageFile ,
}
return & api . InteractionResponseData {
2023-12-27 11:58:10 -07:00
Content : option . NewNullableString ( "Prompt: " + options . Prompt ) ,
Files : [ ] sendpart . File { file } ,
2023-12-27 10:52:24 -07:00
}
}
func ( h * handler ) cmdHDPic ( ctx context . Context , data cmdroute . CommandData ) * api . InteractionResponseData {
2023-12-27 12:47:14 -07:00
// Cooldown Logic
2024-01-03 14:32:37 -07:00
allowed := lib . CooldownHandler ( * data . Event , "hdPic" , time . Minute * 10 )
2023-12-27 15:29:37 -07:00
2023-12-29 01:31:36 -07:00
if ! allowed {
2023-12-27 21:48:55 -07:00
return errorResponse ( errors . New ( "please wait for the cooldown" ) )
2023-12-27 15:02:23 -07:00
}
2023-12-27 12:47:14 -07:00
// Command Logic
2023-12-27 10:52:24 -07:00
var options struct {
Prompt string ` discord:"prompt" `
}
if err := data . Options . Unmarshal ( & options ) ; err != nil {
return errorResponse ( err )
}
client := openai . NewClient ( os . Getenv ( "OPENAI_API_KEY" ) )
// Send the generation request to DALL·E 3
resp , err := client . CreateImage ( context . Background ( ) , openai . ImageRequest {
Prompt : options . Prompt ,
Model : "dall-e-3" ,
Size : "1024x1024" ,
} )
if err != nil {
log . Printf ( "Image creation error: %v\n" , err )
return errorResponse ( fmt . Errorf ( "failed to generate image" ) )
}
imageRes , err := http . Get ( resp . Data [ 0 ] . URL )
if err != nil {
2023-12-27 21:48:55 -07:00
return errorResponse ( err )
2023-12-27 10:52:24 -07:00
}
defer imageRes . Body . Close ( )
imageBytes , err := io . ReadAll ( imageRes . Body )
if err != nil {
2023-12-27 21:48:55 -07:00
return errorResponse ( err )
2023-12-27 10:52:24 -07:00
}
imageFile := bytes . NewBuffer ( imageBytes )
file := sendpart . File {
2023-12-27 11:58:46 -07:00
Name : "himbot_response.png" ,
2023-12-27 10:52:24 -07:00
Reader : imageFile ,
}
return & api . InteractionResponseData {
2023-12-27 11:58:10 -07:00
Content : option . NewNullableString ( "Prompt: " + options . Prompt ) ,
Files : [ ] sendpart . File { file } ,
2023-12-27 10:52:24 -07:00
}
}
func ( h * handler ) cmdHS ( ctx context . Context , data cmdroute . CommandData ) * api . InteractionResponseData {
var options struct {
Arg string ` discord:"nickname" `
}
if err := data . Options . Unmarshal ( & options ) ; err != nil {
return errorResponse ( err )
}
2023-12-27 17:13:32 -07:00
2023-12-27 17:56:21 -07:00
user := lib . GetUserObject ( * data . Event )
2023-12-27 10:52:24 -07:00
2023-12-26 18:39:21 -07:00
return & api . InteractionResponseData {
2023-12-27 17:56:21 -07:00
Content : option . NewNullableString ( options . Arg + " was " + user . DisplayName ( ) + "'s nickname in highschool!" ) ,
2023-12-26 18:39:21 -07:00
}
}
func errorResponse ( err error ) * api . InteractionResponseData {
return & api . InteractionResponseData {
Content : option . NewNullableString ( "**Error:** " + err . Error ( ) ) ,
Flags : discord . EphemeralMessage ,
AllowedMentions : & api . AllowedMentions { /* none */ } ,
}
}