Documentation
¶
Overview ¶
Package storage contains common structures for iterating over peer storage.
Index ¶
- Constants
- Variables
- func DecodePeer(data []byte, p *Peer) error
- func EncodePeer(p Peer) ([]byte, error)
- func ForEach(ctx context.Context, iterator PeerIterator, cb func(Peer) error) error
- func PeerUnchanged(stored []byte, value Peer, enc PeerEncoding) bool
- func UpdateHook(next telegram.UpdateHandler, storage PeerStorage) telegram.UpdateHandler
- type BatchPeerStorage
- type Peer
- func (p Peer) AsInputChannel() (*tg.InputChannel, bool)
- func (p Peer) AsInputPeer() tg.InputPeerClass
- func (p Peer) AsInputUser() (*tg.InputUser, bool)
- func (p *Peer) FromChat(chat tg.ChatClass) bool
- func (p *Peer) FromInputPeer(input tg.InputPeerClass) error
- func (p *Peer) FromUser(user tg.UserClass) bool
- func (p *Peer) Keys() []string
- func (p Peer) Marshal(e *jx.Encoder) error
- func (p Peer) MarshalJSON() ([]byte, error)
- func (p Peer) String() string
- func (p *Peer) Unmarshal(d *jx.Decoder) error
- func (p *Peer) UnmarshalJSON(data []byte) error
- type PeerCollector
- type PeerEncoding
- type PeerIterator
- type PeerKey
- type PeerStorage
- type ResolverCache
Examples ¶
Constants ¶
const LatestVersion = 2
LatestVersion is a latest supported version of data.
Variables ¶
var ErrPeerNotFound = errors.New("peer not found")
ErrPeerNotFound is a special error to return when peer not found.
var ErrPeerUnmarshalMustInvalidate = errors.New("outdated data for Peer (cache miss, must invalidate)")
ErrPeerUnmarshalMustInvalidate means that persisted Peer is outdated and must be invalidated.
var PeerKeyPrefix = []byte("peer") // nolint:gochecknoglobals
PeerKeyPrefix is a key prefix of peer key.
Functions ¶
func DecodePeer ¶ added in v0.26.0
DecodePeer decodes peer encoded by EncodePeer or Peer.MarshalJSON.
Like Peer.UnmarshalJSON, it returns ErrPeerUnmarshalMustInvalidate for outdated data, which must be treated as a cache miss.
Data of an unknown format, e.g. written by a newer version, also gives ErrPeerUnmarshalMustInvalidate.
func EncodePeer ¶ added in v0.26.0
EncodePeer encodes peer in the compact binary format.
Use DecodePeer to decode it: it also accepts the JSON format of Peer.MarshalJSON, so values stored before stay readable.
func PeerUnchanged ¶ added in v0.26.0
func PeerUnchanged(stored []byte, value Peer, enc PeerEncoding) bool
PeerUnchanged reports whether stored is the encoding of value in enc, ignoring CreatedAt, which is set to the current time on every peer conversion. A value stored in another format is reported as changed, so it is rewritten.
Storages use it to skip writing peers that did not change, e.g. users repeated in every update. So the stored CreatedAt is when this content was first stored, not when the peer was last seen.
func UpdateHook ¶ added in v0.3.0
func UpdateHook(next telegram.UpdateHandler, storage PeerStorage) telegram.UpdateHandler
UpdateHook creates update hook, to collect peer data from updates.
Example ¶
package main
import (
"context"
"fmt"
"os"
"os/signal"
pebbledb "github.com/cockroachdb/pebble"
"github.com/go-faster/errors"
"github.com/gotd/td/telegram"
"github.com/gotd/td/telegram/message"
"github.com/gotd/td/tg"
"github.com/gotd/contrib/pebble"
"github.com/gotd/contrib/storage"
)
func updatesHook(ctx context.Context) error {
db, err := pebbledb.Open("pebble.db", &pebbledb.Options{})
if err != nil {
return errors.Wrap(err, "create pebble storage")
}
s := pebble.NewPeerStorage(db)
dispatcher := tg.NewUpdateDispatcher()
handler := storage.UpdateHook(dispatcher, s)
client, err := telegram.ClientFromEnvironment(telegram.Options{
UpdateHandler: handler,
})
if err != nil {
return errors.Wrap(err, "create client")
}
raw := tg.NewClient(client)
sender := message.NewSender(raw)
dispatcher.OnNewMessage(func(ctx context.Context, e tg.Entities, update *tg.UpdateNewMessage) error {
msg, ok := update.Message.(*tg.Message)
if !ok {
return nil
}
// Use PeerID to find peer because *Short updates does not contain any entities, so it necessary to
// store some entities.
// Storage can be filled using PeerCollector.
p, err := storage.FindPeer(ctx, s, msg.GetPeerID())
if err != nil {
return err
}
_, err = sender.To(p.AsInputPeer()).Text(ctx, msg.GetMessage())
return err
})
return client.Run(ctx, func(ctx context.Context) error {
return telegram.RunUntilCanceled(ctx, client)
})
}
func main() {
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()
if err := updatesHook(ctx); err != nil {
_, _ = fmt.Fprintf(os.Stderr, "%+v\n", err)
os.Exit(1)
}
}
Output:
Types ¶
type BatchPeerStorage ¶ added in v0.26.0
type BatchPeerStorage interface {
PeerStorage
// AddMany adds given peers to the storage.
AddMany(ctx context.Context, values []Peer) error
}
BatchPeerStorage is a PeerStorage that can add many peers at once, e.g. in a single transaction.
UpdateHook and PeerCollector use it, if implemented, to write all peers of an update or of a batch with one write.
type Peer ¶
type Peer struct {
Version int
Key dialogs.DialogKey
// CreatedAt is when this content of the peer was first stored. Storages
// skip writing a peer that did not change, so it is not updated when the
// same peer is seen again: don't use it as "last seen".
CreatedAt time.Time
User *tg.User
Chat *tg.Chat
Channel *tg.Channel
Metadata map[string]any
}
Peer is abstraction for persisted peer object.
Note: unmarshal error ErrPeerUnmarshalMustInvalidate MUST be considered as cache miss and cache entry MUST be invalidated.
The only valid ways to marshal and unmarshal Peer are EncodePeer and DecodePeer (compact, used by storages), or MarshalJSON and UnmarshalJSON. DecodePeer also reads the JSON format.
func (Peer) AsInputChannel ¶
func (p Peer) AsInputChannel() (*tg.InputChannel, bool)
AsInputChannel tries to convert peer to *tg.InputChannel.
func (Peer) AsInputPeer ¶
func (p Peer) AsInputPeer() tg.InputPeerClass
AsInputPeer tries to convert peer to tg.InputPeerClass.
func (Peer) AsInputUser ¶
AsInputUser tries to convert peer to *tg.InputUser.
func (*Peer) FromInputPeer ¶
func (p *Peer) FromInputPeer(input tg.InputPeerClass) error
FromInputPeer fills Peer object using given tg.InputPeerClass.
func (*Peer) FromUser ¶ added in v0.3.0
FromUser fills Peer object using given tg.UserClass.
Returns false for empty and min users: min users have no usable access hash and only part of the fields, so they must not replace stored peers. See https://core.telegram.org/api/min.
func (*Peer) Keys ¶ added in v0.3.0
Keys returns list of all associated keys (phones, usernames, etc.) stored in the peer.
func (Peer) MarshalJSON ¶ added in v0.16.0
func (*Peer) UnmarshalJSON ¶ added in v0.16.0
type PeerCollector ¶ added in v0.5.0
type PeerCollector struct {
// contains filtered or unexported fields
}
PeerCollector is a simple helper to collect peers from different sources.
func CollectPeers ¶ added in v0.5.0
func CollectPeers(storage PeerStorage) PeerCollector
CollectPeers creates new PeerCollector.
Example ¶
package main
import (
"context"
"fmt"
"os"
"os/signal"
pebbledb "github.com/cockroachdb/pebble"
"github.com/go-faster/errors"
"github.com/gotd/td/telegram"
"github.com/gotd/td/telegram/query"
"github.com/gotd/td/tg"
"github.com/gotd/contrib/pebble"
"github.com/gotd/contrib/storage"
)
func peerCollector(ctx context.Context) error {
db, err := pebbledb.Open("pebble.db", &pebbledb.Options{})
if err != nil {
return errors.Wrap(err, "create pebble storage")
}
s := pebble.NewPeerStorage(db)
collector := storage.CollectPeers(s)
client, err := telegram.ClientFromEnvironment(telegram.Options{})
if err != nil {
return errors.Wrap(err, "create client")
}
raw := tg.NewClient(client)
return client.Run(ctx, func(ctx context.Context) error {
// Fills storage with user dialogs peers metadata.
return collector.Dialogs(ctx, query.GetDialogs(raw).Iter())
})
}
func main() {
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()
if err := peerCollector(ctx); err != nil {
_, _ = fmt.Fprintf(os.Stderr, "%+v\n", err)
os.Exit(1)
}
}
Output:
func (PeerCollector) Contacts ¶ added in v0.5.0
func (c PeerCollector) Contacts(ctx context.Context, contacts *tg.ContactsContacts) error
Contacts collects peers from contacts iterator.
func (PeerCollector) Participants ¶ added in v0.5.0
func (c PeerCollector) Participants(ctx context.Context, iter *participants.Iterator) error
Participants collects peers from participants iterator.
type PeerEncoding ¶ added in v0.26.0
type PeerEncoding uint8
PeerEncoding is the format storages write peers in.
DecodePeer reads both formats, so changing it keeps stored values readable. Versions of this package before the binary format can't read binary values: downgrading after binary values were written needs the peer storage cleared.
const ( // PeerEncodingJSON is the JSON format of Peer.MarshalJSON. Default. PeerEncodingJSON PeerEncoding = iota // PeerEncodingBinary is the compact binary format of EncodePeer. PeerEncodingBinary )
type PeerIterator ¶ added in v0.5.0
PeerIterator is a peer iterator.
type PeerKey ¶ added in v0.5.0
PeerKey is unique key of peer object.
type PeerStorage ¶
type PeerStorage interface {
// Add adds given peer to the storage.
Add(ctx context.Context, value Peer) error
// Find finds peer using given key.
// If peer not found, it returns ErrPeerNotFound error.
Find(ctx context.Context, key PeerKey) (Peer, error)
// Assign adds given peer to the storage and associates it to the given key.
Assign(ctx context.Context, key string, value Peer) error
// Resolve finds peer using associated key.
// If peer not found, it returns ErrPeerNotFound error.
Resolve(ctx context.Context, key string) (Peer, error)
// Iterate creates and returns new PeerIterator.
Iterate(ctx context.Context) (PeerIterator, error)
}
PeerStorage is abstraction for peer storage.
type ResolverCache ¶
type ResolverCache struct {
// contains filtered or unexported fields
}
ResolverCache is a peer.Resolver cache implemented using peer storage.
Example ¶
package main
import (
"context"
"fmt"
"os"
"os/signal"
pebbledb "github.com/cockroachdb/pebble"
"github.com/go-faster/errors"
"github.com/gotd/td/telegram/message"
"github.com/gotd/td/telegram/message/peer"
"github.com/gotd/td/tg"
"github.com/gotd/td/telegram"
"github.com/gotd/contrib/pebble"
"github.com/gotd/contrib/storage"
)
func resolverCache(ctx context.Context) error {
db, err := pebbledb.Open("pebble.db", &pebbledb.Options{})
if err != nil {
return errors.Wrap(err, "create pebble storage")
}
client, err := telegram.ClientFromEnvironment(telegram.Options{})
if err != nil {
return errors.Wrap(err, "create client")
}
return client.Run(ctx, func(ctx context.Context) error {
raw := tg.NewClient(client)
resolver := storage.NewResolverCache(peer.Plain(raw), pebble.NewPeerStorage(db))
s := message.NewSender(raw).WithResolver(resolver)
_, err := s.Resolve("durov").Text(ctx, "Hi!")
return err
})
}
func main() {
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()
if err := resolverCache(ctx); err != nil {
_, _ = fmt.Fprintf(os.Stderr, "%+v\n", err)
os.Exit(1)
}
}
Output:
func NewResolverCache ¶
func NewResolverCache(next peer.Resolver, storage PeerStorage) ResolverCache
NewResolverCache creates new ResolverCache.
func (ResolverCache) ResolveDomain ¶
func (r ResolverCache) ResolveDomain(ctx context.Context, domain string) (tg.InputPeerClass, error)
ResolveDomain implements peer.Resolver
func (ResolverCache) ResolvePhone ¶
func (r ResolverCache) ResolvePhone(ctx context.Context, phone string) (tg.InputPeerClass, error)
ResolvePhone implements peer.Resolver