Initial commit

master
ALI Hamza 2021-09-07 17:52:27 +07:00
commit 83d17a5ad6
Signed by: hamza
GPG Key ID: 22473A32291F8CB6
6 changed files with 846 additions and 0 deletions

@ -0,0 +1,25 @@
package main
import (
"encoding/json"
"log"
"os"
)
type Configuration struct {
Prefix string `json:"prefix"`
Token string `json:"token"`
}
var config Configuration
func init() {
fileBytes, err := os.ReadFile("config.json")
if err != nil {
log.Fatal(err)
}
err = json.Unmarshal(fileBytes, &config)
if err != nil {
log.Fatal(err)
}
}

516
doc.go

@ -0,0 +1,516 @@
package main
import (
"context"
"fmt"
"log"
"path"
"strings"
"time"
"github.com/diamondburned/arikawa/v3/api"
"github.com/diamondburned/arikawa/v3/discord"
"github.com/diamondburned/arikawa/v3/gateway"
"github.com/hhhapz/doc"
)
var (
searchErr = "Could not find package with the name of `%s`."
notFound = "Could not find type or function `%s` in package `%s`"
methodNotFound = "Could not find method `%s` for type `%s` in package `%s`"
notOwner = "Only the message sender can do this."
cannotExpand = "You cannot expand this embed."
privilegedRoles = map[discord.RoleID]struct{}{
// Gopher Herder ID on discord.gg/golang
370280974593818644: {},
}
)
type interactionData struct {
id string
created time.Time
user *discord.User
query string
full bool
}
var interactionMap = map[string]*interactionData{}
func init() {
go func() {
for {
time.Sleep(time.Minute * 5)
now := time.Now()
for _, data := range interactionMap {
if data.created.After(now) {
delete(interactionMap, data.id)
}
}
}
}()
}
func (b *botState) handleDocs(e *gateway.InteractionCreateEvent) {
data := api.InteractionResponse{Type: api.DeferredMessageInteractionWithSource}
if err := b.state.RespondInteraction(e.ID, e.Token, data); err != nil {
log.Println("failed to send interaction callback:", err)
return
}
args := map[string]gateway.InteractionOption{}
for _, arg := range e.Data.Options {
args[arg.Name] = arg
}
query := args["query"].String()
embed := b.onDocs(e, args["query"].String(), false)
var components *[]discord.Component
if !strings.HasPrefix(embed.Title, "Error") {
interactionMap[e.ID.String()] = &interactionData{
id: e.ID.String(),
created: time.Now(),
user: e.User,
query: query,
}
components = &[]discord.Component{
discord.ActionRowComponent{
Components: []discord.Component{
discord.SelectComponent{
CustomID: e.ID.String(),
Options: selectOptions(false),
Placeholder: "Actions",
},
},
},
}
}
edit := api.EditInteractionResponseData{
Embeds: &[]discord.Embed{embed},
Components: components,
}
if _, err := b.state.EditInteractionResponse(e.AppID, e.Token, edit); err != nil {
log.Println("failed to send interaction callback:", err)
return
}
}
func (b *botState) onDocsComponent(e *gateway.InteractionCreateEvent, data *interactionData) {
var embed discord.Embed
var components *[]discord.Component
var hasRole bool
for _, role := range e.Member.RoleIDs {
if _, ok := privilegedRoles[role]; ok {
hasRole = true
break
}
}
isAdmin := func() bool {
perms, err := b.state.Permissions(e.ChannelID, e.User.ID)
if err != nil {
return false
}
if !perms.Has(discord.PermissionAdministrator) {
return false
}
return true
}
action := e.Data.Values[0]
switch action {
case "minimize":
embed, data.full = b.onDocs(e, data.query, false), false
components = &[]discord.Component{
discord.ActionRowComponent{
Components: []discord.Component{
discord.SelectComponent{
CustomID: data.id,
Options: selectOptions(false),
Placeholder: "Actions",
},
},
},
}
// admin + priviledged only
// only check admin here to reduce total api calls
case "expand":
embed, data.full = b.onDocs(e, data.query, true), true
components = &[]discord.Component{
discord.ActionRowComponent{
Components: []discord.Component{
discord.SelectComponent{
CustomID: data.id,
Options: selectOptions(true),
Placeholder: "Actions",
},
},
},
}
case "hide":
components = &[]discord.Component{}
embed = b.onDocs(e, data.query, data.full)
embed.Description = ""
embed.Footer = nil
delete(interactionMap, data.id)
}
if e.GuildID != discord.NullGuildID {
// check admin last
if e.User.ID != data.user.ID && !hasRole && !isAdmin() {
embed = failEmbed("Error", notOwner)
}
}
var resp api.InteractionResponse
if strings.HasPrefix(embed.Title, "Error") {
resp = api.InteractionResponse{
Type: api.MessageInteractionWithSource,
Data: &api.InteractionResponseData{
Flags: api.EphemeralResponse,
Embeds: &[]discord.Embed{embed},
},
}
} else {
resp = api.InteractionResponse{
Type: api.UpdateMessage,
Data: &api.InteractionResponseData{
Embeds: &[]discord.Embed{embed},
Components: components,
},
}
}
if err := b.state.RespondInteraction(e.ID, e.Token, resp); err != nil {
log.Println("failed to send interaction callback:", err)
}
}
func (b *botState) onDocs(e *gateway.InteractionCreateEvent, query string, full bool) discord.Embed {
module, parts := parseQuery(query)
pkg, err := b.searcher.Search(context.Background(), module)
if err != nil {
log.Printf("Package request by %s failed: %v", e.User.Tag(), err)
return failEmbed("Error", fmt.Sprintf(searchErr, module))
}
switch len(parts) {
case 0:
return b.fullPackage(pkg, full)
case 1:
if typ, ok := pkg.Types[parts[0]]; ok {
return b.typ(pkg, typ, full)
}
if fn, ok := pkg.Functions[parts[0]]; ok {
return b.fn(pkg, fn, full)
}
return failEmbed("Error: Not Found", fmt.Sprintf(notFound, parts[0], module))
default:
typ, ok := pkg.Types[parts[0]]
if !ok {
return failEmbed("Error: Not Found", fmt.Sprintf(notFound, parts[0], module))
}
method, ok := typ.Methods[parts[1]]
if !ok {
return failEmbed("Error: Not Found", fmt.Sprintf(methodNotFound, parts[1], parts[0], module))
}
return b.method(pkg, method, full)
}
}
const (
docLimit = 2800
defLimit = 1000
accentColor = 0x007D9C
)
func (b *botState) fullPackage(pkg doc.Package, full bool) discord.Embed {
return discord.Embed{
Title: "Package " + pkg.URL,
URL: "https://pkg.go.dev/" + pkg.URL,
Description: fmt.Sprintf("**Types:** %d\n**Functions:** %d\n\n%s",
len(pkg.Types), len(pkg.Functions), format(pkg.Overview, 32, full)),
Color: accentColor,
Footer: &discord.EmbedFooter{
Text: "https://pkg.go.dev/" + pkg.URL,
},
}
}
func (b *botState) typ(pkg doc.Package, typ doc.Type, full bool) discord.Embed {
def := typdef(typ.Signature, full)
return discord.Embed{
Title: fmt.Sprintf("%s: %s", pkg.URL, typ.Name),
URL: fmt.Sprintf("https://pkg.go.dev/%s#%s", pkg.URL, typ.Name),
Description: fmt.Sprintf("```go\n%s\n```\n%s", def, format(typ.Comment, len(def), full)),
Color: accentColor,
Footer: &discord.EmbedFooter{
Text: "https://pkg.go.dev/" + pkg.URL,
},
}
}
func (b *botState) fn(pkg doc.Package, fn doc.Function, full bool) discord.Embed {
def := typdef(fn.Signature, full)
return discord.Embed{
Title: fmt.Sprintf("%s: %s", pkg.URL, fn.Name),
URL: fmt.Sprintf("https://pkg.go.dev/%s#%s", pkg.URL, fn.Name),
Description: fmt.Sprintf("```go\n%s\n```\n%s", def, format(fn.Comment, len(def), full)),
Color: accentColor,
Footer: &discord.EmbedFooter{
Text: "https://pkg.go.dev/" + pkg.URL,
},
}
}
func (b *botState) method(pkg doc.Package, method doc.Method, full bool) discord.Embed {
def := typdef(method.Signature, full)
return discord.Embed{
Title: fmt.Sprintf("%s: %s.%s", pkg.URL, method.For, method.Name),
URL: fmt.Sprintf("https://pkg.go.dev/%s#%s.%s", pkg.URL, method.For, method.Name),
Description: fmt.Sprintf("```go\n%s\n```\n%s", def, format(method.Comment, len(def), full)),
Color: accentColor,
Footer: &discord.EmbedFooter{
Text: "https://pkg.go.dev/" + pkg.URL,
},
}
}
func selectOptions(full bool) []discord.SelectComponentOption {
expand := discord.SelectComponentOption{
Label: "Expand",
Value: "expand",
Description: "Show more documentation.",
Emoji: &discord.ButtonEmoji{Name: "⬇️"},
}
if full {
expand = discord.SelectComponentOption{
Label: "Minimize",
Value: "minimize",
Description: "Show less documentation.",
Emoji: &discord.ButtonEmoji{Name: "⬆️"},
}
}
return []discord.SelectComponentOption{
expand,
{
Label: "Hide",
Value: "hide",
Description: "Hide the message.",
Emoji: &discord.ButtonEmoji{Name: "❌"},
},
}
}
func failEmbed(title, description string) discord.Embed {
return discord.Embed{
Title: title,
Description: description,
Color: 0xEE0000,
}
}
func parseQuery(module string) (string, []string) {
module = strings.ReplaceAll(module, " ", ".")
dir, base := path.Split(strings.ToLower(module))
split := strings.Split(base, ".")
full := dir + split[0]
if complete, ok := stdlibPackages[full]; ok {
full = complete
}
return full, split[1:]
}
func typdef(def string, full bool) string {
split := strings.Split(def, "\n")
if !full {
return split[0]
}
b := strings.Builder{}
b.Grow(len(def))
for _, line := range strings.Split(def, "\n") {
b.WriteRune('\n')
if len(line)+b.Len() > defLimit {
b.WriteString("// full signature omitted")
break
}
b.WriteString(line)
}
return b.String()
}
func format(c doc.Comment, initial int, full bool) string {
if len(c) == 0 {
return "*No documentation found*"
}
if !full {
md := c[0].Markdown()
if len(md) > 500 {
md = md[:400] + "...\n\n*More documentation omitted*"
}
if len(c) == 1 {
return md
}
return fmt.Sprintf("%s\n\n*More documentation omitted*", md)
}
var parts doc.Comment
length := initial
for _, note := range c {
l := len(note.Text())
if l+length > docLimit {
parts = append(parts, doc.Paragraph("*More documentation omitted...*"))
break
}
length += l
parts = append(parts, note)
}
return parts.Markdown()
}
var stdlibPackages = map[string]string{
"tar": "archive/tar",
"zip": "archive/zip",
"bzip2": "compress/bzip2",
"flate": "compress/flate",
"gzip": "compress/gzip",
"lzw": "compress/lzw",
"zlib": "compress/zlib",
"heap": "container/heap",
"list": "container/list",
"ring": "container/ring",
"aes": "crypto/aes",
"cipher": "crypto/cipher",
"des": "crypto/des",
"dsa": "crypto/dsa",
"ecdsa": "crypto/ecdsa",
"ed25519": "crypto/ed25519",
"elliptic": "crypto/elliptic",
"hmac": "crypto/hmac",
"md5": "crypto/md5",
"rc4": "crypto/rc4",
"rsa": "crypto/rsa",
"sha1": "crypto/sha1",
"sha256": "crypto/sha256",
"sha512": "crypto/sha512",
"subtle": "crypto/subtle",
"tls": "crypto/tls",
"x509": "crypto/x509",
"pkix": "crypto/x509/pkix",
"dwarf": "debug/dwarf",
"elf": "debug/elf",
"gosym": "debug/gosym",
"macho": "debug/macho",
"pe": "debug/pe",
"plan9obj": "debug/plan9obj",
"ascii85": "encoding/ascii85",
"asn1": "encoding/asn1",
"base32": "encoding/base32",
"base64": "encoding/base64",
"binary": "encoding/binary",
"csv": "encoding/csv",
"gob": "encoding/gob",
"hex": "encoding/hex",
"json": "encoding/json",
"pem": "encoding/pem",
"xml": "encoding/xml",
"ast": "go/ast",
"build": "go/build",
"constraint": "go/build/constraint",
"constant": "go/constant",
"docformat": "go/docformat",
"importer": "go/importer",
"parserprinter": "go/parserprinter",
"scanner": "go/scanner",
"token": "go/token",
"types": "go/types",
"adler32": "hash/adler32",
"crc32": "hash/crc32",
"crc64": "hash/crc64",
"fnv": "hash/fnv",
"maphash": "hash/maphash",
"color": "image/color",
"draw": "image/draw",
"gif": "image/gif",
"jpeg": "image/jpeg",
"parsing": "image/parsing",
"suffixarray": "index/suffixarray",
"fs": "io/fs",
"ioutil": "io/ioutil",
"big": "math/big",
"bits": "math/bits",
"cmplx": "math/cmplx",
"multipart": "mime/multipart",
"quotedprintable": "mime/quotedprintable",
"http": "net/http",
"cgi": "net/http/cgi",
"cookiejar": "net/http/cookiejar",
"fcgi": "net/http/fcgi",
"httptest": "net/http/httptest",
"httptrace": "net/http/httptrace",
"httputil": "net/http/httputil",
"mail": "net/mail",
"rpc": "net/rpc",
"jsonrpc": "net/rpc/jsonrpc",
"smtp": "net/smtp",
"textproto": "net/textproto",
"exec": "os/exec",
"signal": "os/signal",
"user": "os/user",
"filepath": "path/filepath",
"syntax": "regexp/syntax",
"cgo": "runtime/cgo",
"metrics": "runtime/metrics",
"msan": "runtime/msan",
"race": "runtime/race",
"trace": "runtime/trace",
"js": "syscall/js",
"fstest": "testing/fstest",
"iotest": "testing/iotest",
"quick": "testing/quick",
"tabwriter": "text/tabwriter",
"parse": "text/template/parse",
"tzdata": "time/tzdata",
"utf16": "unicode/utf16",
"utf8": "unicode/utf8",
}

