Skip to content

Instantly share code, notes, and snippets.

@miyoyo
Last active March 1, 2020 16:56
Show Gist options
  • Save miyoyo/f565d9224b8e6f88a3355fd9c084786a to your computer and use it in GitHub Desktop.
Save miyoyo/f565d9224b8e6f88a3355fd9c084786a to your computer and use it in GitHub Desktop.
Flutter doc explorer bot (CC-By) (Updated 2019/05/27: Updated domains)
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
"os/signal"
"regexp"
"syscall"
"github.com/bwmarrin/discordgo"
"github.com/jasonlvhit/gocron"
"github.com/sahilm/fuzzy"
)
type index []struct {
Name string `json:"name"`
QualifiedName string `json:"qualifiedName"`
Href string `json:"href"`
Type string `json:"type"`
OverriddenDepth int `json:"overriddenDepth"`
EnclosedBy struct {
Name string `json:"name"`
Type string `json:"type"`
} `json:"enclosedBy,omitempty"`
}
type pubSearch struct {
Packages []struct {
Package string `json:"package"`
} `json:"packages"`
Next string `json:"next"`
}
var searchStrings []string
var searchIndex index
func main() {
directPattern := regexp.MustCompile("!\\[(.*?)\\]")
searchPattern := regexp.MustCompile("\\?\\[(.*?)\\]")
pubPattern := regexp.MustCompile("&\\[(.*?)\\]")
bot, err := discordgo.New("Bot TOKEN")
if err != nil {
panic(err.Error())
}
bot.AddHandler(func(s *discordgo.Session, h *discordgo.MessageCreate) {
if h.Author.ID == s.State.User.ID {
return
}
if len(h.Content) >= 21 {
if h.Content[:21] == "<@462299661995343882>" { //Bot source
bot.ChannelMessageSendEmbed(h.ChannelID, &discordgo.MessageEmbed{
Title: "ℹ️ Help",
Description: "⚠️ These commands can be within a message, and there can be multiple per messages",
Fields: []*discordgo.MessageEmbedField{
&discordgo.MessageEmbedField{
Name: "![query]",
Value: "Gives a direct link to the first result matching 'query', with a bias towards constructors",
},
&discordgo.MessageEmbedField{
Name: "?[query]",
Value: "Shows the 10 first search results about 'query'",
},
&discordgo.MessageEmbedField{
Name: "&[query]",
Value: "Shows up to 13 search results about 'query' on Pub",
},
},
Footer: &discordgo.MessageEmbedFooter{
Text: "Source: https://gist.github.com/miyoyo/f565d9224b8e6f88a3355fd9c084786a",
},
})
}
}
for _, value := range directPattern.FindAllStringSubmatch(h.Content, -1) {
matches := fuzzy.Find(value[1], searchStrings)
if len(matches) == 0 {
s.ChannelMessageSendEmbed(h.ChannelID, &discordgo.MessageEmbed{
Title: "Direct reference - " + value[1],
Description: "Not found.",
Color: 0xEE0000,
})
} else {
selected := matches[0]
if len(matches) > 1 && searchIndex[matches[0].Index].Type != "class" {
if searchIndex[matches[1].Index].Type == "class" {
selected = matches[1]
}
}
s.ChannelMessageSend(h.ChannelID, "https://api.flutter.dev/flutter/"+searchIndex[selected.Index].Href)
}
}
for _, value := range searchPattern.FindAllStringSubmatch(h.Content, -1) {
matches := fuzzy.Find(value[1], searchStrings)
embeds := []*discordgo.MessageEmbedField{}
setSize := 5
if len(matches) < setSize {
setSize = len(matches)
}
for _, result := range matches[:setSize] {
embeds = append(embeds, &discordgo.MessageEmbedField{
Name: searchIndex[result.Index].Type + " " + searchIndex[result.Index].Name + " - " + searchIndex[result.Index].EnclosedBy.Name,
Value: "https://api.flutter.dev/flutter/" + searchIndex[result.Index].Href,
})
}
if len(embeds) == 0 {
s.ChannelMessageSendEmbed(h.ChannelID, &discordgo.MessageEmbed{
Title: "Search results - " + value[1],
Description: "Not found.",
Color: 0xEE0000,
})
} else {
s.ChannelMessageSendEmbed(h.ChannelID, &discordgo.MessageEmbed{
Title: "Search results - " + value[1],
Fields: embeds,
})
}
}
for _, value := range pubPattern.FindAllStringSubmatch(h.Content, -1) {
var pubResult pubSearch
embeds := []*discordgo.MessageEmbedField{}
out, err := http.Get("https://pub.dev/api/search?q=" + value[1])
if err != nil {
bot.ChannelMessageSend("446257816345378827", "Failed to query search api: %s"+err.Error()) //Debug channel
s.ChannelMessageSendEmbed(h.ChannelID, &discordgo.MessageEmbed{
Title: "Search results - " + value[1],
Description: "Could not contact Pub.",
Color: 0xEE0000,
})
return
}
buf := new(bytes.Buffer)
buf.ReadFrom(out.Body)
json.Unmarshal(buf.Bytes(), &pubResult)
for _, result := range pubResult.Packages {
embeds = append(embeds, &discordgo.MessageEmbedField{
Name: result.Package,
Value: "https://pub.dev/packages/" + result.Package,
})
}
if len(embeds) == 0 {
s.ChannelMessageSendEmbed(h.ChannelID, &discordgo.MessageEmbed{
Title: "Pub search results - " + value[1],
Description: "Not found.",
Color: 0xEE0000,
})
return
}
s.ChannelMessageSendEmbed(h.ChannelID, &discordgo.MessageEmbed{
Title: "Pub search results - " + value[1],
Fields: embeds,
})
}
})
if bot.Open() != nil {
panic("could not open bot: " + err.Error())
}
updateIndex(bot)
go func(bot *discordgo.Session) {
gocron.Every(1).Day().Do(updateIndex, bot)
gocron.Every(30).Minutes().Do(func() { bot.UpdateStatus(0, "mention me for help.") })
<-gocron.Start()
}(bot)
fmt.Println("FlutterDoc is now running. Press CTRL-C to exit.")
sc := make(chan os.Signal, 1)
signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill)
<-sc
bot.Close()
}
func updateIndex(bot *discordgo.Session) {
bot.UpdateStatusComplex(discordgo.UpdateStatusData{
Status: "idle",
AFK: true,
Game: &discordgo.Game{
Name: "updating search...",
},
})
out, err := http.Get("https://api.flutter.dev/flutter/index.json")
if err != nil {
bot.ChannelMessageSend("446257816345378827", "Failed to update index: %s"+err.Error()) //Debug channel
return
}
buf := new(bytes.Buffer)
buf.ReadFrom(out.Body)
json.Unmarshal(buf.Bytes(), &searchIndex)
searchStrings = nil
for _, value := range searchIndex {
searchStrings = append(searchStrings, value.QualifiedName)
}
bot.UpdateStatus(0, "mention me for help.")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment