@@ -4,53 +4,27 @@ import (
44 "encoding/json"
55 "fmt"
66 "html"
7- "net/http"
87 "strings"
98
109 "github.com/bwmarrin/discordgo"
1110
1211 "github.com/Southclaws/cj/types"
12+
13+ "github.com/algolia/algoliasearch-client-go/v4/algolia/search"
14+
15+ "go.uber.org/zap"
1316)
1417
1518const cmdUsage = "USAGE: /wiki [function/callback]"
1619
17- type Results struct {
18- Status struct {
19- Total int `json:"total"`
20- Failed int `json:"failed"`
21- Successful int `json:"successful"`
22- } `json:"status"`
23- Request struct {
24- Query struct {
25- Query string `json:"query"`
26- } `json:"query"`
27- Size int `json:"size"`
28- From int `json:"from"`
29- Highlight struct {
30- Style interface {} `json:"style"`
31- Fields interface {} `json:"fields"`
32- } `json:"highlight"`
33- Fields interface {} `json:"fields"`
34- Facets interface {} `json:"facets"`
35- Explain bool `json:"explain"`
36- Sort []string `json:"sort"`
37- IncludeLocations bool `json:"includeLocations"`
38- SearchAfter interface {} `json:"search_after"`
39- SearchBefore interface {} `json:"search_before"`
40- } `json:"request"`
41- Hits []Hit `json:"hits"`
42- TotalHits int `json:"total"`
43- Took int64 `json:"took"`
44- }
45-
46- type Hit struct {
47- Url string `json:"url"`
48- Title string `json:"title"`
49- Description string `json:"desc"`
50- TitleFragments string `json:"title_fragment"`
51- DescriptionFragments string `json:"desc_fragment"`
52- Score float64 `json:"score"`
53- }
20+ // Algolia config
21+ const (
22+ algoliaAppID = "AOKXGK39Z7"
23+ algoliaAPIKey = "54204f37e5c8fc2871052d595ee0505e" //Safe to commit
24+ algoliaIndexName = "open"
25+ algoliaContextualSearch = true
26+ algoliaInsights = false
27+ )
5428
5529func (cm * CommandManager ) commandWiki (
5630 interaction * discordgo.InteractionCreate ,
@@ -66,19 +40,32 @@ func (cm *CommandManager) commandWiki(
6640 return
6741 }
6842
69- r , err := http .Get (fmt .Sprintf ("https://api.open.mp/docs/search?q=%s" , strings .ReplaceAll (searchTerm , " " , "%20" )))
70- if err != nil {
71- cm .replyDirectly (interaction , fmt .Sprintf ("Failed to GET result for search term %s\n Error: %s" , searchTerm , err .Error ()))
43+ algoliaClient , algoliaErr := search .NewClient (algoliaAppID , algoliaAPIKey )
44+
45+ if algoliaErr != nil {
46+ cm .replyDirectly (interaction , fmt .Sprintf ("Failed to initialise Search client: \n %s" , algoliaErr .Error ()))
47+ zap .L ().Error ("Failed to initialise Search client" , zap .Error (algoliaErr ))
7248 return
7349 }
7450
75- var results Results
76- if err = json .NewDecoder (r .Body ).Decode (& results ); err != nil {
77- cm .replyDirectly (interaction , fmt .Sprintf ("Failed to decode result for search term %s\n Error: %s\n " , searchTerm , err .Error ()))
51+ response , err := algoliaClient .Search (algoliaClient .NewApiSearchRequest (
52+ search .NewEmptySearchMethodParams ().SetRequests (
53+ []search.SearchQuery {* search .SearchForHitsAsSearchQuery (
54+ search .NewEmptySearchForHits ().
55+ SetIndexName (algoliaIndexName ).
56+ SetQuery (searchTerm ).
57+ SetHitsPerPage (3 ),
58+ )},
59+ ),
60+ ))
61+ if err != nil {
62+ cm .replyDirectly (interaction , fmt .Sprintf ("Failed to search: %s" , err .Error ()))
7863 return
7964 }
8065
81- if results .TotalHits == 0 {
66+ finalResult := response .Results [0 ]
67+
68+ if * finalResult .SearchResponse .NbHits == 0 {
8269 cm .replyDirectlyEmbed (interaction , "" , & discordgo.MessageEmbed {
8370 Type : discordgo .EmbedTypeRich ,
8471 Title : fmt .Sprintf ("No results: %s" , searchTerm ),
@@ -89,24 +76,65 @@ func (cm *CommandManager) commandWiki(
8976
9077 desc := strings.Builder {}
9178
92- rendered := 0
93- for _ , hit := range results .Hits {
94- if rendered == 3 {
95- break
79+ seenUrls := make (map [string ]bool )
80+
81+ actuallyFoundResults := 0
82+ for _ , hit := range finalResult .SearchResponse .Hits {
83+ var hitData map [string ]interface {}
84+ hitJSON , err := json .Marshal (hit )
85+ if err != nil {
86+ // Would reply, but then it may error again - and we cant re-send messages
87+ // cm.replyDirectly(interaction, fmt.Sprintf("Error marshalling hit: %s", err.Error()))
88+ continue
89+ }
90+
91+ if err := json .Unmarshal (hitJSON , & hitData ); err != nil {
92+ // Would reply, but then it may error again - and we cant re-send messages
93+ // cm.replyDirectly(interaction, fmt.Sprintf("Error unmarshalling hit: %s", err.Error()))
94+ continue
95+ }
96+
97+ if seenUrls [hitData ["url_without_anchor" ].(string )] { //Already presented to user - Algolia does this thing of sending twice the same thing
98+ continue
9699 }
97100
98- // Skip searching translations
99- if strings .Contains (hit .Url , "translations" ) {
101+ seenUrls [hitData ["url_without_anchor" ].(string )] = true
102+
103+ stringParts := strings .Split (strings .TrimSuffix (hitData ["url_without_anchor" ].(string ), "/" ), "/" ) //Algolia doesnt give the Function/Callback name - so I steal it from the URL
104+
105+ if stringParts [len (stringParts )- 2 ] == "blog" { //Remove blog posts from results
100106 continue
101107 }
102108
109+ actuallyFoundResults ++
110+
111+ content , ok := hitData ["content" ].(string )
112+ description := ""
113+ if ! ok {
114+ description = "(No description found)"
115+ } else {
116+ description = formatDescription (& content )
117+ }
118+
103119 desc .WriteString (fmt .Sprintf (
104- "[%s](https://open.mp/%s): %s\n " ,
105- hit .Title ,
106- strings .TrimSuffix (hit .Url , ".md" ),
107- formatDescription (hit )))
108- rendered ++
120+ "[%s](%s) [%s/%s]: %s\n " ,
121+ stringParts [len (stringParts )- 1 ],
122+ hitData ["url_without_anchor" ].(string ),
123+ stringParts [len (stringParts )- 3 ],
124+ stringParts [len (stringParts )- 2 ],
125+ description ,
126+ ))
109127 }
128+
129+ if actuallyFoundResults == 0 { //Hitting this means all results were filtered out
130+ cm .replyDirectlyEmbed (interaction , "" , & discordgo.MessageEmbed {
131+ Type : discordgo .EmbedTypeRich ,
132+ Title : fmt .Sprintf ("No results: %s" , searchTerm ),
133+ Description : "There were no results for that query." ,
134+ })
135+ return
136+ }
137+
110138 embed := & discordgo.MessageEmbed {
111139 Type : discordgo .EmbedTypeRich ,
112140 Title : fmt .Sprintf ("Documentation Search Results: %s" , searchTerm ),
@@ -117,14 +145,10 @@ func (cm *CommandManager) commandWiki(
117145 return false , err // Todo: remove this
118146}
119147
120- func formatDescription (hit Hit ) string {
121- if len (hit .Description ) == 0 {
122- return "(No description found)"
123- }
124-
148+ func formatDescription (hit * string ) string {
125149 return html .UnescapeString (strings .ReplaceAll (
126150 strings .ReplaceAll (
127- hit . Description ,
151+ * hit ,
128152 "<mark>" , "**" ),
129153 "</mark>" , "**" ))
130154}
0 commit comments