@ -0,0 +1,90 @@
package main
import "testing"
func TestParseQuery(t *testing.T) {
cases := []struct {
name string
query string
module string
parts []string
}{
{
name: "stdlib basic",
query: "strings",
module: "strings",
parts: nil,
},
{
name: "stdlib type",
query: "strings.Split",
module: "strings",
parts: []string{"split"},
},
{
name: "stdlib method",
query: "strings.Builder.Grow",
module: "strings",
parts: []string{"builder", "grow"},
},
{
name: "stdlib redirect basic",
query: "json",
module: "encoding/json",
parts: nil,
},
{
name: "stdlib redirect type",
query: "json.Unmarshal",
module: "encoding/json",
parts: []string{"unmarshal"},
},
{
name: "stdlib redirect method",
query: "json.NewDecoder.Decode",
module: "encoding/json",
parts: []string{"newdecoder", "decode"},
},
{
name: "custom basic",
query: "github.com/golang/go",
module: "github.com/golang/go",
parts: nil,
},
{
name: "custom type",
query: "github.com/bwmarrin/discordgo.Session",
module: "github.com/bwmarrin/discordgo",
parts: []string{"session"},
},
{
name: "custom method",
query: "github.com/bwmarrin/discordgo.Session.AddHandler",
module: "github.com/bwmarrin/discordgo",
parts: []string{"session", "addhandler"},
},
{
name: "custom method with space",
query: "github.com/bwmarrin/discordgo Session AddHandler",
module: "github.com/bwmarrin/discordgo",
parts: []string{"session", "addhandler"},
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
module, parts := parseQuery(c.query)
if module != c.module {
t.Errorf("INVALID MODULE:\nGOT:%s\nEXPECTED:%s", module, c.module)
}
if len(parts) != len(c.parts) {
t.Errorf("INVALID PARTS:\nGOT:%v\nEXPECTED:%v", parts, c.parts)
}
for i, part := range parts {
if part != c.parts[i] {
t.Errorf("INVALID PARTS(%d):\nGOT:%v\nEXPECTED:%v", i, part, c.parts[i])
}
}
})
}
}

@ -0,0 +1,24 @@
module github.com/hhhapz/discodoc
go 1.17
require (
github.com/diamondburned/arikawa/v3 v3.0.0-rc.1
github.com/hhhapz/doc v0.3.1
github.com/k0kubun/pp v3.0.1+incompatible
github.com/pkg/errors v0.9.1
)
require (
github.com/PuerkitoBio/goquery v1.7.1 // indirect
github.com/andybalholm/cascadia v1.2.0 // indirect
github.com/gorilla/schema v1.2.0 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 // indirect
github.com/mattn/go-colorable v0.1.8 // indirect
github.com/mattn/go-isatty v0.0.13 // indirect
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 // indirect
golang.org/x/net v0.0.0-20210614182718-04defd469f4e // indirect
golang.org/x/sys v0.0.0-20210423082822-04245dca01da // indirect
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
)

@ -0,0 +1,63 @@
github.com/PuerkitoBio/goquery v1.7.1 h1:oE+T06D+1T7LNrn91B4aERsRIeCLJ/oPSa6xB9FPnz4=
github.com/PuerkitoBio/goquery v1.7.1/go.mod h1:XY0pP4kfraEmmV1O7Uf6XyjoslwsneBbgeDjLYuN8xY=
github.com/andybalholm/cascadia v1.2.0 h1:vuRCkM5Ozh/BfmsaTm26kbjm0mIOM3yS5Ek/F5h18aE=
github.com/andybalholm/cascadia v1.2.0/go.mod h1:YCyR8vOZT9aZ1CHEd8ap0gMVm2aFgxBp0T0eFw1RUQY=
github.com/diamondburned/arikawa/v3 v3.0.0-20210824182349-f334491deed4 h1:Lj1nG3DiPyY4Mu7N4Wk0wtKtQsFlX9/8oT4g1EjENIo=
github.com/diamondburned/arikawa/v3 v3.0.0-20210824182349-f334491deed4/go.mod h1:sNqM/iGXuH87wEH1rpQBEY1PR0AAkRKJuUhJGOdo7To=
github.com/diamondburned/arikawa/v3 v3.0.0-rc.1 h1:1RHtYaVstlEJ5v8b3PNUIpa7D0cfg3qT7n7WbWjb5ZQ=
github.com/diamondburned/arikawa/v3 v3.0.0-rc.1/go.mod h1:sNqM/iGXuH87wEH1rpQBEY1PR0AAkRKJuUhJGOdo7To=
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/hhhapz/doc v0.1.0 h1:Saowq412pkjVwzbbaj26E4695MsAh2YvRAy7ai2gE9Y=
github.com/hhhapz/doc v0.1.0/go.mod h1:EmQF3Mq8Y4MacCdVLDORczoxh1AD4WS6VpvHILXNbDE=
github.com/hhhapz/doc v0.1.2 h1:WqVVy/1Pa0LvPd5hhi7Z6SDRNBZ1CJ30cb/vEOww0iQ=
github.com/hhhapz/doc v0.1.2/go.mod h1:veI3TCLBx/7jTkNVShg1omZOx+XducAFqRQN3yG/xyM=
github.com/hhhapz/doc v0.1.3 h1:GOwFE+ewsLE+fIqQyBfJixuYfK3hpzVJss/gIVwu/4o=
github.com/hhhapz/doc v0.1.3/go.mod h1:RzUhRa6guPMbXtNfNm4jW6aYCu+JBapioQEgrzoHSMo=
github.com/hhhapz/doc v0.1.4 h1:xspKqlE36MmkuKhcHAzNUWlz6iyL4EWPh0Ky2Y/eaZQ=
github.com/hhhapz/doc v0.1.4/go.mod h1:RzUhRa6guPMbXtNfNm4jW6aYCu+JBapioQEgrzoHSMo=
github.com/hhhapz/doc v0.2.0 h1:vUCk8kQ7s+SPrjF/+ID2+qa1Jvo0YQXmco/n4vfLG2k=
github.com/hhhapz/doc v0.2.0/go.mod h1:RzUhRa6guPMbXtNfNm4jW6aYCu+JBapioQEgrzoHSMo=
github.com/hhhapz/doc v0.3.0 h1:SuUTYmZu83tTXTQ0wyOWYCcVeSSxfzvnSXEsE6jr4VI=
github.com/hhhapz/doc v0.3.0/go.mod h1:RzUhRa6guPMbXtNfNm4jW6aYCu+JBapioQEgrzoHSMo=
github.com/hhhapz/doc v0.3.1 h1:TKlytW6nDcJydOIDffYF7OINF6z3x6AUhEvlnfXk9FU=
github.com/hhhapz/doc v0.3.1/go.mod h1:RzUhRa6guPMbXtNfNm4jW6aYCu+JBapioQEgrzoHSMo=
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM=
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
github.com/k0kubun/pp v3.0.1+incompatible h1:3tqvf7QgUnZ5tXO6pNAZlrvHgl6DvifjDrd9g2S9Z40=
github.com/k0kubun/pp v3.0.1+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA=
github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/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-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s=
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
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=

