fork of indigo with slightly nicer lexgen
at main 3.2 kB view raw
1package search 2 3import ( 4 "context" 5 "log/slog" 6 "strings" 7 "time" 8 9 "github.com/bluesky-social/indigo/atproto/identity" 10 "github.com/bluesky-social/indigo/atproto/syntax" 11) 12 13// ParsePostQuery takes a query string and pulls out some facet patterns ("from:handle.net") as filters 14func ParsePostQuery(ctx context.Context, dir identity.Directory, raw string, viewer *syntax.DID) PostSearchParams { 15 quoted := false 16 parts := strings.FieldsFunc(raw, func(r rune) bool { 17 if r == '"' { 18 quoted = !quoted 19 } 20 return r == ' ' && !quoted 21 }) 22 23 params := PostSearchParams{} 24 25 keep := make([]string, 0, len(parts)) 26 for _, p := range parts { 27 // pass-through quoted, either phrase or single token 28 if strings.HasPrefix(p, "\"") { 29 keep = append(keep, p) 30 continue 31 } 32 33 // tags (array) 34 if strings.HasPrefix(p, "#") && len(p) > 1 { 35 params.Tags = append(params.Tags, p[1:]) 36 continue 37 } 38 39 // handle (mention) 40 if strings.HasPrefix(p, "@") && len(p) > 1 { 41 handle, err := syntax.ParseHandle(p[1:]) 42 if err != nil { 43 keep = append(keep, p) 44 continue 45 } 46 id, err := dir.LookupHandle(ctx, handle) 47 if err != nil { 48 if err != identity.ErrHandleNotFound { 49 slog.Error("failed to resolve handle", "err", err) 50 } 51 continue 52 } 53 params.Mentions = &id.DID 54 continue 55 } 56 57 tokParts := strings.SplitN(p, ":", 2) 58 if len(tokParts) == 1 { 59 keep = append(keep, p) 60 continue 61 } 62 63 switch tokParts[0] { 64 case "did": 65 // Used as a hack for `from:me` when supplied by the client 66 did, err := syntax.ParseDID(p) 67 if err != nil { 68 continue 69 } 70 params.Author = &did 71 continue 72 case "from", "to", "mentions": 73 raw := tokParts[1] 74 if raw == "me" { 75 if viewer != nil && tokParts[0] == "from" { 76 params.Author = viewer 77 } else if viewer != nil { 78 params.Mentions = viewer 79 } 80 continue 81 } 82 if strings.HasPrefix(raw, "@") && len(raw) > 1 { 83 raw = raw[1:] 84 } 85 handle, err := syntax.ParseHandle(raw) 86 if err != nil { 87 continue 88 } 89 id, err := dir.LookupHandle(ctx, handle) 90 if err != nil { 91 if err != identity.ErrHandleNotFound { 92 slog.Error("failed to resolve handle", "err", err) 93 } 94 continue 95 } 96 if tokParts[0] == "from" { 97 params.Author = &id.DID 98 } else { 99 params.Mentions = &id.DID 100 } 101 continue 102 case "http", "https": 103 params.URL = p 104 continue 105 case "domain": 106 params.Domain = tokParts[1] 107 continue 108 case "lang": 109 lang, err := syntax.ParseLanguage(tokParts[1]) 110 if nil == err { 111 params.Lang = &lang 112 } 113 continue 114 case "since", "until": 115 var dt syntax.Datetime 116 // first try just date 117 date, err := time.Parse(time.DateOnly, tokParts[1]) 118 if nil == err { 119 dt = syntax.Datetime(date.Format(syntax.AtprotoDatetimeLayout)) 120 } else { 121 // fallback to formal atproto datetime format 122 dt, err = syntax.ParseDatetimeLenient(tokParts[1]) 123 if err != nil { 124 continue 125 } 126 } 127 if tokParts[0] == "since" { 128 params.Since = &dt 129 } else { 130 params.Until = &dt 131 } 132 continue 133 } 134 135 keep = append(keep, p) 136 } 137 138 out := "" 139 for _, p := range keep { 140 if out == "" { 141 out = p 142 } else { 143 out += " " + p 144 } 145 } 146 if out == "" { 147 out = "*" 148 } 149 params.Query = out 150 return params 151}