slidge-whatsapp 0.2.1__cp311-cp311-manylinux_2_36_x86_64.whl → 0.2.2__cp311-cp311-manylinux_2_36_x86_64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- slidge_whatsapp/__init__.py +1 -1
- slidge_whatsapp/event.go +117 -69
- slidge_whatsapp/gateway.go +1 -1
- slidge_whatsapp/generated/_whatsapp.cpython-311-x86_64-linux-gnu.h +161 -169
- slidge_whatsapp/generated/_whatsapp.cpython-311-x86_64-linux-gnu.so +0 -0
- slidge_whatsapp/generated/build.py +129 -134
- slidge_whatsapp/generated/go.py +12 -78
- slidge_whatsapp/generated/whatsapp.c +1330 -1423
- slidge_whatsapp/generated/whatsapp.go +1126 -1199
- slidge_whatsapp/generated/whatsapp.py +1171 -1241
- slidge_whatsapp/generated/whatsapp_go.h +161 -169
- slidge_whatsapp/go.mod +29 -0
- slidge_whatsapp/go.sum +62 -0
- slidge_whatsapp/group.py +11 -6
- slidge_whatsapp/media/media.go +150 -56
- slidge_whatsapp/media/mupdf.go +47 -0
- slidge_whatsapp/media/stub.go +19 -0
- slidge_whatsapp/session.go +49 -15
- slidge_whatsapp/session.py +15 -7
- slidge_whatsapp-0.2.2.dist-info/LICENSE +661 -0
- slidge_whatsapp-0.2.2.dist-info/METADATA +744 -0
- slidge_whatsapp-0.2.2.dist-info/RECORD +31 -0
- {slidge_whatsapp-0.2.1.dist-info → slidge_whatsapp-0.2.2.dist-info}/WHEEL +1 -1
- slidge_whatsapp-0.2.1.dist-info/METADATA +0 -86
- slidge_whatsapp-0.2.1.dist-info/RECORD +0 -26
- {slidge_whatsapp-0.2.1.dist-info → slidge_whatsapp-0.2.2.dist-info}/entry_points.txt +0 -0
slidge_whatsapp/__init__.py
CHANGED
slidge_whatsapp/event.go
CHANGED
|
@@ -2,18 +2,15 @@ package whatsapp
|
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
4
|
// Standard library.
|
|
5
|
-
"bytes"
|
|
6
5
|
"context"
|
|
7
6
|
"fmt"
|
|
8
|
-
"image/gif"
|
|
9
7
|
"mime"
|
|
10
8
|
"strings"
|
|
11
9
|
|
|
12
10
|
// Internal packages.
|
|
13
|
-
"
|
|
11
|
+
"codeberg.org/slidge/slidge-whatsapp/slidge_whatsapp/media"
|
|
14
12
|
|
|
15
13
|
// Third-party libraries.
|
|
16
|
-
"github.com/h2non/filetype"
|
|
17
14
|
"go.mau.fi/whatsmeow"
|
|
18
15
|
"go.mau.fi/whatsmeow/proto/waE2E"
|
|
19
16
|
"go.mau.fi/whatsmeow/proto/waWeb"
|
|
@@ -185,6 +182,21 @@ type Attachment struct {
|
|
|
185
182
|
spec *media.Spec // Metadata specific to audio/video files, used in processing.
|
|
186
183
|
}
|
|
187
184
|
|
|
185
|
+
// GetSpec returns metadata for this attachment, as derived from the underlying attachment data.
|
|
186
|
+
func (a *Attachment) GetSpec(ctx context.Context) (*media.Spec, error) {
|
|
187
|
+
if a.spec != nil {
|
|
188
|
+
return a.spec, nil
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
spec, err := media.GetSpec(ctx, a.Data)
|
|
192
|
+
if err != nil {
|
|
193
|
+
return nil, err
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
a.spec = spec
|
|
197
|
+
return a.spec, nil
|
|
198
|
+
}
|
|
199
|
+
|
|
188
200
|
// PreviewKind represents different ways of previewingadditional data inline with messages.
|
|
189
201
|
type PreviewKind int
|
|
190
202
|
|
|
@@ -230,10 +242,9 @@ func newMessageEvent(client *whatsmeow.Client, evt *events.Message) (EventKind,
|
|
|
230
242
|
IsCarbon: evt.Info.IsFromMe,
|
|
231
243
|
}
|
|
232
244
|
|
|
233
|
-
// Handle Broadcasts and Status Updates; currently, only non-carbon, non-status broadcast
|
|
234
|
-
// messages are handled as plain messages, as support for analogues is lacking in the XMPP
|
|
235
|
-
// world.
|
|
236
245
|
if evt.Info.Chat.Server == types.BroadcastServer {
|
|
246
|
+
// Handle non-carbon, non-status broadcast messages as plain messages; support for other
|
|
247
|
+
// types is lacking in the XMPP world.
|
|
237
248
|
if evt.Info.Chat.User == types.StatusBroadcastJID.User || message.IsCarbon {
|
|
238
249
|
return EventUnknown, nil
|
|
239
250
|
}
|
|
@@ -427,7 +438,7 @@ func getMessageAttachments(client *whatsmeow.Client, message *waE2E.Message) ([]
|
|
|
427
438
|
const (
|
|
428
439
|
// The MIME type used by voice messages on WhatsApp.
|
|
429
440
|
voiceMessageMIME = string(media.TypeOgg) + "; codecs=opus"
|
|
430
|
-
//
|
|
441
|
+
// The MIME type used by animated images on WhatsApp.
|
|
431
442
|
animatedImageMIME = "image/gif"
|
|
432
443
|
|
|
433
444
|
// The maximum image attachment size we'll attempt to process in any way, in bytes.
|
|
@@ -437,10 +448,6 @@ const (
|
|
|
437
448
|
|
|
438
449
|
// The maximum number of samples to return in media waveforms.
|
|
439
450
|
maxWaveformSamples = 64
|
|
440
|
-
|
|
441
|
-
// Default thumbnail width in pixels.
|
|
442
|
-
defaultThumbnailWidth = 100
|
|
443
|
-
previewThumbnailWidth = 250
|
|
444
451
|
)
|
|
445
452
|
|
|
446
453
|
var (
|
|
@@ -476,6 +483,18 @@ var (
|
|
|
476
483
|
MIME: media.TypeJPEG,
|
|
477
484
|
ImageQuality: 85,
|
|
478
485
|
}
|
|
486
|
+
|
|
487
|
+
// Default target specifications for default and preview-size thumbnails.
|
|
488
|
+
defaultThumbnailSpec = media.Spec{
|
|
489
|
+
MIME: media.TypeJPEG,
|
|
490
|
+
ImageWidth: 100,
|
|
491
|
+
StripMetadata: true,
|
|
492
|
+
}
|
|
493
|
+
previewThumbnailSpec = media.Spec{
|
|
494
|
+
MIME: media.TypeJPEG,
|
|
495
|
+
ImageWidth: 250,
|
|
496
|
+
StripMetadata: true,
|
|
497
|
+
}
|
|
479
498
|
)
|
|
480
499
|
|
|
481
500
|
// ConvertAttachment attempts to process a given attachment from a less-supported type to a
|
|
@@ -488,11 +507,11 @@ var (
|
|
|
488
507
|
// If the input MIME type is unknown, or conversion is impossible, the given attachment is not
|
|
489
508
|
// changed.
|
|
490
509
|
func convertAttachment(attach *Attachment) error {
|
|
491
|
-
var detectedMIME
|
|
492
|
-
if t
|
|
493
|
-
detectedMIME = t
|
|
510
|
+
var detectedMIME media.MIMEType
|
|
511
|
+
if t := media.DetectMIMEType(attach.Data); t != media.TypeUnknown {
|
|
512
|
+
detectedMIME = t
|
|
494
513
|
if attach.MIME == "" || attach.MIME == "application/octet-stream" {
|
|
495
|
-
attach.MIME = detectedMIME
|
|
514
|
+
attach.MIME = string(detectedMIME)
|
|
496
515
|
}
|
|
497
516
|
}
|
|
498
517
|
|
|
@@ -500,41 +519,32 @@ func convertAttachment(attach *Attachment) error {
|
|
|
500
519
|
var ctx = context.Background()
|
|
501
520
|
|
|
502
521
|
switch detectedMIME {
|
|
503
|
-
case
|
|
522
|
+
case media.TypePNG, media.TypeWebP:
|
|
504
523
|
// Convert common image formats to JPEG for inline preview.
|
|
505
524
|
if len(attach.Data) > maxConvertImageSize {
|
|
506
525
|
return fmt.Errorf("attachment size %d exceeds maximum of %d", len(attach.Data), maxConvertImageSize)
|
|
507
526
|
}
|
|
508
527
|
|
|
509
528
|
spec = imageMessageSpec
|
|
510
|
-
case
|
|
511
|
-
// Convert
|
|
529
|
+
case media.TypeGIF:
|
|
530
|
+
// Convert GIFs to JPEG or MP4, if animated, as required by WhatsApp.
|
|
512
531
|
if len(attach.Data) > maxConvertImageSize {
|
|
513
532
|
return fmt.Errorf("attachment size %d exceeds maximum of %d", len(attach.Data), maxConvertImageSize)
|
|
514
533
|
}
|
|
515
534
|
|
|
516
|
-
|
|
517
|
-
if err
|
|
518
|
-
return fmt.Errorf("unable to decode GIF attachment")
|
|
519
|
-
} else if len(img.Image) == 1 {
|
|
520
|
-
spec = imageMessageSpec
|
|
521
|
-
} else {
|
|
535
|
+
spec = imageMessageSpec
|
|
536
|
+
if s, err := attach.GetSpec(ctx); err == nil && s.ImageFrameRate > 0 {
|
|
522
537
|
spec = videoMessageSpec
|
|
523
|
-
|
|
524
|
-
for d := range img.Delay {
|
|
525
|
-
t += float64(d) / 100
|
|
526
|
-
}
|
|
527
|
-
spec.ImageFrameRate = int(float64(len(img.Image)) / t)
|
|
538
|
+
spec.ImageFrameRate = s.ImageFrameRate
|
|
528
539
|
}
|
|
529
|
-
case
|
|
540
|
+
case media.TypeM4A:
|
|
530
541
|
if len(attach.Data) > maxConvertAudioVideoSize {
|
|
531
542
|
return fmt.Errorf("attachment size %d exceeds maximum of %d", len(attach.Data), maxConvertAudioVideoSize)
|
|
532
543
|
}
|
|
533
544
|
|
|
534
545
|
spec = voiceMessageSpec
|
|
535
546
|
|
|
536
|
-
if s, err :=
|
|
537
|
-
attach.spec = s
|
|
547
|
+
if s, err := attach.GetSpec(ctx); err == nil {
|
|
538
548
|
if s.AudioCodec == "alac" {
|
|
539
549
|
// Don't attempt to process lossless files at all, as it's assumed that the sender
|
|
540
550
|
// wants to retain these characteristics. Since WhatsApp will try (and likely fail)
|
|
@@ -543,29 +553,27 @@ func convertAttachment(attach *Attachment) error {
|
|
|
543
553
|
return nil
|
|
544
554
|
}
|
|
545
555
|
}
|
|
546
|
-
case
|
|
556
|
+
case media.TypeOgg:
|
|
547
557
|
if len(attach.Data) > maxConvertAudioVideoSize {
|
|
548
558
|
return fmt.Errorf("attachment size %d exceeds maximum of %d", len(attach.Data), maxConvertAudioVideoSize)
|
|
549
559
|
}
|
|
550
560
|
|
|
551
561
|
spec = audioMessageSpec
|
|
552
|
-
if s, err :=
|
|
553
|
-
attach.spec = s
|
|
562
|
+
if s, err := attach.GetSpec(ctx); err == nil {
|
|
554
563
|
if s.AudioCodec == "opus" {
|
|
555
564
|
// Assume that Opus-encoded Ogg files are meant to be voice messages, and re-encode
|
|
556
565
|
// them as such for WhatsApp.
|
|
557
566
|
spec = voiceMessageSpec
|
|
558
567
|
}
|
|
559
568
|
}
|
|
560
|
-
case
|
|
569
|
+
case media.TypeMP4, media.TypeWebM:
|
|
561
570
|
if len(attach.Data) > maxConvertAudioVideoSize {
|
|
562
571
|
return fmt.Errorf("attachment size %d exceeds maximum of %d", len(attach.Data), maxConvertAudioVideoSize)
|
|
563
572
|
}
|
|
564
573
|
|
|
565
574
|
spec = videoMessageSpec
|
|
566
575
|
|
|
567
|
-
if s, err :=
|
|
568
|
-
attach.spec = s
|
|
576
|
+
if s, err := attach.GetSpec(ctx); err == nil {
|
|
569
577
|
// Try to see if there's a video stream for ostensibly video-related MIME types, as
|
|
570
578
|
// these are some times misdetected as such.
|
|
571
579
|
if s.VideoWidth == 0 && s.VideoHeight == 0 && s.AudioSampleRate > 0 && s.Duration > 0 {
|
|
@@ -638,18 +646,17 @@ func uploadAttachment(client *whatsmeow.Client, attach *Attachment) (*waE2E.Mess
|
|
|
638
646
|
FileLength: ptrTo(uint64(len(attach.Data))),
|
|
639
647
|
},
|
|
640
648
|
}
|
|
641
|
-
t, err := media.Convert(ctx, attach.Data, &
|
|
649
|
+
t, err := media.Convert(ctx, attach.Data, &defaultThumbnailSpec)
|
|
642
650
|
if err != nil {
|
|
643
651
|
client.Log.Warnf("failed generating attachment thumbnail: %s", err)
|
|
644
652
|
} else {
|
|
645
653
|
message.ImageMessage.JPEGThumbnail = t
|
|
646
654
|
}
|
|
647
655
|
case whatsmeow.MediaAudio:
|
|
648
|
-
spec := attach.
|
|
649
|
-
if
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
}
|
|
656
|
+
spec, err := attach.GetSpec(ctx)
|
|
657
|
+
if err != nil {
|
|
658
|
+
client.Log.Warnf("failed fetching attachment metadata: %s", err)
|
|
659
|
+
spec = &media.Spec{}
|
|
653
660
|
}
|
|
654
661
|
message = &waE2E.Message{
|
|
655
662
|
AudioMessage: &waE2E.AudioMessage{
|
|
@@ -675,11 +682,10 @@ func uploadAttachment(client *whatsmeow.Client, attach *Attachment) (*waE2E.Mess
|
|
|
675
682
|
}
|
|
676
683
|
}
|
|
677
684
|
case whatsmeow.MediaVideo:
|
|
678
|
-
spec := attach.
|
|
679
|
-
if
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
}
|
|
685
|
+
spec, err := attach.GetSpec(ctx)
|
|
686
|
+
if err != nil {
|
|
687
|
+
client.Log.Warnf("failed fetching attachment metadata: %s", err)
|
|
688
|
+
spec = &media.Spec{}
|
|
683
689
|
}
|
|
684
690
|
message = &waE2E.Message{
|
|
685
691
|
VideoMessage: &waE2E.VideoMessage{
|
|
@@ -695,7 +701,7 @@ func uploadAttachment(client *whatsmeow.Client, attach *Attachment) (*waE2E.Mess
|
|
|
695
701
|
Height: ptrTo(uint32(spec.VideoHeight)),
|
|
696
702
|
},
|
|
697
703
|
}
|
|
698
|
-
t, err := media.
|
|
704
|
+
t, err := media.Convert(ctx, attach.Data, &defaultThumbnailSpec)
|
|
699
705
|
if err != nil {
|
|
700
706
|
client.Log.Warnf("failed generating attachment thumbnail: %s", err)
|
|
701
707
|
} else {
|
|
@@ -715,7 +721,22 @@ func uploadAttachment(client *whatsmeow.Client, attach *Attachment) (*waE2E.Mess
|
|
|
715
721
|
FileSHA256: upload.FileSHA256,
|
|
716
722
|
FileLength: ptrTo(uint64(len(attach.Data))),
|
|
717
723
|
FileName: &attach.Filename,
|
|
718
|
-
}
|
|
724
|
+
},
|
|
725
|
+
}
|
|
726
|
+
switch media.MIMEType(attach.MIME) {
|
|
727
|
+
case media.TypePDF:
|
|
728
|
+
if spec, err := attach.GetSpec(ctx); err != nil {
|
|
729
|
+
client.Log.Warnf("failed fetching attachment metadata: %s", err)
|
|
730
|
+
} else {
|
|
731
|
+
message.DocumentMessage.PageCount = ptrTo(uint32(spec.DocumentPage))
|
|
732
|
+
}
|
|
733
|
+
t, err := media.Convert(ctx, attach.Data, &previewThumbnailSpec)
|
|
734
|
+
if err != nil {
|
|
735
|
+
client.Log.Warnf("failed generating attachment thumbnail: %s", err)
|
|
736
|
+
} else {
|
|
737
|
+
message.DocumentMessage.JPEGThumbnail = t
|
|
738
|
+
}
|
|
739
|
+
}
|
|
719
740
|
}
|
|
720
741
|
|
|
721
742
|
return message, nil
|
|
@@ -992,11 +1013,27 @@ type GroupSubject struct {
|
|
|
992
1013
|
type GroupParticipantAction int
|
|
993
1014
|
|
|
994
1015
|
const (
|
|
995
|
-
GroupParticipantActionAdd
|
|
996
|
-
|
|
997
|
-
|
|
1016
|
+
GroupParticipantActionAdd GroupParticipantAction = iota // Default action; add participant to list.
|
|
1017
|
+
GroupParticipantActionRemove // Remove participant from list, if existing.
|
|
1018
|
+
GroupParticipantActionPromote // Make group member into administrator.
|
|
1019
|
+
GroupParticipantActionDemote // Make group administrator into member.
|
|
998
1020
|
)
|
|
999
1021
|
|
|
1022
|
+
// ToParticipantChange converts our public [GroupParticipantAction] to the internal [ParticipantChange]
|
|
1023
|
+
// representation.
|
|
1024
|
+
func (a GroupParticipantAction) toParticipantChange() whatsmeow.ParticipantChange {
|
|
1025
|
+
switch a {
|
|
1026
|
+
case GroupParticipantActionRemove:
|
|
1027
|
+
return whatsmeow.ParticipantChangeRemove
|
|
1028
|
+
case GroupParticipantActionPromote:
|
|
1029
|
+
return whatsmeow.ParticipantChangePromote
|
|
1030
|
+
case GroupParticipantActionDemote:
|
|
1031
|
+
return whatsmeow.ParticipantChangeDemote
|
|
1032
|
+
default:
|
|
1033
|
+
return whatsmeow.ParticipantChangeAdd
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1000
1037
|
// A GroupParticipant represents a contact who is currently joined in a given group. Participants in
|
|
1001
1038
|
// WhatsApp can always be derived back to their individual [Contact]; there are no anonymous groups
|
|
1002
1039
|
// in WhatsApp.
|
|
@@ -1006,6 +1043,25 @@ type GroupParticipant struct {
|
|
|
1006
1043
|
Action GroupParticipantAction // The specific action to take for this participant; typically to add.
|
|
1007
1044
|
}
|
|
1008
1045
|
|
|
1046
|
+
// NewGroupParticipant returns a [GroupParticipant], filling fields from the internal participant
|
|
1047
|
+
// type. This is a no-op if [types.GroupParticipant.Error] is non-zero, and other fields may only
|
|
1048
|
+
// be set optionally.
|
|
1049
|
+
func newGroupParticipant(p types.GroupParticipant) GroupParticipant {
|
|
1050
|
+
if p.Error > 0 {
|
|
1051
|
+
return GroupParticipant{}
|
|
1052
|
+
}
|
|
1053
|
+
var affiliation = GroupAffiliationNone
|
|
1054
|
+
if p.IsSuperAdmin {
|
|
1055
|
+
affiliation = GroupAffiliationOwner
|
|
1056
|
+
} else if p.IsAdmin {
|
|
1057
|
+
affiliation = GroupAffiliationAdmin
|
|
1058
|
+
}
|
|
1059
|
+
return GroupParticipant{
|
|
1060
|
+
JID: p.JID.ToNonAD().String(),
|
|
1061
|
+
Affiliation: affiliation,
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1009
1065
|
// NewGroupEvent returns event data meant for [Session.propagateEvent] for the primive group event
|
|
1010
1066
|
// given. Group data returned by this function can be partial, and callers should take care to only
|
|
1011
1067
|
// handle non-empty values.
|
|
@@ -1036,14 +1092,14 @@ func newGroupEvent(evt *events.GroupInfo) (EventKind, *EventPayload) {
|
|
|
1036
1092
|
for _, p := range evt.Promote {
|
|
1037
1093
|
group.Participants = append(group.Participants, GroupParticipant{
|
|
1038
1094
|
JID: p.ToNonAD().String(),
|
|
1039
|
-
Action:
|
|
1095
|
+
Action: GroupParticipantActionPromote,
|
|
1040
1096
|
Affiliation: GroupAffiliationAdmin,
|
|
1041
1097
|
})
|
|
1042
1098
|
}
|
|
1043
1099
|
for _, p := range evt.Demote {
|
|
1044
1100
|
group.Participants = append(group.Participants, GroupParticipant{
|
|
1045
1101
|
JID: p.ToNonAD().String(),
|
|
1046
|
-
Action:
|
|
1102
|
+
Action: GroupParticipantActionDemote,
|
|
1047
1103
|
Affiliation: GroupAffiliationNone,
|
|
1048
1104
|
})
|
|
1049
1105
|
}
|
|
@@ -1055,20 +1111,12 @@ func newGroupEvent(evt *events.GroupInfo) (EventKind, *EventPayload) {
|
|
|
1055
1111
|
// be called when partial data is to be returned.
|
|
1056
1112
|
func newGroup(client *whatsmeow.Client, info *types.GroupInfo) Group {
|
|
1057
1113
|
var participants []GroupParticipant
|
|
1058
|
-
for
|
|
1059
|
-
|
|
1114
|
+
for i := range info.Participants {
|
|
1115
|
+
p := newGroupParticipant(info.Participants[i])
|
|
1116
|
+
if p.JID == "" {
|
|
1060
1117
|
continue
|
|
1061
1118
|
}
|
|
1062
|
-
|
|
1063
|
-
if p.IsSuperAdmin {
|
|
1064
|
-
affiliation = GroupAffiliationOwner
|
|
1065
|
-
} else if p.IsAdmin {
|
|
1066
|
-
affiliation = GroupAffiliationAdmin
|
|
1067
|
-
}
|
|
1068
|
-
participants = append(participants, GroupParticipant{
|
|
1069
|
-
JID: p.JID.ToNonAD().String(),
|
|
1070
|
-
Affiliation: affiliation,
|
|
1071
|
-
})
|
|
1119
|
+
participants = append(participants, p)
|
|
1072
1120
|
}
|
|
1073
1121
|
return Group{
|
|
1074
1122
|
JID: info.JID.ToNonAD().String(),
|
slidge_whatsapp/gateway.go
CHANGED