slidge 0.1.0rc1__py3-none-any.whl → 0.1.2__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (164) hide show
  1. slidge/__init__.py +54 -31
  2. slidge/__main__.py +51 -5
  3. slidge/command/__init__.py +28 -0
  4. slidge/command/adhoc.py +258 -0
  5. slidge/command/admin.py +193 -0
  6. slidge/command/base.py +441 -0
  7. slidge/command/categories.py +3 -0
  8. slidge/command/chat_command.py +288 -0
  9. slidge/command/register.py +179 -0
  10. slidge/command/user.py +250 -0
  11. slidge/contact/__init__.py +8 -0
  12. slidge/contact/contact.py +452 -0
  13. slidge/contact/roster.py +192 -0
  14. slidge/core/__init__.py +2 -0
  15. slidge/core/cache.py +121 -39
  16. slidge/core/config.py +116 -11
  17. slidge/core/gateway/__init__.py +3 -0
  18. slidge/core/gateway/base.py +895 -0
  19. slidge/core/gateway/caps.py +63 -0
  20. slidge/core/gateway/delivery_receipt.py +52 -0
  21. slidge/core/gateway/disco.py +80 -0
  22. slidge/core/gateway/mam.py +75 -0
  23. slidge/core/gateway/muc_admin.py +35 -0
  24. slidge/core/gateway/ping.py +66 -0
  25. slidge/core/gateway/presence.py +95 -0
  26. slidge/core/gateway/registration.py +53 -0
  27. slidge/core/gateway/search.py +102 -0
  28. slidge/core/gateway/session_dispatcher.py +795 -0
  29. slidge/core/gateway/vcard_temp.py +130 -0
  30. slidge/core/mixins/__init__.py +9 -1
  31. slidge/core/mixins/attachment.py +506 -0
  32. slidge/core/mixins/avatar.py +167 -0
  33. slidge/core/mixins/base.py +6 -19
  34. slidge/core/mixins/disco.py +66 -15
  35. slidge/core/mixins/lock.py +31 -0
  36. slidge/core/mixins/message.py +254 -252
  37. slidge/core/mixins/message_maker.py +154 -0
  38. slidge/core/mixins/presence.py +128 -31
  39. slidge/core/mixins/recipient.py +43 -0
  40. slidge/core/pubsub.py +275 -116
  41. slidge/core/session.py +586 -518
  42. slidge/group/__init__.py +10 -0
  43. slidge/group/archive.py +125 -0
  44. slidge/group/bookmarks.py +163 -0
  45. slidge/group/participant.py +458 -0
  46. slidge/group/room.py +1103 -0
  47. slidge/migration.py +18 -0
  48. slidge/slixfix/__init__.py +68 -0
  49. slidge/{util/xep_0050 → slixfix/link_preview}/__init__.py +4 -5
  50. slidge/slixfix/link_preview/link_preview.py +17 -0
  51. slidge/slixfix/link_preview/stanza.py +99 -0
  52. slidge/slixfix/roster.py +60 -0
  53. slidge/{util → slixfix}/xep_0077/register.py +1 -2
  54. slidge/slixfix/xep_0077/stanza.py +104 -0
  55. slidge/{util → slixfix}/xep_0100/gateway.py +17 -12
  56. slidge/slixfix/xep_0153/__init__.py +10 -0
  57. slidge/slixfix/xep_0153/stanza.py +25 -0
  58. slidge/slixfix/xep_0153/vcard_avatar.py +23 -0
  59. slidge/slixfix/xep_0264/__init__.py +5 -0
  60. slidge/slixfix/xep_0264/stanza.py +36 -0
  61. slidge/slixfix/xep_0264/thumbnail.py +23 -0
  62. slidge/slixfix/xep_0292/__init__.py +5 -0
  63. slidge/slixfix/xep_0292/vcard4.py +100 -0
  64. slidge/slixfix/xep_0313/__init__.py +12 -0
  65. slidge/slixfix/xep_0313/mam.py +262 -0
  66. slidge/slixfix/xep_0313/stanza.py +359 -0
  67. slidge/slixfix/xep_0317/__init__.py +5 -0
  68. slidge/slixfix/xep_0317/hats.py +17 -0
  69. slidge/slixfix/xep_0317/stanza.py +28 -0
  70. slidge/{util → slixfix}/xep_0356_old/privilege.py +9 -7
  71. slidge/slixfix/xep_0424/__init__.py +9 -0
  72. slidge/slixfix/xep_0424/retraction.py +77 -0
  73. slidge/slixfix/xep_0424/stanza.py +28 -0
  74. slidge/slixfix/xep_0490/__init__.py +8 -0
  75. slidge/slixfix/xep_0490/mds.py +47 -0
  76. slidge/slixfix/xep_0490/stanza.py +17 -0
  77. slidge/util/__init__.py +4 -6
  78. slidge/util/archive_msg.py +61 -0
  79. slidge/util/conf.py +25 -4
  80. slidge/util/db.py +23 -69
  81. slidge/util/schema.sql +126 -0
  82. slidge/util/sql.py +508 -0
  83. slidge/util/test.py +136 -86
  84. slidge/util/types.py +155 -14
  85. slidge/util/util.py +225 -51
  86. slidge-0.1.2.dist-info/METADATA +111 -0
  87. slidge-0.1.2.dist-info/RECORD +96 -0
  88. {slidge-0.1.0rc1.dist-info → slidge-0.1.2.dist-info}/WHEEL +1 -1
  89. slidge/core/adhoc.py +0 -492
  90. slidge/core/chat_command.py +0 -197
  91. slidge/core/contact.py +0 -441
  92. slidge/core/disco.py +0 -59
  93. slidge/core/gateway.py +0 -899
  94. slidge/core/muc/__init__.py +0 -3
  95. slidge/core/muc/bookmarks.py +0 -74
  96. slidge/core/muc/participant.py +0 -152
  97. slidge/core/muc/room.py +0 -348
  98. slidge/plugins/discord/__init__.py +0 -121
  99. slidge/plugins/discord/client.py +0 -121
  100. slidge/plugins/discord/session.py +0 -172
  101. slidge/plugins/dummy.py +0 -334
  102. slidge/plugins/facebook.py +0 -591
  103. slidge/plugins/hackernews.py +0 -209
  104. slidge/plugins/mattermost/__init__.py +0 -1
  105. slidge/plugins/mattermost/api.py +0 -288
  106. slidge/plugins/mattermost/gateway.py +0 -417
  107. slidge/plugins/mattermost/websocket.py +0 -248
  108. slidge/plugins/signal/__init__.py +0 -4
  109. slidge/plugins/signal/config.py +0 -4
  110. slidge/plugins/signal/contact.py +0 -104
  111. slidge/plugins/signal/gateway.py +0 -379
  112. slidge/plugins/signal/group.py +0 -76
  113. slidge/plugins/signal/session.py +0 -515
  114. slidge/plugins/signal/txt.py +0 -13
  115. slidge/plugins/signal/util.py +0 -32
  116. slidge/plugins/skype.py +0 -310
  117. slidge/plugins/steam.py +0 -400
  118. slidge/plugins/telegram/__init__.py +0 -6
  119. slidge/plugins/telegram/client.py +0 -325
  120. slidge/plugins/telegram/config.py +0 -21
  121. slidge/plugins/telegram/contact.py +0 -154
  122. slidge/plugins/telegram/gateway.py +0 -182
  123. slidge/plugins/telegram/group.py +0 -184
  124. slidge/plugins/telegram/session.py +0 -275
  125. slidge/plugins/telegram/util.py +0 -153
  126. slidge/plugins/whatsapp/__init__.py +0 -6
  127. slidge/plugins/whatsapp/config.py +0 -17
  128. slidge/plugins/whatsapp/contact.py +0 -33
  129. slidge/plugins/whatsapp/event.go +0 -455
  130. slidge/plugins/whatsapp/gateway.go +0 -156
  131. slidge/plugins/whatsapp/gateway.py +0 -69
  132. slidge/plugins/whatsapp/go.mod +0 -17
  133. slidge/plugins/whatsapp/go.sum +0 -22
  134. slidge/plugins/whatsapp/session.go +0 -371
  135. slidge/plugins/whatsapp/session.py +0 -370
  136. slidge/util/xep_0030/__init__.py +0 -13
  137. slidge/util/xep_0030/disco.py +0 -811
  138. slidge/util/xep_0030/stanza/__init__.py +0 -7
  139. slidge/util/xep_0030/stanza/info.py +0 -270
  140. slidge/util/xep_0030/stanza/items.py +0 -147
  141. slidge/util/xep_0030/static.py +0 -467
  142. slidge/util/xep_0050/adhoc.py +0 -631
  143. slidge/util/xep_0050/stanza.py +0 -180
  144. slidge/util/xep_0077/stanza.py +0 -71
  145. slidge/util/xep_0292/__init__.py +0 -1
  146. slidge/util/xep_0292/stanza.py +0 -167
  147. slidge/util/xep_0292/vcard4.py +0 -74
  148. slidge/util/xep_0356/__init__.py +0 -7
  149. slidge/util/xep_0356/permissions.py +0 -35
  150. slidge/util/xep_0356/privilege.py +0 -160
  151. slidge/util/xep_0356/stanza.py +0 -44
  152. slidge/util/xep_0461/__init__.py +0 -6
  153. slidge/util/xep_0461/reply.py +0 -48
  154. slidge/util/xep_0461/stanza.py +0 -80
  155. slidge-0.1.0rc1.dist-info/METADATA +0 -171
  156. slidge-0.1.0rc1.dist-info/RECORD +0 -99
  157. /slidge/{plugins/__init__.py → py.typed} +0 -0
  158. /slidge/{util → slixfix}/xep_0077/__init__.py +0 -0
  159. /slidge/{util → slixfix}/xep_0100/__init__.py +0 -0
  160. /slidge/{util → slixfix}/xep_0100/stanza.py +0 -0
  161. /slidge/{util → slixfix}/xep_0356_old/__init__.py +0 -0
  162. /slidge/{util → slixfix}/xep_0356_old/stanza.py +0 -0
  163. {slidge-0.1.0rc1.dist-info → slidge-0.1.2.dist-info}/LICENSE +0 -0
  164. {slidge-0.1.0rc1.dist-info → slidge-0.1.2.dist-info}/entry_points.txt +0 -0
