From 425fa31a85dc85f202f2d7e58b45d0c6f414ce35 Mon Sep 17 00:00:00 2001 From: Jesper Handskemager Date: Thu, 14 Jul 2022 13:38:31 +0200 Subject: [PATCH] Init --- README.md | 4 + go.mod | 25 +++++ go.sum | 59 ++++++++++++ main.go | 273 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 361 insertions(+) create mode 100644 README.md create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go diff --git a/README.md b/README.md new file mode 100644 index 0000000..8918681 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +# OpenBibleDiscord +A discord topical bible, simply use the included slash command and search for a topic and the bot will scrape [openbible.info](https://openbible.info) for relevant verses + +[Add the bot to your server](https://discord.com/api/oauth2/authorize?client_id=995775405757845635&permissions=2147601472&scope=bot+applications.commands) \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..c503667 --- /dev/null +++ b/go.mod @@ -0,0 +1,25 @@ +module codeberg.org/debatklubben/OpenBibleBot + +go 1.18 + +require ( + github.com/PuerkitoBio/goquery v1.8.0 // indirect + github.com/andybalholm/cascadia v1.3.1 // indirect + github.com/antchfx/htmlquery v1.2.5 // indirect + github.com/antchfx/xmlquery v1.3.11 // indirect + github.com/antchfx/xpath v1.2.1 // indirect + github.com/bwmarrin/discordgo v0.25.0 // indirect + github.com/gobwas/glob v0.2.3 // indirect + github.com/gocolly/colly v1.2.0 // indirect + github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect + github.com/golang/protobuf v1.3.1 // indirect + github.com/gorilla/websocket v1.4.2 // indirect + github.com/kennygrant/sanitize v1.2.4 // indirect + github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca // indirect + github.com/temoto/robotstxt v1.1.2 // indirect + golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b // indirect + golang.org/x/net v0.0.0-20210916014120-12bc252f5db8 // indirect + golang.org/x/sys v0.0.0-20210423082822-04245dca01da // indirect + golang.org/x/text v0.3.6 // indirect + google.golang.org/appengine v1.6.7 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..4756454 --- /dev/null +++ b/go.sum @@ -0,0 +1,59 @@ +github.com/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0gta/U= +github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI= +github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c= +github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= +github.com/antchfx/htmlquery v1.2.5 h1:1lXnx46/1wtv1E/kzmH8vrfMuUKYgkdDBA9pIdMJnk4= +github.com/antchfx/htmlquery v1.2.5/go.mod h1:2MCVBzYVafPBmKbrmwB9F5xdd+IEgRY61ci2oOsOQVw= +github.com/antchfx/xmlquery v1.3.11 h1:8aRK7l3+dJjL8ZmwgVzG5AXysrP7Mss2424tfntKWKY= +github.com/antchfx/xmlquery v1.3.11/go.mod h1:ywPcYkN0GvURUxXpUujaMVvuLSOYQBzoSfHKfAYezCE= +github.com/antchfx/xpath v1.2.1 h1:qhp4EW6aCOVr5XIkT+l6LJ9ck/JsUH/yyauNgTQkBF8= +github.com/antchfx/xpath v1.2.1/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs= +github.com/bwmarrin/discordgo v0.25.0 h1:NXhdfHRNxtwso6FPdzW2i3uBvvU7UIQTghmV2T4nqAs= +github.com/bwmarrin/discordgo v0.25.0/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/gocolly/colly v1.2.0 h1:qRz9YAn8FIH0qzgNUw+HT9UN7wm1oF9OBAilwEWpyrI= +github.com/gocolly/colly v1.2.0/go.mod h1:Hof5T3ZswNVsOHYmba1u03W65HDWgpV5HifSuueE0EA= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +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/kennygrant/sanitize v1.2.4 h1:gN25/otpP5vAsO2djbMhF/LQX6R7+O1TB4yv8NzpJ3o= +github.com/kennygrant/sanitize v1.2.4/go.mod h1:LGsjYYtgxbetdg5owWB2mpgUL6e2nfw2eObZ0u0qvak= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca h1:NugYot0LIVPxTvN8n+Kvkn6TrbMyxQiuvKdEwFdR9vI= +github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/temoto/robotstxt v1.1.2 h1:W2pOjSJ6SWvldyEuiFXNxz3xZ8aiWX5LbfDiOFd7Fxg= +github.com/temoto/robotstxt v1.1.2/go.mod h1:+1AmkuG3IYkh1kv0d2qEB9Le88ehNO0zwOr3ujewlOo= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210916014120-12bc252f5db8 h1:/6y1LfuqNuQdHAm0jjtPtgRcxIxjVZgm5OTu8/QhZvk= +golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= diff --git a/main.go b/main.go new file mode 100644 index 0000000..5e3f2e0 --- /dev/null +++ b/main.go @@ -0,0 +1,273 @@ +package main + +import ( + "flag" + "log" + "os" + "os/signal" + "strings" + "time" + + "github.com/bwmarrin/discordgo" + "github.com/gocolly/colly" +) + +type BibleStructure struct { + bibleref string + verse string +} + +func TopicScraper(keyword string) []BibleStructure { + + c := colly.NewCollector() + c.SetRequestTimeout(120 * time.Second) + verses := make([]BibleStructure, 0) + + c.OnHTML("div.verse", func(h *colly.HTMLElement) { + v := BibleStructure{} + + v.bibleref = h.ChildText("a") + v.verse = h.ChildText("p") + + verses = append(verses, v) + }) + + c.Visit("https://www.openbible.info/topics/" + keyword) + + return verses +} + +func VerseBuilder(search string, n int) string { + // If user enters negative number + if n < 1 { + return "Invalid" + } + // Set max ammount of verses to 5 + if n > 5 { + n = 5 + } + + topic := TopicScraper(search) + + topicLen := len(topic) + // If site can't find any topics + if topicLen == 0 { + return "No results" + } + + var returnString strings.Builder + + // String to be returned + returnString.WriteString("Topic: " + search + "\n") + + // Populate return string + for i := 0; i < n; i++ { + if i < topicLen { + returnString.WriteString(topic[i].bibleref + "\n" + "> " + topic[i].verse + "\n\n") + } else { + return returnString.String() + } + } + + return returnString.String() +} + +// Bot parameters +var ( + GuildID = flag.String("guild", "", "Test guild ID. If not passed - bot registers commands globally") + BotToken = flag.String("token", "[YOUR-DISCORD-TOKEN]", "Bot access token") + RemoveCommands = flag.Bool("rmcmd", true, "Remove all commands after shutdowning or not") +) + +var s *discordgo.Session + +func init() { flag.Parse() } + +func init() { + var err error + s, err = discordgo.New("Bot " + *BotToken) + if err != nil { + log.Fatalf("Invalid bot parameters: %v", err) + } +} + +var ( + commands = []*discordgo.ApplicationCommand{ + { + Name: "bible-topic", + Description: "What does the bible say about a topic?", + Type: discordgo.ChatApplicationCommand, + Options: []*discordgo.ApplicationCommandOption{ + { + Name: "topic", + Description: "A topic in the bible", + Type: discordgo.ApplicationCommandOptionString, + Required: true, + Autocomplete: true, + }, + { + Name: "amount", + Description: "Amount of verses to see (max 5)", + Type: discordgo.ApplicationCommandOptionInteger, + Required: false, + Autocomplete: false, + }, + }, + }, + } + + commandHandlers = map[string]func(s *discordgo.Session, i *discordgo.InteractionCreate){ + "bible-topic": func(s *discordgo.Session, i *discordgo.InteractionCreate) { + switch i.Type { + case discordgo.InteractionApplicationCommand: + data := i.ApplicationCommandData() + amountOfVerses := 1 + if len(data.Options) > 1 { + amountOfVerses = int(data.Options[1].IntValue()) + } + err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: VerseBuilder(data.Options[0].StringValue(), amountOfVerses), + }, + }) + if err != nil { + panic(err) + } + // Autocomplete options introduce a new interaction type (8) for returning custom autocomplete results. + case discordgo.InteractionApplicationCommandAutocomplete: + data := i.ApplicationCommandData() + choices := []*discordgo.ApplicationCommandOptionChoice{ + { + Name: "10 commandments", + Value: "10 commandments", + }, + { + Name: "homosexaulity", + Value: "homosexaulity", + }, + { + Name: "forgiveness", + Value: "forgiveness", + }, + { + Name: "sodom", + Value: "sodom", + }, + { + Name: "signs of the end times", + Value: "signs of the end times", + }, + { + Name: "bitter woman", + Value: "bitter woman", + }, + { + Name: "the purpose of the woman", + Value: "the purpose of the woman", + }, + { + Name: "woman", + Value: "woman", + }, + { + Name: "abortion", + Value: "abortion", + }, + { + Name: "harming your body", + Value: "harming your body", + }, + { + Name: "healthy eating", + Value: "healthy eating", + }, + { + Name: "confrontation", + Value: "confrontation", + }, + { + Name: "confusion", + Value: "confusion", + }, + { + Name: "non believers", + Value: "non believers", + }, + { + Name: "not giving up", + Value: "not giving up", + }, + { + Name: "truth", + Value: "truth", + }, + { + Name: "transvestites", + Value: "transvestites", + }, + { + Name: "telling the truth", + Value: "telling the truth", + }, + { + Name: "not telling the truth", + Value: "not telling the truth", + }, + // And so on, up to 25 choices + } + + if data.Options[0].StringValue() != "" { + choices = append(choices, &discordgo.ApplicationCommandOptionChoice{ + Name: data.Options[0].StringValue(), // To get user input you just get value of the autocomplete option. + Value: data.Options[0].StringValue(), + }) + } + + err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionApplicationCommandAutocompleteResult, + Data: &discordgo.InteractionResponseData{ + Choices: choices, // This is basically the whole purpose of autocomplete interaction - return custom options to the user. + }, + }) + if err != nil { + panic(err) + } + } + }, + } +) + +func main() { + s.AddHandler(func(s *discordgo.Session, r *discordgo.Ready) { log.Println("Bot is up!") }) + s.AddHandler(func(s *discordgo.Session, i *discordgo.InteractionCreate) { + if h, ok := commandHandlers[i.ApplicationCommandData().Name]; ok { + h(s, i) + } + }) + err := s.Open() + if err != nil { + log.Fatalf("Cannot open the session: %v", err) + } + defer s.Close() + + createdCommands, err := s.ApplicationCommandBulkOverwrite(s.State.User.ID, *GuildID, commands) + + if err != nil { + log.Fatalf("Cannot register commands: %v", err) + } + + stop := make(chan os.Signal, 1) + signal.Notify(stop, os.Interrupt) + <-stop + log.Println("Gracefully shutting down") + + if *RemoveCommands { + for _, cmd := range createdCommands { + err := s.ApplicationCommandDelete(s.State.User.ID, *GuildID, cmd.ID) + if err != nil { + log.Fatalf("Cannot delete %q command: %v", cmd.Name, err) + } + } + } +}