]> git.giacomolli.uk - onechat/blob - whatsapp_groups.go
Write the log rules in the Go skill
[onechat] / whatsapp_groups.go
1 package main
2
3 import (
4 "context"
5 "log/slog"
6 "maps"
7 "sync"
8 "time"
9 )
10
11 const (
12 // whatsappGroupsRefresh is the shortest time between two reads of the
13 // groups from WhatsApp for a group with no name.
14 whatsappGroupsRefresh = time.Minute
15
16 // whatsappUnknownGroup is the name of a group whose name is not known.
17 whatsappUnknownGroup = "A group with no known name"
18
19 // whatsappGroupsAge is how long whatsapp_find uses the groups that it
20 // read from WhatsApp.
21 whatsappGroupsAge = time.Hour
22 )
23
24 // newWhatsAppGroups returns the names of the groups with no names yet. logger
25 // gets the failures of a read.
26 func newWhatsAppGroups(logger *slog.Logger) *WhatsAppGroups {
27 return &WhatsAppGroups{
28 logger: logger,
29 names: make(map[string]string),
30 }
31 }
32
33 // WhatsAppGroups holds the names of the groups of the account, by the ID of the
34 // group. They come from the events of the groups and from reads of the groups
35 // from WhatsApp, and they stay in memory only. triedAt is the time of the last
36 // read, and readAt the time of the last read that succeeded. The mutex
37 // protects all fields after logger.
38 type WhatsAppGroups struct {
39 logger *slog.Logger
40
41 mutex sync.Mutex
42 names map[string]string
43 triedAt time.Time
44 readAt time.Time
45 }
46
47 // name returns the name of the group id, or whatsappUnknownGroup. A group that
48 // is not in the names reads the groups from WhatsApp with the client of link
49 // again, at most once in whatsappGroupsRefresh.
50 func (g *WhatsAppGroups) name(ctx context.Context, link WhatsAppLink, id string) string {
51 g.mutex.Lock()
52
53 name, found := g.names[id]
54 fresh := time.Since(g.triedAt) < whatsappGroupsRefresh
55 g.mutex.Unlock()
56
57 if !found && !fresh {
58 g.read(ctx, link)
59
60 g.mutex.Lock()
61
62 name, found = g.names[id]
63 g.mutex.Unlock()
64 }
65
66 if !found || name == "" {
67 return whatsappUnknownGroup
68 }
69
70 return name
71 }
72
73 // read reads the names of the groups of the account from WhatsApp with the
74 // client of link, and tells if it could.
75 func (g *WhatsAppGroups) read(ctx context.Context, link WhatsAppLink) bool {
76 if link.client == nil {
77 return false
78 }
79
80 ctx, cancel := context.WithTimeout(ctx, whatsappLookupTime)
81 defer cancel()
82
83 groups, err := link.client.GetJoinedGroups(ctx)
84 if err != nil {
85 g.logger.Warn("group list failed", "err", whatsappRedact(link.own, err.Error()))
86 }
87
88 g.mutex.Lock()
89 defer g.mutex.Unlock()
90
91 g.triedAt = time.Now()
92 if err != nil {
93 return false
94 }
95
96 g.readAt = g.triedAt
97 for _, group := range groups {
98 g.names[group.JID.String()] = group.Name
99 }
100
101 return true
102 }
103
104 // set makes name the name of the group id.
105 func (g *WhatsAppGroups) set(id, name string) {
106 g.mutex.Lock()
107 defer g.mutex.Unlock()
108
109 g.names[id] = name
110 }
111
112 // stale tells if the last read that succeeded is older than whatsappGroupsAge.
113 func (g *WhatsAppGroups) stale() bool {
114 g.mutex.Lock()
115 defer g.mutex.Unlock()
116
117 return time.Since(g.readAt) >= whatsappGroupsAge
118 }
119
120 // all returns a copy of the names of the groups.
121 func (g *WhatsAppGroups) all() map[string]string {
122 g.mutex.Lock()
123 defer g.mutex.Unlock()
124
125 return maps.Clone(g.names)
126 }