@@ -1,455 +0,0 @@
1
- package whatsapp
2
-
3
- import (
4
- // Standard library.
5
- "context"
6
- "fmt"
7
- "mime"
8
-
9
- // Third-party libraries.
10
- "go.mau.fi/whatsmeow"
11
- "go.mau.fi/whatsmeow/binary/proto"
12
- "go.mau.fi/whatsmeow/types"
13
- "go.mau.fi/whatsmeow/types/events"
14
- )
15
-
16
- // EventKind represents all event types recognized by the Python session adapter, as emitted by the
17
- // Go session adapter.
18
- type EventKind int
19
-
20
- // The event types handled by the overarching session adapter handler.
21
- const (
22
- EventUnknown EventKind = iota
23
- EventQRCode
24
- EventPairSuccess
25
- EventConnected
26
- EventLoggedOut
27
- EventContactSync
28
- EventPresence
29
- EventMessage
30
- EventChatState
31
- EventReceipt
32
- EventCall
33
- )
34
-
35
- // EventPayload represents the collected payloads for all event types handled by the overarching
36
- // session adapter handler. Only specific fields will be populated in events emitted by internal
37
- // handlers, see documentation for specific types for more information.
38
- type EventPayload struct {
39
- QRCode string
40
- PairDeviceID string
41
- Contact Contact
42
- Presence Presence
43
- Message Message
44
- ChatState ChatState
45
- Receipt Receipt
46
- Call Call
47
- }
48
-
49
- // A Contact represents any entity that be communicated with directly in WhatsApp. This typically
50
- // represents people, but may represent a business or bot as well, but not a group-chat.
51
- type Contact struct {
52
- JID string
53
- Name string
54
- AvatarURL string
55
- }
56
-
57
- // NewContactSyncEvent returns event data meant for [Session.propagateEvent] for the contact information
58
- // given. Unknown or invalid contact information will return an [EventUnknown] event with nil data.
59
- func newContactSyncEvent(c *whatsmeow.Client, jid types.JID, info types.ContactInfo) (EventKind, *EventPayload) {
60
- var contact = Contact{
61
- JID: jid.ToNonAD().String(),
62
- }
63
-
64
- for _, n := range []string{info.FullName, info.FirstName, info.BusinessName, info.PushName} {
65
- if n != "" {
66
- contact.Name = n
67
- break
68
- }
69
- }
70
-
71
- // Don't attempt to synchronize contacts with no user-readable name.
72
- if contact.Name == "" {
73
- return EventUnknown, nil
74
- }
75
-
76
- if p, _ := c.GetProfilePictureInfo(jid, nil); p != nil {
77
- contact.AvatarURL = p.URL
78
- }
79
-
80
- return EventContactSync, &EventPayload{Contact: contact}
81
- }
82
-
83
- // Precence represents a contact's general state of activity, and is periodically updated as
84
- // contacts start or stop paying attention to their client of choice.
85
- type Presence struct {
86
- JID string
87
- Away bool
88
- LastSeen int64
89
- }
90
-
91
- // NewPresenceEvent returns event data meant for [Session.propagateEvent] for the primitive presence
92
- // event given.
93
- func newPresenceEvent(evt *events.Presence) (EventKind, *EventPayload) {
94
- return EventPresence, &EventPayload{Presence: Presence{
95
- JID: evt.From.ToNonAD().String(),
96
- Away: evt.Unavailable,
97
- LastSeen: evt.LastSeen.Unix(),
98
- }}
99
- }
100
-
101
- // MessageKind represents all concrete message types (plain-text messages, edit messages, reactions)
102
- // recognized by the Python session adapter.
103
- type MessageKind int
104
-
105
- // The message types handled by the overarching session event handler.
106
- const (
107
- MessagePlain MessageKind = 1 + iota
108
- MessageRevoke
109
- MessageReaction
110
- MessageAttachment
111
- )
112
-
113
- // A Message represents one of many kinds of bidirectional communication payloads, for example, a
114
- // text message, a file (image, video) attachment, an emoji reaction, etc. Messages of different
115
- // kinds are denoted as such, and re-use fields where the semantics overlap.
116
- type Message struct {
117
- Kind MessageKind // The concrete message kind being sent or received.
118
- ID string // The unique message ID, used for referring to a specific Message instance.
119
- JID string // The JID this message concerns, semantics can change based on IsCarbon.
120
- Body string // The plain-text message body. For attachment messages, this can be a caption.
121
- Timestamp int64 // The Unix timestamp denoting when this message was created.
122
- IsCarbon bool // Whether or not this message concerns the gateway user themselves.
123
- ReplyID string // The unique message ID this message is in reply to, if any.
124
- ReplyBody string // The full body of the message this message is in reply to, if any.
125
- Attachments []Attachment // The list of file (image, video, etc.) attachments contained in this message.
126
- }
127
-
128
- // A Attachment represents additional binary data (e.g. images, videos, documents) provided alongside
129
- // a message, for display or storage on the recepient client.
130
- type Attachment struct {
131
- MIME string // The MIME type for attachment.
132
- Filename string // The recommended file name for this attachment. May be an auto-generated name.
133
- Caption string // The user-provided caption, provided alongside this attachment.
134
- Data []byte // The raw binary data for this attachment. Mutually exclusive with [.URL].
135
- URL string // The URL to download attachment data from. Mutually exclusive with [.Data].
136
- }
137
-
138
- // GenerateMessageID returns a valid, pseudo-random message ID for use in outgoing messages. This
139
- // function will panic if there is no entropy available for random ID generation.
140
- func GenerateMessageID() string {
141
- return whatsmeow.GenerateMessageID()
142
- }
143
-
144
- // NewMessageEvent returns event data meant for [Session.propagateEvent] for the primive message
145
- // event given. Unknown or invalid messages will return an [EventUnknown] event with nil data.
146
- func newMessageEvent(client *whatsmeow.Client, evt *events.Message) (EventKind, *EventPayload) {
147
- // Ignore incoming messages sent or received over group-chats until proper support is implemented.
148
- if evt.Info.IsGroup {
149
- return EventUnknown, nil
150
- }
151
-
152
- // Set basic data for message, to be potentially amended depending on the concrete version of
153
- // the underlying message.
154
- var message = Message{
155
- Kind: MessagePlain,
156
- ID: evt.Info.ID,
157
- Body: evt.Message.GetConversation(),
158
- Timestamp: evt.Info.Timestamp.Unix(),
159
- IsCarbon: evt.Info.IsFromMe,
160
- }
161
-
162
- // Set message JID based on whether a message is originating from ourselves or someone else.
163
- if message.IsCarbon {
164
- message.JID = evt.Info.MessageSource.Chat.ToNonAD().String()
165
- } else {
166
- message.JID = evt.Info.MessageSource.Sender.ToNonAD().String()
167
- }
168
-
169
- // Handle handle protocol messages (such as message deletion or editing).
170
- if p := evt.Message.GetProtocolMessage(); p != nil {
171
- switch p.GetType() {
172
- case proto.ProtocolMessage_REVOKE:
173
- message.Kind = MessageRevoke
174
- message.ID = p.Key.GetId()
175
- return EventMessage, &EventPayload{Message: message}
176
- }
177
- }
178
-
179
- // Handle emoji reaction to existing message.
180
- if r := evt.Message.GetReactionMessage(); r != nil {
181
- message.Kind = MessageReaction
182
- message.ID = r.Key.GetId()
183
- message.Body = r.GetText()
184
- return EventMessage, &EventPayload{Message: message}
185
- }
186
-
187
- // Handle message attachments, if any.
188
- if attach, err := getMessageAttachments(client, evt.Message); err != nil {
189
- client.Log.Errorf("Failed getting message attachments: %s", err)
190
- return EventUnknown, nil
191
- } else if len(attach) > 0 {
192
- message.Attachments = append(message.Attachments, attach...)
193
- message.Kind = MessageAttachment
194
- }
195
-
196
- // Get extended information from message, if available. Extended messages typically represent
197
- // messages with additional context, such as replies, forwards, etc.
198
- if e := evt.Message.GetExtendedTextMessage(); e != nil {
199
- if message.Body == "" {
200
- message.Body = e.GetText()
201
- }
202
- if c := e.GetContextInfo(); c != nil {
203
- message.ReplyID = c.GetStanzaId()
204
- if q := c.GetQuotedMessage(); q != nil {
205
- message.ReplyBody = q.GetConversation()
206
- }
207
- }
208
- }
209
-
210
- // Ignore obviously invalid messages.
211
- if message.Kind == MessagePlain && message.Body == "" {
212
- return EventUnknown, nil
213
- }
214
-
215
- return EventMessage, &EventPayload{Message: message}
216
- }
217
-
218
- // GetMessageAttachments fetches and decrypts attachments (images, audio, video, or documents) sent
219
- // via WhatsApp. Any failures in retrieving any attachment will return an error immediately.
220
- func getMessageAttachments(client *whatsmeow.Client, message *proto.Message) ([]Attachment, error) {
221
- var result []Attachment
222
- var kinds = []whatsmeow.DownloadableMessage{
223
- message.GetImageMessage(),
224
- message.GetAudioMessage(),
225
- message.GetVideoMessage(),
226
- message.GetDocumentMessage(),
227
- }
228
-
229
- for _, msg := range kinds {
230
- // Handle data for specific attachment type.
231
- var a Attachment
232
- switch msg := msg.(type) {
233
- case *proto.ImageMessage:
234
- a.MIME, a.Caption = msg.GetMimetype(), msg.GetCaption()
235
- case *proto.AudioMessage:
236
- a.MIME = msg.GetMimetype()
237
- case *proto.VideoMessage:
238
- a.MIME, a.Caption = msg.GetMimetype(), msg.GetCaption()
239
- case *proto.DocumentMessage:
240
- a.MIME, a.Caption, a.Filename = msg.GetMimetype(), msg.GetCaption(), msg.GetFileName()
241
- }
242
-
243
- // Ignore attachments with empty or unknown MIME types.
244
- if a.MIME == "" {
245
- continue
246
- }
247
-
248
- // Set filename from SHA256 checksum and MIME type, if none is already set.
249
- if a.Filename == "" {
250
- a.Filename = fmt.Sprintf("%x%s", msg.GetFileSha256(), extensionByType(a.MIME))
251
- }
252
-
253
- // Attempt to download and decrypt raw attachment data, if any.
254
- data, err := client.Download(msg)
255
- if err != nil {
256
- return nil, err
257
- }
258
-
259
- a.Data = data
260
- result = append(result, a)
261
- }
262
-
263
- return result, nil
264
- }
265
-
266
- // KnownMediaTypes represents MIME type to WhatsApp media types known to be handled by WhatsApp in a
267
- // special way (that is, not as generic file uploads).
268
- var knownMediaTypes = map[string]whatsmeow.MediaType{
269
- "image/jpeg": whatsmeow.MediaImage,
270
- "audio/ogg": whatsmeow.MediaAudio,
271
- "application/ogg": whatsmeow.MediaAudio,
272
- "video/mp4": whatsmeow.MediaVideo,
273
- }
274
-
275
- // UploadAttachment attempts to push the given attachment data to WhatsApp according to the MIME type
276
- // specified within. Attachments are handled as generic file uploads unless they're of a specific
277
- // format, see [knownMediaTypes] for more information.
278
- func uploadAttachment(client *whatsmeow.Client, attach Attachment) (*proto.Message, error) {
279
- mediaType := knownMediaTypes[attach.MIME]
280
- if mediaType == "" {
281
- mediaType = whatsmeow.MediaDocument
282
- }
283
-
284
- upload, err := client.Upload(context.Background(), attach.Data, mediaType)
285
- if err != nil {
286
- return nil, err
287
- }
288
-
289
- var message *proto.Message
290
- switch mediaType {
291
- case whatsmeow.MediaImage:
292
- message = &proto.Message{
293
- ImageMessage: &proto.ImageMessage{
294
- Url: &upload.URL,
295
- DirectPath: &upload.DirectPath,
296
- MediaKey: upload.MediaKey,
297
- Mimetype: &attach.MIME,
298
- FileEncSha256: upload.FileEncSHA256,
299
- FileSha256: upload.FileSHA256,
300
- FileLength: ptrTo(uint64(len(attach.Data))),
301
- },
302
- }
303
- case whatsmeow.MediaAudio:
304
- message = &proto.Message{
305
- AudioMessage: &proto.AudioMessage{
306
- Url: &upload.URL,
307
- DirectPath: &upload.DirectPath,
308
- MediaKey: upload.MediaKey,
309
- Mimetype: &attach.MIME,
310
- FileEncSha256: upload.FileEncSHA256,
311
- FileSha256: upload.FileSHA256,
312
- FileLength: ptrTo(uint64(len(attach.Data))),
313
- },
314
- }
315
- case whatsmeow.MediaVideo:
316
- message = &proto.Message{
317
- VideoMessage: &proto.VideoMessage{
318
- Url: &upload.URL,
319
- DirectPath: &upload.DirectPath,
320
- MediaKey: upload.MediaKey,
321
- Mimetype: &attach.MIME,
322
- FileEncSha256: upload.FileEncSHA256,
323
- FileSha256: upload.FileSHA256,
324
- FileLength: ptrTo(uint64(len(attach.Data))),
325
- }}
326
- case whatsmeow.MediaDocument:
327
- message = &proto.Message{
328
- DocumentMessage: &proto.DocumentMessage{
329
- Url: &upload.URL,
330
- DirectPath: &upload.DirectPath,
331
- MediaKey: upload.MediaKey,
332
- Mimetype: &attach.MIME,
333
- FileEncSha256: upload.FileEncSHA256,
334
- FileSha256: upload.FileSHA256,
335
- FileLength: ptrTo(uint64(len(attach.Data))),
336
- FileName: &attach.Filename,
337
- }}
338
- }
339
-
340
- return message, nil
341
- }
342
-
343
- // ExtensionByType returns the file extension for the given MIME type, or a generic extension if the
344
- // MIME type is unknown.
345
- func extensionByType(typ string) string {
346
- if ext, _ := mime.ExtensionsByType(typ); len(ext) > 0 {
347
- return ext[0]
348
- }
349
- return ".bin"
350
- }
351
-
352
- // ChatStateKind represents the different kinds of chat-states possible in WhatsApp.
353
- type ChatStateKind int
354
-
355
- // The chat states handled by the overarching session event handler.
356
- const (
357
- ChatStateComposing ChatStateKind = 1 + iota
358
- ChatStatePaused
359
- )
360
-
361
- // A ChatState represents the activity of a contact within a certain discussion, for instance,
362
- // whether the contact is currently composing a message. This is separate to the concept of a
363
- // Presence, which is the contact's general state across all discussions.
364
- type ChatState struct {
365
- JID string
366
- Kind ChatStateKind
367
- }
368
-
369
- // NewChatStateEvent returns event data meant for [Session.propagateEvent] for the primitive
370
- // chat-state event given.
371
- func newChatStateEvent(evt *events.ChatPresence) (EventKind, *EventPayload) {
372
- var state = ChatState{JID: evt.MessageSource.Sender.ToNonAD().String()}
373
- switch evt.State {
374
- case types.ChatPresenceComposing:
375
- state.Kind = ChatStateComposing
376
- case types.ChatPresencePaused:
377
- state.Kind = ChatStatePaused
378
- }
379
- return EventChatState, &EventPayload{ChatState: state}
380
- }
381
-
382
- // ReceiptKind represents the different types of delivery receipts possible in WhatsApp.
383
- type ReceiptKind int
384
-
385
- // The delivery receipts handled by the overarching session event handler.
386
- const (
387
- ReceiptDelivered ReceiptKind = 1 + iota
388
- ReceiptRead
389
- )
390
-
391
- // A Receipt represents a notice of delivery or presentation for [Message] instances sent or
392
- // received. Receipts can be delivered for many messages at once, but are generally all delivered
393
- // under one specific state at a time.
394
- type Receipt struct {
395
- Kind ReceiptKind
396
- MessageIDs []string
397
- JID string
398
- Timestamp int64
399
- IsCarbon bool
400
- }
401
-
402
- // NewReceiptEvent returns event data meant for [Session.propagateEvent] for the primive receipt
403
- // event given. Unknown or invalid receipts will return an [EventUnknown] event with nil data.
404
- func newReceiptEvent(evt *events.Receipt) (EventKind, *EventPayload) {
405
- var receipt = Receipt{
406
- MessageIDs: append([]string{}, evt.MessageIDs...),
407
- Timestamp: evt.Timestamp.Unix(),
408
- IsCarbon: evt.MessageSource.IsFromMe,
409
- }
410
-
411
- if len(receipt.MessageIDs) == 0 {
412
- return EventUnknown, nil
413
- }
414
-
415
- if receipt.IsCarbon {
416
- receipt.JID = evt.MessageSource.Chat.ToNonAD().String()
417
- } else {
418
- receipt.JID = evt.MessageSource.Sender.ToNonAD().String()
419
- }
420
-
421
- switch evt.Type {
422
- case events.ReceiptTypeDelivered:
423
- receipt.Kind = ReceiptDelivered
424
- case events.ReceiptTypeRead:
425
- receipt.Kind = ReceiptRead
426
- }
427
-
428
- return EventReceipt, &EventPayload{Receipt: receipt}
429
- }
430
-
431
- // CallState represents the state of the call to synchronize with.
432
- type CallState int
433
-
434
- // The calls tates handled by the overarching session event handler.
435
- const (
436
- CallMissed CallState = 1 + iota
437
- )
438
-
439
- // A Call represents an incoming or outgoing voice/video call made over WhatsApp. Full support for
440
- // calls is currently not implemented, and this structure contains the bare minimum data required
441
- // for notifying on missed calls.
442
- type Call struct {
443
- State CallState
444
- JID string
445
- Timestamp int64
446
- }
447
-
448
- // NewCallEvent returns event data meant for [Session.propagateEvent] for the call metadata given.
449
- func newCallEvent(state CallState, meta types.BasicCallMeta) (EventKind, *EventPayload) {
450
- return EventCall, &EventPayload{Call: Call{
451
- State: state,
452
- JID: meta.From.ToNonAD().String(),
453
- Timestamp: meta.Timestamp.Unix(),
454
- }}
455
- }
@@ -1,156 +0,0 @@
1
- package whatsapp
2
-
3
- import (
4
- // Standard library.
5
- "crypto/tls"
6
- "fmt"
7
- "net/http"
8
- "runtime"
9
- "time"
10
-
11
- // Third-party libraries.
12
- _ "github.com/mattn/go-sqlite3"
13
- "go.mau.fi/whatsmeow/store"
14
- "go.mau.fi/whatsmeow/store/sqlstore"
15
- "go.mau.fi/whatsmeow/types"
16
- walog "go.mau.fi/whatsmeow/util/log"
17
- )
18
-
19
- // A LinkedDevice represents a unique pairing session between the gateway and WhatsApp. It is not
20
- // unique to the underlying "main" device (or phone number), as multiple linked devices may be paired
21
- // with any main device.
22
- type LinkedDevice struct {
23
- // ID is an opaque string identifying this LinkedDevice to the Session. Noted that this string
24
- // is currently equivalent to a password, and needs to be protected accordingly.
25
- ID string
26
- }
27
-
28
- // JID returns the WhatsApp JID corresponding to the LinkedDevice ID. Empty or invalid device IDs
29
- // may return invalid JIDs, and this function does not handle errors.
30
- func (d LinkedDevice) JID() types.JID {
31
- jid, _ := types.ParseJID(d.ID)
32
- return jid
33
- }
34
-
35
- // A ErrorLevel is a value representing the severity of a log message being handled.
36
- type ErrorLevel int
37
-
38
- // The log levels handled by the overarching Session logger.
39
- const (
40
- LevelError ErrorLevel = 1 + iota
41
- LevelWarning
42
- LevelInfo
43
- LevelDebug
44
- )
45
-
46
- // HandleLogFunc is the signature for the overarching Gateway log handling function.
47
- type HandleLogFunc func(ErrorLevel, string)
48
-
49
- // Errorf handles the given message as representing a (typically) fatal error.
50
- func (h HandleLogFunc) Errorf(msg string, args ...interface{}) {
51
- h(LevelError, fmt.Sprintf(msg, args...))
52
- }
53
-
54
- // Warn handles the given message as representing a non-fatal error or warning thereof.
55
- func (h HandleLogFunc) Warnf(msg string, args ...interface{}) {
56
- h(LevelWarning, fmt.Sprintf(msg, args...))
57
- }
58
-
59
- // Infof handles the given message as representing an informational notice.
60
- func (h HandleLogFunc) Infof(msg string, args ...interface{}) {
61
- h(LevelInfo, fmt.Sprintf(msg, args...))
62
- }
63
-
64
- // Debugf handles the given message as representing an internal-only debug message.
65
- func (h HandleLogFunc) Debugf(msg string, args ...interface{}) {
66
- h(LevelDebug, fmt.Sprintf(msg, args...))
67
- }
68
-
69
- // Sub is a no-op and will return the receiver itself.
70
- func (h HandleLogFunc) Sub(string) walog.Logger {
71
- return h
72
- }
73
-
74
- // A Gateway represents a persistent process for establishing individual sessions between linked
75
- // devices and WhatsApp.
76
- type Gateway struct {
77
- DBPath string // The filesystem path for the client database.
78
- Name string // The name to display when linking devices on WhatsApp.
79
- SkipVerifyTLS bool // Whether or not our internal HTTP client will skip TLS certificate verification.
80
-
81
- // Internal variables.
82
- container *sqlstore.Container
83
- httpClient *http.Client
84
- logger walog.Logger
85
- }
86
-
87
- // NewSession returns a new for the LinkedDevice given. If the linked device does not have a valid
88
- // ID, a pair operation will be required, as described in [Session.Login].
89
- func (w *Gateway) NewSession(device LinkedDevice) *Session {
90
- return &Session{device: device, gateway: w}
91
- }
92
-
93
- // CleanupSession will remove all invalid and obsolete references to the given device, and should be
94
- // used when pairing a new device or unregistering from the Gateway.
95
- func (w *Gateway) CleanupSession(device LinkedDevice) error {
96
- devices, err := w.container.GetAllDevices()
97
- if err != nil {
98
- return err
99
- }
100
-
101
- for _, d := range devices {
102
- if d.ID == nil {
103
- w.logger.Infof("Removing invalid device %s from database", d.ID.String())
104
- _ = d.Delete()
105
- } else if device.ID != "" {
106
- if jid := device.JID(); d.ID.ToNonAD() == jid.ToNonAD() && *d.ID != jid {
107
- w.logger.Infof("Removing obsolete device %s from database", d.ID.String())
108
- _ = d.Delete()
109
- }
110
- }
111
- }
112
-
113
- return nil
114
- }
115
-
116
- // Init performs initialization procedures for the Gateway, and is expected to be run before any
117
- // calls to [Gateway.Session].
118
- func (w *Gateway) Init() error {
119
- container, err := sqlstore.New("sqlite3", w.DBPath, w.logger)
120
- if err != nil {
121
- return err
122
- }
123
-
124
- if w.Name != "" {
125
- store.SetOSInfo(w.Name, [...]uint32{1, 0, 0})
126
- }
127
-
128
- // Set up shared HTTP client with less lenient timeouts.
129
- w.httpClient = &http.Client{
130
- Timeout: time.Second * 10,
131
- Transport: &http.Transport{
132
- TLSClientConfig: &tls.Config{InsecureSkipVerify: w.SkipVerifyTLS},
133
- },
134
- }
135
-
136
- w.container = container
137
- return nil
138
- }
139
-
140
- // SetLogHandler specifies the log handling function to use for all [Gateway] and [Session] operations.
141
- func (w *Gateway) SetLogHandler(h HandleLogFunc) {
142
- w.logger = HandleLogFunc(func(level ErrorLevel, message string) {
143
- // Don't allow other Goroutines from using this thread, as this might lead to concurrent
144
- // use of the GIL, which can lead to crashes.
145
- runtime.LockOSThread()
146
- defer runtime.UnlockOSThread()
147
-
148
- h(level, message)
149
- })
150
- }
151
-
152
- // NewGateway returns a new, un-initialized Gateway. This function should always be followed by calls
153
- // to [Gateway.Init], assuming a valid [Gateway.DBPath] is set.
154
- func NewGateway() *Gateway {
155
- return &Gateway{}
156
- }
@@ -1,69 +0,0 @@
1
- from logging import getLogger
2
- from pathlib import Path
3
- from shelve import open
4
-
5
- from slidge import BaseGateway, GatewayUser, global_config
6
- from slidge.plugins.whatsapp.generated import whatsapp
7
-
8
- from . import config
9
-
10
- REGISTRATION_INSTRUCTIONS = (
11
- "Continue and scan the resulting QR codes on your main device to complete registration. "
12
- "More information at https://slidge.readthedocs.io/en/latest/user/plugins/whatsapp.html"
13
- )
14
-
15
- WELCOME_MESSAGE = (
16
- "Thank you for registering! Please scan the following QR code on your main device to complete "
17
- "registration, or type 'help' to list other available commands."
18
- )
19
-
20
-
21
- class Gateway(BaseGateway):
22
- COMPONENT_NAME = "WhatsApp (slidge)"
23
- COMPONENT_TYPE = "whatsapp"
24
- COMPONENT_AVATAR = "https://www.whatsapp.com/apple-touch-icon.png"
25
- REGISTRATION_INSTRUCTIONS = REGISTRATION_INSTRUCTIONS
26
- WELCOME_MESSAGE = WELCOME_MESSAGE
27
- REGISTRATION_FIELDS = []
28
- ROSTER_GROUP = "WhatsApp"
29
- MARK_ALL_MESSAGES = True
30
-
31
- def __init__(self):
32
- super().__init__()
33
- Path(config.DB_PATH.parent).mkdir(exist_ok=True)
34
- self.whatsapp = whatsapp.NewGateway()
35
- self.whatsapp.SetLogHandler(handle_log)
36
- self.whatsapp.DBPath = str(config.DB_PATH)
37
- self.whatsapp.SkipVerifyTLS = config.SKIP_VERIFY_TLS
38
- self.whatsapp.Name = "Slidge on " + str(global_config.JID)
39
- self.whatsapp.Init()
40
-
41
- async def unregister(self, user: GatewayUser):
42
- user_shelf_path = (
43
- global_config.HOME_DIR / "whatsapp" / (user.bare_jid + ".shelf")
44
- )
45
- with open(str(user_shelf_path)) as shelf:
46
- try:
47
- device = whatsapp.LinkedDevice(ID=shelf["device_id"])
48
- self.whatsapp.CleanupSession(device)
49
- except KeyError:
50
- pass
51
- except RuntimeError as err:
52
- log.error("Failed to clean up WhatsApp session: %s", err)
53
-
54
-
55
- def handle_log(level, msg: str):
56
- """
57
- Log given message of specified level in system-wide logger.
58
- """
59
- if level == whatsapp.LevelError:
60
- log.error(msg)
61
- elif level == whatsapp.LevelWarning:
62
- log.warning(msg)
63
- elif level == whatsapp.LevelDebug:
64
- log.debug(msg)
65
- else:
66
- log.info(msg)
67
-
68
-
69
- log = getLogger(__name__)
@@ -1,17 +0,0 @@
1
- module git.sr.ht/~nicoco/slidge/slidge/plugins/whatsapp
2
-
3
- go 1.19
4
-
5
- require (
6
- github.com/go-python/gopy v0.4.5
7
- github.com/mattn/go-sqlite3 v1.14.16
8
- go.mau.fi/libsignal v0.0.0-20221015105917-d970e7c3c9cf
9
- go.mau.fi/whatsmeow v0.0.0-20221213225758-70ef67df3c68
10
- )
11
-
12
- require (
13
- filippo.io/edwards25519 v1.0.0 // indirect
14
- github.com/gorilla/websocket v1.5.0 // indirect
15
- golang.org/x/crypto v0.0.0-20221012134737-56aed061732a // indirect
16
- google.golang.org/protobuf v1.28.1 // indirect
17
- )