Trim or remove comments that: - State the obvious (function names already convey purpose) - Repeat what the code clearly shows - Are excessively long without adding value Keep comments that explain *why*, not *what*.
201 lines
4.3 KiB
Go
201 lines
4.3 KiB
Go
// kafka — a privacy-respecting metasearch engine
|
|
// Copyright (C) 2026-present metamorphosis-dev
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
package contracts
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
)
|
|
|
|
// MainResult represents one element of the `results` array.
|
|
// Unknown keys are preserved in `raw` and re-emitted via MarshalJSON.
|
|
type MainResult struct {
|
|
raw map[string]any
|
|
|
|
Template string `json:"template"`
|
|
Title string `json:"title"`
|
|
Content string `json:"content"`
|
|
URL *string `json:"url"`
|
|
Pubdate *string `json:"pubdate"`
|
|
Thumbnail string `json:"thumbnail"`
|
|
|
|
Engine string `json:"engine"`
|
|
Score float64 `json:"score"`
|
|
Category string `json:"category"`
|
|
Priority string `json:"priority"`
|
|
|
|
Positions []int `json:"positions"`
|
|
Engines []string `json:"engines"`
|
|
|
|
OpenGroup bool `json:"open_group"`
|
|
CloseGroup bool `json:"close_group"`
|
|
|
|
ParsedURL any `json:"parsed_url"`
|
|
}
|
|
|
|
func (mr *MainResult) UnmarshalJSON(data []byte) error {
|
|
dec := json.NewDecoder(bytes.NewReader(data))
|
|
dec.UseNumber()
|
|
|
|
var m map[string]any
|
|
if err := dec.Decode(&m); err != nil {
|
|
return err
|
|
}
|
|
|
|
mr.raw = m
|
|
|
|
mr.Template = stringOrEmpty(m["template"])
|
|
mr.Title = stringOrEmpty(m["title"])
|
|
mr.Content = stringOrEmpty(m["content"])
|
|
mr.Engine = stringOrEmpty(m["engine"])
|
|
mr.Thumbnail = stringOrEmpty(m["thumbnail"])
|
|
mr.Category = stringOrEmpty(m["category"])
|
|
mr.Priority = stringOrEmpty(m["priority"])
|
|
|
|
if s, ok := stringOrNullable(m["url"]); ok {
|
|
mr.URL = &s
|
|
}
|
|
if s, ok := stringOrNullable(m["pubdate"]); ok {
|
|
mr.Pubdate = &s
|
|
}
|
|
|
|
mr.Score = floatOrZero(m["score"])
|
|
|
|
if v, ok := sliceOfStrings(m["engines"]); ok {
|
|
mr.Engines = v
|
|
}
|
|
if v, ok := sliceOfInts(m["positions"]); ok {
|
|
mr.Positions = v
|
|
}
|
|
|
|
if v, ok := boolOrFalse(m["open_group"]); ok {
|
|
mr.OpenGroup = v
|
|
}
|
|
if v, ok := boolOrFalse(m["close_group"]); ok {
|
|
mr.CloseGroup = v
|
|
}
|
|
|
|
mr.ParsedURL = m["parsed_url"]
|
|
|
|
return nil
|
|
}
|
|
|
|
func (mr MainResult) MarshalJSON() ([]byte, error) {
|
|
if mr.raw != nil {
|
|
return json.Marshal(mr.raw)
|
|
}
|
|
|
|
m := map[string]any{
|
|
"template": mr.Template,
|
|
"title": mr.Title,
|
|
"content": mr.Content,
|
|
"url": mr.URL,
|
|
"pubdate": mr.Pubdate,
|
|
"thumbnail": mr.Thumbnail,
|
|
"engine": mr.Engine,
|
|
"score": mr.Score,
|
|
"category": mr.Category,
|
|
"priority": mr.Priority,
|
|
"positions": mr.Positions,
|
|
"engines": mr.Engines,
|
|
"open_group": mr.OpenGroup,
|
|
"close_group": mr.CloseGroup,
|
|
"parsed_url": mr.ParsedURL,
|
|
}
|
|
return json.Marshal(m)
|
|
}
|
|
|
|
func stringOrEmpty(v any) string {
|
|
s, _ := v.(string)
|
|
return s
|
|
}
|
|
|
|
func stringOrNullable(v any) (string, bool) {
|
|
if v == nil {
|
|
return "", false
|
|
}
|
|
s, ok := v.(string)
|
|
return s, ok
|
|
}
|
|
|
|
func floatOrZero(v any) float64 {
|
|
switch t := v.(type) {
|
|
case float64:
|
|
return t
|
|
case float32:
|
|
return float64(t)
|
|
case int:
|
|
return float64(t)
|
|
case int64:
|
|
return float64(t)
|
|
case json.Number:
|
|
f, _ := t.Float64()
|
|
return f
|
|
default:
|
|
return 0
|
|
}
|
|
}
|
|
|
|
func boolOrFalse(v any) (bool, bool) {
|
|
b, ok := v.(bool)
|
|
if !ok {
|
|
return false, false
|
|
}
|
|
return b, true
|
|
}
|
|
|
|
func sliceOfStrings(v any) ([]string, bool) {
|
|
raw, ok := v.([]any)
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
out := make([]string, 0, len(raw))
|
|
for _, item := range raw {
|
|
s, ok := item.(string)
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
out = append(out, s)
|
|
}
|
|
return out, true
|
|
}
|
|
|
|
func sliceOfInts(v any) ([]int, bool) {
|
|
raw, ok := v.([]any)
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
out := make([]int, 0, len(raw))
|
|
for _, item := range raw {
|
|
switch t := item.(type) {
|
|
case float64:
|
|
out = append(out, int(t))
|
|
case int:
|
|
out = append(out, t)
|
|
case json.Number:
|
|
i64, err := t.Int64()
|
|
if err != nil {
|
|
return nil, false
|
|
}
|
|
out = append(out, int(i64))
|
|
default:
|
|
return nil, false
|
|
}
|
|
}
|
|
return out, true
|
|
}
|
|
|