@ -0,0 +1,128 @@
package main
import (
"context"
"flag"
"log"
"net/http"
"github.com/diamondburned/arikawa/v3/api"
"github.com/diamondburned/arikawa/v3/discord"
"github.com/diamondburned/arikawa/v3/gateway"
"github.com/diamondburned/arikawa/v3/state"
"github.com/hhhapz/doc"
"github.com/hhhapz/doc/godocs"
"github.com/pkg/errors"
)
type botState struct {
appID discord.AppID
searcher *doc.CachedSearcher
state *state.State
}
func (b *botState) OnCommand(e *gateway.InteractionCreateEvent) {
if e.GuildID != discord.NullGuildID {
e.User = &e.Member.User
}
if e.Data.Name == "docs" {
b.handleDocs(e)
return
}
if data, ok := interactionMap[e.Data.CustomID]; ok {
b.onDocsComponent(e, data)
return
}
}
var update bool
func main() {
updateVar := flag.Bool("update", false, "update all commands, regardless of if they are present or not")
flag.Parse()
update = *updateVar
if config.Token == "" {
log.Fatalln("no token provided")
}
s, err := state.New("Bot " + config.Token)
if err != nil {
log.Fatalln("session failed:", err)
}
searcher := doc.New(http.DefaultClient, godocs.Parser)
cs := doc.WithCache(searcher)
b := botState{
searcher: cs,
state: s,
}
s.AddHandler(b.OnCommand)
s.AddIntents(gateway.IntentGuildMessageReactions)
if err := s.Open(context.Background()); err != nil {
log.Fatalln("failed to open:", err)
}
defer s.Close()
log.Println("Gateway connection established.")
me, err := s.Me()
if err != nil {
log.Println("Could not get me:", err)
return
}
b.appID = discord.AppID(me.ID)
log.Println("Logged in as ", me.Tag())
if err := loadCommands(s, me.ID); err != nil {
log.Println("Could not load commands:", err)
return
}
select {}
}
func loadCommands(s *state.State, me discord.UserID) error {
appID := discord.AppID(me)
registered, err := s.Commands(appID)
if err != nil {
return err
}
registeredMap := map[string]bool{}
if !update {
for _, c := range registered {
registeredMap[c.Name] = true
log.Println("Registered command:", c.Name)
}
}
for _, c := range commands {
if registeredMap[c.Name] {
continue
}
if _, err := s.CreateCommand(appID, c); err != nil {
return errors.Wrap(err, "could not register "+c.Name)
}
log.Println("Created command:", c.Name)
}
return nil
}
var commands = []api.CreateCommandData{
{
Name: "docs",
Description: "Base command",
Options: []discord.CommandOption{
{
Name: "query",
Description: "Search query",
Type: discord.StringOption,
Required: true,
},
},
},
}