]> git.giacomolli.uk - onechat/blob - whatsapp_groups.go
Start a turn when a schedule comes due
[onechat] / whatsapp_groups.go
1 package main
2
3 import (
4         "context"
5         "log"
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 *log.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 *log.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         name, found := g.names[id]
53         fresh := time.Since(g.triedAt) < whatsappGroupsRefresh
54         g.mutex.Unlock()
55
56         if !found && !fresh {
57                 g.read(ctx, link)
58
59                 g.mutex.Lock()
60                 name, found = g.names[id]
61                 g.mutex.Unlock()
62         }
63
64         if !found || name == "" {
65                 return whatsappUnknownGroup
66         }
67
68         return name
69 }
70
71 // read reads the names of the groups of the account from WhatsApp with the
72 // client of link, and tells if it could.
73 func (g *WhatsAppGroups) read(ctx context.Context, link WhatsAppLink) bool {
74         if link.client == nil {
75                 return false
76         }
77
78         ctx, cancel := context.WithTimeout(ctx, whatsappLookupTime)
79         defer cancel()
80
81         groups, err := link.client.GetJoinedGroups(ctx)
82         if err != nil {
83                 g.logger.Printf("read the groups: %s", whatsappRedact(link.own, err.Error()))
84         }
85
86         g.mutex.Lock()
87         defer g.mutex.Unlock()
88
89         g.triedAt = time.Now()
90         if err != nil {
91                 return false
92         }
93
94         g.readAt = g.triedAt
95         for _, group := range groups {
96                 g.names[group.JID.String()] = group.Name
97         }
98
99         return true
100 }
101
102 // set makes name the name of the group id.
103 func (g *WhatsAppGroups) set(id, name string) {
104         g.mutex.Lock()
105         defer g.mutex.Unlock()
106
107         g.names[id] = name
108 }
109
110 // stale tells if the last read that succeeded is older than whatsappGroupsAge.
111 func (g *WhatsAppGroups) stale() bool {
112         g.mutex.Lock()
113         defer g.mutex.Unlock()
114
115         return time.Since(g.readAt) >= whatsappGroupsAge
116 }
117
118 // all returns a copy of the names of the groups.
119 func (g *WhatsAppGroups) all() map[string]string {
120         g.mutex.Lock()
121         defer g.mutex.Unlock()
122
123         return maps.Clone(g.names)
124 }