2021-11-20 21:26:39 +00:00
|
|
|
/*
|
|
|
|
* Ekster is a microsub server
|
|
|
|
* Copyright (c) 2021 The Ekster authors
|
|
|
|
*
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU 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 General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
2018-03-27 19:25:39 +00:00
|
|
|
// Package microsub describes the protocol methods of the Microsub protocol
|
2018-02-16 21:13:01 +00:00
|
|
|
package microsub
|
|
|
|
|
2019-01-03 20:34:36 +00:00
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
2019-03-24 15:21:38 +00:00
|
|
|
|
|
|
|
"p83.nl/go/ekster/pkg/sse"
|
2019-01-03 20:34:36 +00:00
|
|
|
)
|
|
|
|
|
2018-02-16 21:13:01 +00:00
|
|
|
/*
|
|
|
|
channels
|
|
|
|
search
|
|
|
|
preview
|
|
|
|
follow / unfollow
|
|
|
|
timeline
|
|
|
|
mute / unmute
|
|
|
|
block / unblock
|
|
|
|
*/
|
|
|
|
|
2019-03-07 19:55:25 +00:00
|
|
|
// Constants for Unread
|
2019-01-03 20:34:36 +00:00
|
|
|
const (
|
2019-01-03 21:06:16 +00:00
|
|
|
UnreadBool = 0
|
|
|
|
UnreadCount = 1
|
2019-01-03 20:34:36 +00:00
|
|
|
)
|
|
|
|
|
2019-03-07 19:55:25 +00:00
|
|
|
// Unread is a special int/bool value for the JSON response
|
2019-01-03 20:34:36 +00:00
|
|
|
type Unread struct {
|
|
|
|
Type int
|
|
|
|
Unread bool
|
|
|
|
UnreadCount int
|
|
|
|
}
|
|
|
|
|
2018-02-16 21:13:01 +00:00
|
|
|
// Channel contains information about a channel.
|
|
|
|
type Channel struct {
|
|
|
|
// UID is a unique id for the channel
|
2018-04-07 23:45:56 +00:00
|
|
|
UID string `json:"uid"`
|
|
|
|
Name string `json:"name"`
|
2019-01-03 21:06:16 +00:00
|
|
|
Unread Unread `json:"unread,omitempty"`
|
2018-02-16 21:13:01 +00:00
|
|
|
}
|
|
|
|
|
2019-03-24 15:21:38 +00:00
|
|
|
// Card contains the fields of an author or location.
|
2018-04-08 18:23:00 +00:00
|
|
|
type Card struct {
|
2018-12-08 15:56:08 +00:00
|
|
|
// Filled bool `json:"filled,omitempty"`
|
2018-04-08 18:23:00 +00:00
|
|
|
Type string `json:"type,omitempty"`
|
2018-09-12 20:35:49 +00:00
|
|
|
Name string `json:"name,omitempty" mf2:"name"`
|
|
|
|
URL string `json:"url,omitempty" mf2:"url"`
|
|
|
|
Photo string `json:"photo,omitempty" mf2:"photo"`
|
|
|
|
Locality string `json:"locality,omitempty" mf2:"locality"`
|
|
|
|
Region string `json:"region,omitempty" mf2:"region"`
|
|
|
|
CountryName string `json:"country-name,omitempty" mf2:"country-name"`
|
|
|
|
Longitude string `json:"longitude,omitempty" mf2:"longitude"`
|
|
|
|
Latitude string `json:"latitude,omitempty" mf2:"latitude"`
|
2018-02-16 21:13:01 +00:00
|
|
|
}
|
|
|
|
|
2019-03-24 15:21:38 +00:00
|
|
|
// Content contains the Text or HTML content of an Item.
|
2018-02-16 21:13:01 +00:00
|
|
|
type Content struct {
|
2018-09-12 20:35:49 +00:00
|
|
|
Text string `json:"text,omitempty" mf2:"value"`
|
|
|
|
HTML string `json:"html,omitempty" mf2:"html"`
|
2018-02-16 21:13:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Item is a post object
|
|
|
|
type Item struct {
|
2018-08-05 15:23:50 +00:00
|
|
|
Type string `json:"type"`
|
2018-09-12 20:35:49 +00:00
|
|
|
Name string `json:"name,omitempty" mf2:"name"`
|
|
|
|
Published string `json:"published,omitempty" mf2:"published"`
|
|
|
|
Updated string `json:"updated,omitempty" mf2:"updated"`
|
|
|
|
URL string `json:"url,omitempty" mf2:"url"`
|
|
|
|
UID string `json:"uid,omitempty" mf2:"uid"`
|
|
|
|
Author *Card `json:"author,omitempty" mf2:"author"`
|
|
|
|
Category []string `json:"category,omitempty" mf2:"category"`
|
|
|
|
Photo []string `json:"photo,omitempty" mf2:"photo"`
|
|
|
|
LikeOf []string `json:"like-of,omitempty" mf2:"like-of"`
|
|
|
|
BookmarkOf []string `json:"bookmark-of,omitempty" mf2:"bookmark-of"`
|
|
|
|
RepostOf []string `json:"repost-of,omitempty" mf2:"repost-of"`
|
|
|
|
InReplyTo []string `json:"in-reply-to,omitempty" mf2:"in-reply-to"`
|
|
|
|
Content *Content `json:"content,omitempty" mf2:"content"`
|
2018-12-29 09:34:51 +00:00
|
|
|
Summary string `json:"summary,omitempty" mf2:"summary"`
|
2018-09-12 20:35:49 +00:00
|
|
|
Latitude string `json:"latitude,omitempty" mf2:"latitude"`
|
|
|
|
Longitude string `json:"longitude,omitempty" mf2:"longitude"`
|
|
|
|
Checkin *Card `json:"checkin,omitempty" mf2:"checkin"`
|
2018-08-05 15:23:50 +00:00
|
|
|
Refs map[string]Item `json:"refs,omitempty"`
|
|
|
|
ID string `json:"_id,omitempty"`
|
|
|
|
Read bool `json:"_is_read"`
|
2021-10-21 19:47:37 +00:00
|
|
|
Source *Source `json:"_source,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// Source is an Item source
|
|
|
|
type Source struct {
|
|
|
|
ID string `json:"_id"`
|
|
|
|
URL string `json:"url"`
|
|
|
|
Name string `json:"name"`
|
|
|
|
Photo string `json:"photo"`
|
2018-02-16 21:13:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Pagination contains information about paging
|
|
|
|
type Pagination struct {
|
|
|
|
After string `json:"after,omitempty"`
|
|
|
|
Before string `json:"before,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// Timeline is a combination of items and paging information
|
|
|
|
type Timeline struct {
|
2018-04-06 23:27:27 +00:00
|
|
|
Items []Item `json:"items"`
|
|
|
|
Paging Pagination `json:"paging"`
|
2018-02-16 21:13:01 +00:00
|
|
|
}
|
|
|
|
|
2019-03-24 15:21:38 +00:00
|
|
|
// Feed is one microsub feed.
|
2018-02-16 21:13:01 +00:00
|
|
|
type Feed struct {
|
2018-04-08 09:44:41 +00:00
|
|
|
Type string `json:"type"`
|
|
|
|
URL string `json:"url"`
|
|
|
|
Name string `json:"name,omitempty"`
|
|
|
|
Photo string `json:"photo,omitempty"`
|
|
|
|
Description string `json:"description,omitempty"`
|
2018-04-08 18:23:00 +00:00
|
|
|
Author Card `json:"author,omitempty"`
|
2018-02-16 21:13:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Microsub is the main protocol that should be implemented by a backend
|
|
|
|
type Microsub interface {
|
2018-07-07 14:40:04 +00:00
|
|
|
ChannelsGetList() ([]Channel, error)
|
|
|
|
ChannelsCreate(name string) (Channel, error)
|
|
|
|
ChannelsUpdate(uid, name string) (Channel, error)
|
|
|
|
ChannelsDelete(uid string) error
|
2018-02-16 21:13:01 +00:00
|
|
|
|
2018-07-07 14:40:04 +00:00
|
|
|
TimelineGet(before, after, channel string) (Timeline, error)
|
2018-02-16 21:13:01 +00:00
|
|
|
|
2018-07-07 14:40:04 +00:00
|
|
|
MarkRead(channel string, entry []string) error
|
2018-03-27 22:40:04 +00:00
|
|
|
|
2018-07-07 14:40:04 +00:00
|
|
|
FollowGetList(uid string) ([]Feed, error)
|
|
|
|
FollowURL(uid string, url string) (Feed, error)
|
2018-02-16 21:13:01 +00:00
|
|
|
|
2018-07-07 14:40:04 +00:00
|
|
|
UnfollowURL(uid string, url string) error
|
2018-02-16 21:13:01 +00:00
|
|
|
|
2018-07-07 14:40:04 +00:00
|
|
|
Search(query string) ([]Feed, error)
|
|
|
|
PreviewURL(url string) (Timeline, error)
|
2019-03-24 15:21:38 +00:00
|
|
|
|
2021-05-30 20:01:34 +00:00
|
|
|
ItemSearch(channel, query string) ([]Item, error)
|
|
|
|
|
2019-03-24 15:21:38 +00:00
|
|
|
Events() (chan sse.Message, error)
|
2018-02-16 21:13:01 +00:00
|
|
|
}
|
2019-01-03 20:34:36 +00:00
|
|
|
|
2019-03-24 15:21:38 +00:00
|
|
|
// MarshalJSON encodes an Unread value as JSON
|
2019-01-03 21:06:16 +00:00
|
|
|
func (unread Unread) MarshalJSON() ([]byte, error) {
|
|
|
|
switch unread.Type {
|
|
|
|
case UnreadBool:
|
|
|
|
return json.Marshal(unread.Unread)
|
|
|
|
case UnreadCount:
|
|
|
|
return json.Marshal(unread.UnreadCount)
|
|
|
|
}
|
|
|
|
return json.Marshal(nil)
|
2019-01-03 20:34:36 +00:00
|
|
|
}
|
|
|
|
|
2019-03-24 15:21:38 +00:00
|
|
|
// UnmarshalJSON decodes an Unread value from JSON
|
2019-01-03 20:34:36 +00:00
|
|
|
func (unread *Unread) UnmarshalJSON(bytes []byte) error {
|
|
|
|
var b bool
|
|
|
|
err := json.Unmarshal(bytes, &b)
|
|
|
|
if err == nil {
|
2019-01-03 21:06:16 +00:00
|
|
|
unread.Type = UnreadBool
|
2019-01-03 20:34:36 +00:00
|
|
|
unread.Unread = b
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var count int
|
|
|
|
err = json.Unmarshal(bytes, &count)
|
|
|
|
if err == nil {
|
2019-01-03 21:06:16 +00:00
|
|
|
unread.Type = UnreadCount
|
2019-01-03 20:34:36 +00:00
|
|
|
unread.UnreadCount = count
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return fmt.Errorf("can't unmarshal as bool or int")
|
|
|
|
}
|
|
|
|
|
2019-03-24 15:21:38 +00:00
|
|
|
// String returns a string of the unread value
|
2019-01-03 20:34:36 +00:00
|
|
|
func (unread Unread) String() string {
|
|
|
|
switch unread.Type {
|
2019-01-03 21:06:16 +00:00
|
|
|
case UnreadBool:
|
2019-01-03 20:34:36 +00:00
|
|
|
return fmt.Sprint(unread.Unread)
|
2019-01-03 21:06:16 +00:00
|
|
|
case UnreadCount:
|
2019-01-03 20:34:36 +00:00
|
|
|
return fmt.Sprint(unread.UnreadCount)
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2019-03-24 15:21:38 +00:00
|
|
|
// HasUnread return true of there are unread items.
|
2019-01-03 20:34:36 +00:00
|
|
|
func (unread *Unread) HasUnread() bool {
|
|
|
|
switch unread.Type {
|
2019-01-03 21:06:16 +00:00
|
|
|
case UnreadBool:
|
2019-01-03 20:34:36 +00:00
|
|
|
return unread.Unread
|
2019-01-03 21:06:16 +00:00
|
|
|
case UnreadCount:
|
2019-01-03 20:34:36 +00:00
|
|
|
return unread.UnreadCount > 0
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|