1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
|
package msgstore
import (
"bytes"
"context"
"encoding/base64"
"fmt"
"time"
"git.sr.ht/~sircmpwn/go-bare"
"gopkg.in/irc.v4"
"codeberg.org/emersion/soju/database"
)
type LoadMessageOptions struct {
Network *database.Network
Entity string
Limit int
Events bool
}
// Store is a per-user store for IRC messages.
type Store interface {
Close() error
// LastMsgID queries the last message ID for the given network, entity and
// date. The message ID returned may not refer to a valid message, but can be
// used in history queries.
LastMsgID(ctx context.Context, network *database.Network, entity string, t time.Time) (string, error)
// LoadLatestID queries the latest non-event messages for the given network,
// entity and date, up to a count of limit messages, sorted from oldest to newest.
LoadLatestID(ctx context.Context, id string, options *LoadMessageOptions) ([]*irc.Message, error)
Append(ctx context.Context, network *database.Network, entity string, msg *irc.Message) (id string, err error)
}
type ChatHistoryTarget struct {
Name string
LatestMessage time.Time
}
// ChatHistoryStore is a message store that supports chat history operations.
type ChatHistoryStore interface {
Store
// ListTargets lists channels and nicknames by time of the latest message.
// It returns up to limit targets, starting from start and ending on end,
// both excluded. end may be before or after start.
// If events is false, only PRIVMSG/NOTICE messages are considered.
ListTargets(ctx context.Context, network *database.Network, start, end time.Time, limit int, events bool) ([]ChatHistoryTarget, error)
// LoadBeforeTime loads up to limit messages before start down to end. The
// returned messages must be between and excluding the provided bounds.
// end is before start.
// If events is false, only PRIVMSG/NOTICE messages are considered.
LoadBeforeTime(ctx context.Context, start, end time.Time, options *LoadMessageOptions) ([]*irc.Message, error)
// LoadAfterTime loads up to limit messages after start up to end. The
// returned messages must be between and excluding the provided bounds.
// end is after start.
// If events is false, only PRIVMSG/NOTICE messages are considered.
LoadAfterTime(ctx context.Context, start, end time.Time, options *LoadMessageOptions) ([]*irc.Message, error)
}
type SearchMessageOptions struct {
Start time.Time
End time.Time
Limit int
From string
In string
Text string
}
// SearchStore is a message store that supports server-side search operations.
type SearchStore interface {
Store
// Search returns messages matching the specified options.
Search(ctx context.Context, network *database.Network, options *SearchMessageOptions) ([]*irc.Message, error)
}
// RenameNetworkStore is a message store which needs to be notified of network
// name changes.
type RenameNetworkStore interface {
Store
RenameNetwork(oldNet, newNet *database.Network) error
}
type msgIDType uint
const (
msgIDNone msgIDType = iota
msgIDMemory
msgIDFS
msgIDDB
)
const msgIDVersion uint = 0
type msgIDHeader struct {
Version uint
Network bare.Int
Target string
Type msgIDType
}
type msgIDBody interface {
msgIDType() msgIDType
}
func formatMsgID(netID int64, target string, body msgIDBody) string {
var buf bytes.Buffer
w := bare.NewWriter(&buf)
header := msgIDHeader{
Version: msgIDVersion,
Network: bare.Int(netID),
Target: target,
Type: body.msgIDType(),
}
if err := bare.MarshalWriter(w, &header); err != nil {
panic(err)
}
if err := bare.MarshalWriter(w, body); err != nil {
panic(err)
}
return base64.RawURLEncoding.EncodeToString(buf.Bytes())
}
func ParseMsgID(s string, body msgIDBody) (netID int64, target string, err error) {
b, err := base64.RawURLEncoding.DecodeString(s)
if err != nil {
return 0, "", fmt.Errorf("invalid internal message ID: %v", err)
}
r := bare.NewReader(bytes.NewReader(b))
var header msgIDHeader
if err := bare.UnmarshalBareReader(r, &header); err != nil {
return 0, "", fmt.Errorf("invalid internal message ID: %v", err)
}
if header.Version != msgIDVersion {
return 0, "", fmt.Errorf("invalid internal message ID: got version %v, want %v", header.Version, msgIDVersion)
}
if body != nil {
typ := body.msgIDType()
if header.Type != typ {
return 0, "", fmt.Errorf("invalid internal message ID: got type %v, want %v", header.Type, typ)
}
if err := bare.UnmarshalBareReader(r, body); err != nil {
return 0, "", fmt.Errorf("invalid internal message ID: %v", err)
}
}
return int64(header.Network), header.Target, nil
}
|