]> git.giacomolli.uk - onechat/blobdiff - whatsapp.go
Pass caller contexts through WhatsApp callbacks
[onechat] / whatsapp.go
index 0c68a44ace8a0184b89789e473537d75ec876199..be1acc65b8e763b131f107513b3efd9c2e71af0f 100644 (file)
@@ -95,6 +95,7 @@ type WhatsAppClient interface {
        PairPhone(ctx context.Context, phone string, showPushNotification bool, clientType whatsmeow.PairClientType, clientDisplayName string) (string, error)
        Logout(ctx context.Context) error
        GetJoinedGroups(ctx context.Context) ([]*types.GroupInfo, error)
+       Download(ctx context.Context, message whatsmeow.DownloadableMessage) ([]byte, error)
        GenerateMessageID() types.MessageID
        SendMessage(ctx context.Context, to types.JID, message *waE2E.Message, extra ...whatsmeow.SendRequestExtra) (whatsmeow.SendResponse, error)
 }
@@ -106,6 +107,7 @@ type WhatsAppClient interface {
 // the client of a device with its event handler.
 func openWhatsApp(ctx context.Context, dir string, clock func() time.Time, logger *slog.Logger, newClient func(device *store.Device, handle func(event any) bool) WhatsAppClient) (*WhatsApp, error) {
        w := &WhatsApp{
+               ctx:         ctx,
                dir:         filepath.Join(dir, whatsappDirName),
                clock:       clock,
                logger:      logger,
@@ -123,7 +125,7 @@ func openWhatsApp(ctx context.Context, dir string, clock func() time.Time, logge
                return nil, err
        }
 
-       w.inbox = newWhatsAppInbox(w.state, clock, w.groups)
+       w.inbox = newWhatsAppInbox(w.state, clock, w.groups, &ImageStore{dir: filepath.Join(dir, imageDirName)}, logger)
 
        info, err := os.Stat(w.sessionPath())
        if errors.Is(err, os.ErrNotExist) {
@@ -168,9 +170,12 @@ func openWhatsApp(ctx context.Context, dir string, clock func() time.Time, logge
 // creation, close, removal, and snapshots. It is held before mutex, and stays
 // held when removal releases mutex for client callbacks.
 //
-// The inbox puts the text messages into batches and logs them, and groups
-// holds the names of the groups. Both get a WhatsAppLink from link for each
-// call, so they use the current device and client.
+// ctx is the caller context until Run sets its context for library callbacks.
+// The mutex protects it, so callbacks follow daemon cancellation.
+//
+// The inbox puts messages into batches and logs them, and groups holds the
+// names of the groups. Both get a WhatsAppLink from link for each call, so they
+// use the current device and client.
 //
 // The library logs nothing, because its log lines hold the ID of the own
 // account, which is the phone number of the user. The own number and the own
@@ -180,6 +185,7 @@ func openWhatsApp(ctx context.Context, dir string, clock func() time.Time, logge
 // log, and those of private chats hold phone numbers, because the agent needs
 // them to send.
 type WhatsApp struct {
+       ctx         context.Context
        dir         string
        clock       func() time.Time
        logger      *slog.Logger
@@ -190,6 +196,7 @@ type WhatsApp struct {
        state       *WhatsAppState
        groups      *WhatsAppGroups
        inbox       *WhatsAppInbox
+       routes      *Routes
 
        sessionMutex   sync.Mutex
        mutex          sync.Mutex
@@ -222,6 +229,7 @@ type WhatsApp struct {
 func (w *WhatsApp) Run(ctx context.Context, l *TurnLoop, notify func(Event)) error {
        w.mutex.Lock()
 
+       w.ctx = ctx
        w.notify = notify
        linked := w.linked
        w.retrying = linked
@@ -358,23 +366,31 @@ func (w *WhatsApp) Command(args string, answer func(text string)) {
 }
 
 // Status returns the state of the link: not linked, linked and connected, or
-// linked and disconnected, with the times of the connection. It never holds the
-// phone number.
+// linked and disconnected, with the times of the connection and the routes.
+// Route source IDs can hold the phone numbers of other chats.
 func (w *WhatsApp) Status() string {
        w.mutex.Lock()
        defer w.mutex.Unlock()
 
+       routes := ""
+       if w.routes != nil {
+               listed := w.routes.List()
+               if listed != "No routes." {
+                       routes = "\nWhatsApp routes:\n" + listed
+               }
+       }
+
        switch {
        case !w.linked:
-               return "WhatsApp is not linked. Run /whatsapp pair PHONE to link it."
+               return "WhatsApp is not linked. Run /whatsapp pair PHONE to link it." + routes
        case w.connected:
-               return "WhatsApp is linked and connected since " + w.connectedAt.Format(whatsappTimeLayout) + "."
+               return "WhatsApp is linked and connected since " + w.connectedAt.Format(whatsappTimeLayout) + "." + routes
        case w.disconnectedAt.IsZero():
-               return "WhatsApp is linked and not connected yet."
+               return "WhatsApp is linked and not connected yet." + routes
        case w.connectedAt.IsZero():
-               return "WhatsApp is linked and disconnected since " + w.disconnectedAt.Format(whatsappTimeLayout) + "."
+               return "WhatsApp is linked and disconnected since " + w.disconnectedAt.Format(whatsappTimeLayout) + "." + routes
        default:
-               return "WhatsApp is linked and disconnected since " + w.disconnectedAt.Format(whatsappTimeLayout) + ". The last connection started at " + w.connectedAt.Format(whatsappTimeLayout) + "."
+               return "WhatsApp is linked and disconnected since " + w.disconnectedAt.Format(whatsappTimeLayout) + ". The last connection started at " + w.connectedAt.Format(whatsappTimeLayout) + "." + routes
        }
 }
 
@@ -890,9 +906,14 @@ func (w *WhatsApp) linkedBy(client WhatsAppClient) {
 // connected again with the length of the outage. The retries of the library and
 // Run between them log nothing.
 func (w *WhatsApp) handle(event any) bool {
+       w.mutex.Lock()
+
+       ctx := w.ctx
+       w.mutex.Unlock()
+
        switch event := event.(type) {
        case *events.Message:
-               return w.inbox.receive(w.link(), event)
+               return w.inbox.receive(ctx, w.link(), event)
        case *events.GroupInfo:
                if event.Name != nil {
                        w.groups.set(event.JID.String(), event.Name.Name)
@@ -977,7 +998,7 @@ func (w *WhatsApp) handle(event any) bool {
        w.mutex.Unlock()
 
        if line != "" {
-               w.logger.Log(context.Background(), level, line, attrs...)
+               w.logger.Log(ctx, level, line, attrs...)
        }
 
        if notice != "" {