-
Notifications
You must be signed in to change notification settings - Fork 384
/
bot.go
130 lines (111 loc) · 3.29 KB
/
bot.go
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
package bertybot
import (
"context"
"fmt"
"sync"
"time"
"go.uber.org/zap"
"moul.io/u"
"berty.tech/berty/v2/go/pkg/messengertypes"
)
type Bot struct {
client messengertypes.MessengerServiceClient
logger *zap.Logger
displayName string
bertyID *messengertypes.InstanceShareableBertyID_Reply
withReplay bool
withFromMyself bool
withEntityUpdates bool
handlers map[HandlerType][]Handler
isReplaying bool
handledEvents uint
commands map[string]command
store struct {
conversations map[string]*messengertypes.Conversation
mutex sync.Mutex
}
}
// New initializes a new Bot.
// The order of the passed options may have an impact.
func New(opts ...NewOption) (*Bot, error) {
b := Bot{
logger: zap.NewNop(),
handlers: make(map[HandlerType][]Handler),
commands: make(map[string]command),
}
b.store.conversations = make(map[string]*messengertypes.Conversation)
// configure bot with options
for _, opt := range opts {
if err := opt(&b); err != nil {
return nil, fmt.Errorf("bot: opt failed: %w", err)
}
}
// check minimal requirements
if b.client == nil {
return nil, fmt.Errorf("bot: missing messenger client")
}
// apply defaults
if b.displayName == "" {
b.displayName = "My Berty Bot"
}
// retrieve Berty ID to check if everything is well configured, and cache it for easy access
{
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
req := &messengertypes.InstanceShareableBertyID_Request{
DisplayName: b.displayName,
}
ret, err := b.client.InstanceShareableBertyID(ctx, req)
if err != nil {
return nil, fmt.Errorf("bot: cannot retrieve berty ID: %w", err)
}
b.bertyID = ret
}
return &b, nil
}
// BertyIDURL returns the shareable Berty ID in the form of `https://berty.tech/id#xxx`.
func (b *Bot) BertyIDURL() string {
return b.bertyID.WebURL
}
// PublicKey returns the public key of the messenger node.
func (b *Bot) PublicKey() string {
return u.B64Encode(b.bertyID.Link.BertyID.AccountPK)
}
// Start starts the main event loop and can be stopped by canceling the passed context.
func (b *Bot) Start(ctx context.Context) error {
b.logger.Info("connecting to the event stream")
s, err := b.client.EventStream(ctx, &messengertypes.EventStream_Request{})
if err != nil {
return fmt.Errorf("failed to listen to EventStream: %w", err)
}
b.isReplaying = true
for {
gme, err := s.Recv()
if err != nil {
return fmt.Errorf("stream error: %w", err)
}
if b.isReplaying {
if gme.Event.Type == messengertypes.StreamEvent_TypeListEnded {
b.logger.Info("finished replaying logs from the previous sessions", zap.Uint("count", b.handledEvents))
b.isReplaying = false
}
b.handledEvents++
if !b.withReplay {
continue
}
}
if err := b.handleEvent(ctx, gme.Event); err != nil {
b.logger.Error("bot.handleEvent failed", zap.Error(err))
}
}
}
func (b *Bot) CreateGroup(ctx context.Context, name string) (*messengertypes.ConversationCreate_Reply, error) {
conv, err := b.client.ConversationCreate(ctx, &messengertypes.ConversationCreate_Request{
DisplayName: name,
ContactsToInvite: nil,
})
if err != nil {
return nil, fmt.Errorf("failed to create conversation: %w", err)
}
return conv, nil
}