Use tulir/go-whatsapp fork

This commit is contained in:
Wim 2021-01-23 18:08:37 +01:00
parent 5dd15ef8e7
commit 3719dd93ab
22 changed files with 11037 additions and 4601 deletions

3
go.mod
View File

@ -40,6 +40,7 @@ require (
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca
github.com/shazow/ssh-chat v1.10.1
github.com/sirupsen/logrus v1.7.0
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
github.com/slack-go/slack v0.7.4
github.com/spf13/afero v1.3.4 // indirect
github.com/spf13/cast v1.3.1 // indirect
@ -58,4 +59,6 @@ require (
layeh.com/gumble v0.0.0-20200818122324-146f9205029b
)
replace github.com/Rhymen/go-whatsapp => github.com/tulir/go-whatsapp v0.3.16
go 1.15

19
go.sum
View File

@ -76,13 +76,6 @@ github.com/Philipp15b/go-steam v1.0.1-0.20200727090957-6ae9b3c0a560 h1:ItnC9PEEM
github.com/Philipp15b/go-steam v1.0.1-0.20200727090957-6ae9b3c0a560/go.mod h1:o38AwUFFS4gzbjSoyIgrZ1h9UeDrKwcci1Pj6baifvI=
github.com/PuerkitoBio/goquery v1.4.1/go.mod h1:T9ezsOHcCrDCgA8aF1Cqr3sSYbO/xgdy8/R/XiIMAhA=
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
github.com/Rhymen/go-whatsapp v0.0.0/go.mod h1:rdQr95g2C1xcOfM7QGOhza58HeI3I+tZ/bbluv7VazA=
github.com/Rhymen/go-whatsapp v0.1.2-0.20201226125722-8029c28f5c5a h1:xE1ogaIgFJQbEDoIkiAkMH9wVEAmlKOy/M+kf1xmtCY=
github.com/Rhymen/go-whatsapp v0.1.2-0.20201226125722-8029c28f5c5a/go.mod h1:o7jjkvKnigfu432dMbQ/w4PH0Yp5u4Y6ysCNjUlcYCk=
github.com/Rhymen/go-whatsapp/examples/echo v0.0.0-20190325075644-cc2581bbf24d/go.mod h1:zgCiQtBtZ4P4gFWvwl9aashsdwOcbb/EHOGRmSzM8ME=
github.com/Rhymen/go-whatsapp/examples/restoreSession v0.0.0-20190325075644-cc2581bbf24d/go.mod h1:5sCUSpG616ZoSJhlt9iBNI/KXBqrVLcNUJqg7J9+8pU=
github.com/Rhymen/go-whatsapp/examples/sendImage v0.0.0-20190325075644-cc2581bbf24d/go.mod h1:RdiyhanVEGXTam+mZ3k6Y3VDCCvXYCwReOoxGozqhHw=
github.com/Rhymen/go-whatsapp/examples/sendTextMessages v0.0.0-20190325075644-cc2581bbf24d/go.mod h1:suwzklatySS3Q0+NCxCDh5hYfgXdQUWU1DNcxwAxStM=
github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo=
github.com/RoaringBitmap/roaring v0.5.1/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo=
github.com/RocketChat/Rocket.Chat.Go.SDK v0.0.0-20200922220614-e4a51dfb52e4 h1:u7UvmSK6McEMXFZB310/YZ6uvfDaSFrSoqWoy/qaOW0=
@ -306,7 +299,6 @@ github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@ -382,7 +374,6 @@ github.com/gorilla/schema v1.2.0 h1:YufUaxZYCKGFuAq3c96BOhjgd5nmXiOY9NGzF247Tsc=
github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
@ -570,7 +561,6 @@ github.com/mattermost/mattermost-server/v5 v5.30.1 h1:vsTTMyQcsZGevgsvR1EbQM4/RA
github.com/mattermost/mattermost-server/v5 v5.30.1/go.mod h1:+6oGzqA4hEsoYpmFHT9j+3BtAscj7LJa/qNDxbGvrp4=
github.com/mattermost/rsc v0.0.0-20160330161541-bbaefb05eaa0/go.mod h1:nV5bfVpT//+B1RPD2JvRnxbkLmJEYXmRaaVl15fsXjs=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw=
@ -579,7 +569,6 @@ github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
@ -840,8 +829,8 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/skip2/go-qrcode v0.0.0-20190110000554-dc11ecdae0a9 h1:lpEzuenPuO1XNTeikEmvqYFcU37GVLl8SRNblzyvGBE=
github.com/skip2/go-qrcode v0.0.0-20190110000554-dc11ecdae0a9/go.mod h1:PLPIyL7ikehBD1OAjmKKiOEhbvWyHGaNDjquXMcYABo=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
github.com/slack-go/slack v0.7.4 h1:Z+7CmUDV+ym4lYLA4NNLFIpr3+nDgViHrx8xsuXgrYs=
github.com/slack-go/slack v0.7.4/go.mod h1:FGqNzJBmxIsZURAxh2a8D21AnOVvvXZvGligs4npPUM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
@ -912,6 +901,8 @@ github.com/tinylib/msgp v1.1.2 h1:gWmO7n0Ys2RBEb7GPYB9Ujq8Mk5p2U08lRnmMcGy6BQ=
github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tulir/go-whatsapp v0.3.16 h1:NfcXC2DQXwls3qkAjbFqSeoMX+rUbbpBBGGvCXI3RUw=
github.com/tulir/go-whatsapp v0.3.16/go.mod h1:U5+sm33vrv3wz62YyRM/VS7q2ObXkxU4Xqj/3KOmN9o=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/tylerb/graceful v1.2.15/go.mod h1:LPYTbOYmUTdabwRt0TGhLllQ0MUNbs0Y5q1WXJOI9II=
github.com/uber/jaeger-client-go v2.25.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
@ -1020,7 +1011,6 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@ -1029,6 +1019,7 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 h1:DZhuSZLsGlFL4CmhA8BcRA0mnthyA/nZ00AqCUo7vHg=
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=

File diff suppressed because it is too large Load Diff

View File

@ -69,18 +69,46 @@ message InteractiveAnnotation {
}
}
message DeviceListMetadata {
optional bytes senderKeyHash = 1;
optional uint64 senderTimestamp = 2;
optional bytes recipientKeyHash = 8;
optional uint64 recipientTimestamp = 9;
}
message MessageContextInfo {
optional DeviceListMetadata deviceListMetadata = 1;
}
message AdReplyInfo {
optional string advertiserName = 1;
enum AD_REPLY_INFO_MEDIATYPE {
enum AdReplyInfoMediaType {
NONE = 0;
IMAGE = 1;
VIDEO = 2;
}
optional AD_REPLY_INFO_MEDIATYPE mediaType = 2;
optional AdReplyInfoMediaType mediaType = 2;
optional bytes jpegThumbnail = 16;
optional string caption = 17;
}
message ExternalAdReplyInfo {
optional string title = 1;
optional string body = 2;
enum ExternalAdReplyInfoMediaType {
NONE = 0;
IMAGE = 1;
VIDEO = 2;
}
optional ExternalAdReplyInfoMediaType mediaType = 3;
optional string thumbnailUrl = 4;
optional string mediaUrl = 5;
optional bytes thumbnail = 6;
optional string sourceType = 7;
optional string sourceId = 8;
optional string sourceUrl = 9;
}
message ContextInfo {
optional string stanzaId = 1;
optional string participant = 2;
@ -96,6 +124,8 @@ message ContextInfo {
optional MessageKey placeholderKey = 24;
optional uint32 expiration = 25;
optional int64 ephemeralSettingTimestamp = 26;
optional bytes ephemeralSharedSecret = 27;
optional ExternalAdReplyInfo externalAdReply = 28;
}
message SenderKeyDistributionMessage {
@ -125,6 +155,24 @@ message ImageMessage {
repeated uint32 scanLengths = 22;
optional bytes midQualityFileSha256 = 23;
optional bytes midQualityFileEncSha256 = 24;
optional bool viewOnce = 25;
}
message InvoiceMessage {
optional string note = 1;
optional string token = 2;
enum InvoiceMessageAttachmentType {
IMAGE = 0;
PDF = 1;
}
optional InvoiceMessageAttachmentType attachmentType = 3;
optional string attachmentMimetype = 4;
optional bytes attachmentMediaKey = 5;
optional int64 attachmentMediaKeyTimestamp = 6;
optional bytes attachmentFileSha256 = 7;
optional bytes attachmentFileEncSha256 = 8;
optional string attachmentDirectPath = 9;
optional bytes attachmentJpegThumbnail = 10;
}
message ContactMessage {
@ -156,7 +204,7 @@ message ExtendedTextMessage {
optional string title = 6;
optional fixed32 textArgb = 7;
optional fixed32 backgroundArgb = 8;
enum EXTENDED_TEXT_MESSAGE_FONTTYPE {
enum ExtendedTextMessageFontType {
SANS_SERIF = 0;
SERIF = 1;
NORICAN_REGULAR = 2;
@ -164,12 +212,12 @@ message ExtendedTextMessage {
BEBASNEUE_REGULAR = 4;
OSWALD_HEAVY = 5;
}
optional EXTENDED_TEXT_MESSAGE_FONTTYPE font = 9;
enum EXTENDED_TEXT_MESSAGE_PREVIEWTYPE {
optional ExtendedTextMessageFontType font = 9;
enum ExtendedTextMessagePreviewType {
NONE = 0;
VIDEO = 1;
}
optional EXTENDED_TEXT_MESSAGE_PREVIEWTYPE previewType = 10;
optional ExtendedTextMessagePreviewType previewType = 10;
optional bytes jpegThumbnail = 16;
optional ContextInfo contextInfo = 17;
optional bool doNotPlayInline = 18;
@ -187,8 +235,14 @@ message DocumentMessage {
optional bytes fileEncSha256 = 9;
optional string directPath = 10;
optional int64 mediaKeyTimestamp = 11;
optional bool contactVcard = 12;
optional string thumbnailDirectPath = 13;
optional bytes thumbnailSha256 = 14;
optional bytes thumbnailEncSha256 = 15;
optional bytes jpegThumbnail = 16;
optional ContextInfo contextInfo = 17;
optional uint32 thumbnailHeight = 18;
optional uint32 thumbnailWidth = 19;
}
message AudioMessage {
@ -224,12 +278,13 @@ message VideoMessage {
optional bytes jpegThumbnail = 16;
optional ContextInfo contextInfo = 17;
optional bytes streamingSidecar = 18;
enum VIDEO_MESSAGE_ATTRIBUTION {
enum VideoMessageAttribution {
NONE = 0;
GIPHY = 1;
TENOR = 2;
}
optional VIDEO_MESSAGE_ATTRIBUTION gifAttribution = 19;
optional VideoMessageAttribution gifAttribution = 19;
optional bool viewOnce = 20;
}
message Call {
@ -243,16 +298,23 @@ message Chat {
message ProtocolMessage {
optional MessageKey key = 1;
enum PROTOCOL_MESSAGE_TYPE {
enum ProtocolMessageType {
REVOKE = 0;
EPHEMERAL_SETTING = 3;
EPHEMERAL_SYNC_RESPONSE = 4;
HISTORY_SYNC_NOTIFICATION = 5;
APP_STATE_SYNC_KEY_SHARE = 6;
APP_STATE_SYNC_KEY_REQUEST = 7;
MSG_FANOUT_BACKFILL_REQUEST = 8;
INITIAL_SECURITY_NOTIFICATION_SETTING_SYNC = 9;
}
optional PROTOCOL_MESSAGE_TYPE type = 2;
optional ProtocolMessageType type = 2;
optional uint32 ephemeralExpiration = 4;
optional int64 ephemeralSettingTimestamp = 5;
optional HistorySyncNotification historySyncNotification = 6;
optional AppStateSyncKeyShare appStateSyncKeyShare = 7;
optional AppStateSyncKeyRequest appStateSyncKeyRequest = 8;
optional InitialSecurityNotificationSettingSync initialSecurityNotificationSettingSync = 9;
}
message HistorySyncNotification {
@ -261,14 +323,49 @@ message HistorySyncNotification {
optional bytes mediaKey = 3;
optional bytes fileEncSha256 = 4;
optional string directPath = 5;
enum HISTORY_SYNC_NOTIFICATION_HISTORYSYNCTYPE {
enum HistorySyncNotificationHistorySyncType {
INITIAL_BOOTSTRAP = 0;
INITIAL_STATUS_V3 = 1;
FULL = 2;
RECENT = 3;
PUSH_NAME = 4;
}
optional HISTORY_SYNC_NOTIFICATION_HISTORYSYNCTYPE syncType = 6;
optional HistorySyncNotificationHistorySyncType syncType = 6;
optional uint32 chunkOrder = 7;
optional string originalMessageId = 8;
}
message AppStateSyncKey {
optional AppStateSyncKeyId keyId = 1;
optional AppStateSyncKeyData keyData = 2;
}
message AppStateSyncKeyId {
optional bytes keyId = 1;
}
message AppStateSyncKeyFingerprint {
optional uint32 rawId = 1;
optional uint32 currentIndex = 2;
repeated uint32 deviceIndexes = 3 [packed=true];
}
message AppStateSyncKeyData {
optional bytes keyData = 1;
optional AppStateSyncKeyFingerprint fingerprint = 2;
optional int64 timestamp = 3;
}
message AppStateSyncKeyShare {
repeated AppStateSyncKey keys = 1;
}
message AppStateSyncKeyRequest {
repeated AppStateSyncKeyId keyIds = 1;
}
message InitialSecurityNotificationSettingSync {
optional bool securityNotificationEnabled = 1;
}
message ContactsArrayMessage {
@ -283,7 +380,7 @@ message HSMCurrency {
}
message HSMDateTimeComponent {
enum HSM_DATE_TIME_COMPONENT_DAYOFWEEKTYPE {
enum HSMDateTimeComponentDayOfWeekType {
MONDAY = 1;
TUESDAY = 2;
WEDNESDAY = 3;
@ -292,17 +389,17 @@ message HSMDateTimeComponent {
SATURDAY = 6;
SUNDAY = 7;
}
optional HSM_DATE_TIME_COMPONENT_DAYOFWEEKTYPE dayOfWeek = 1;
optional HSMDateTimeComponentDayOfWeekType dayOfWeek = 1;
optional uint32 year = 2;
optional uint32 month = 3;
optional uint32 dayOfMonth = 4;
optional uint32 hour = 5;
optional uint32 minute = 6;
enum HSM_DATE_TIME_COMPONENT_CALENDARTYPE {
enum HSMDateTimeComponentCalendarType {
GREGORIAN = 1;
SOLAR_HIJRI = 2;
}
optional HSM_DATE_TIME_COMPONENT_CALENDARTYPE calendar = 7;
optional HSMDateTimeComponentCalendarType calendar = 7;
}
message HSMDateTimeUnixEpoch {
@ -347,6 +444,13 @@ message RequestPaymentMessage {
optional uint64 amount1000 = 2;
optional string requestFrom = 3;
optional int64 expiryTimestamp = 5;
optional PaymentMoney amount = 6;
}
message PaymentMoney {
optional int64 value = 1;
optional uint32 offset = 2;
optional string currencyCode = 3;
}
message DeclinePaymentRequestMessage {
@ -457,6 +561,64 @@ message ProductMessage {
optional ContextInfo contextInfo = 17;
}
message OrderMessage {
optional string orderId = 1;
optional bytes thumbnail = 2;
optional int32 itemCount = 3;
enum OrderMessageOrderStatus {
INQUIRY = 1;
}
optional OrderMessageOrderStatus status = 4;
enum OrderMessageOrderSurface {
CATALOG = 1;
}
optional OrderMessageOrderSurface surface = 5;
optional string message = 6;
optional string orderTitle = 7;
optional string sellerJid = 8;
optional string token = 9;
optional ContextInfo contextInfo = 17;
}
message Row {
optional string title = 1;
optional string description = 2;
optional string rowId = 3;
}
message Section {
optional string title = 1;
repeated Row rows = 2;
}
message ListMessage {
optional string title = 1;
optional string description = 2;
optional string buttonText = 3;
enum ListMessageListType {
UNKNOWN = 0;
SINGLE_SELECT = 1;
}
optional ListMessageListType listType = 4;
repeated Section sections = 5;
}
message SingleSelectReply {
optional string selectedRowId = 1;
}
message ListResponseMessage {
optional string title = 1;
enum ListResponseMessageListType {
UNKNOWN = 0;
SINGLE_SELECT = 1;
}
optional ListResponseMessageListType listType = 2;
optional SingleSelectReply singleSelectReply = 3;
optional ContextInfo contextInfo = 4;
optional string description = 5;
}
message GroupInviteMessage {
optional string groupJid = 1;
optional string inviteCode = 2;
@ -467,13 +629,50 @@ message GroupInviteMessage {
optional ContextInfo contextInfo = 7;
}
message EphemeralSetting {
optional string chatJid = 1;
optional uint32 ephemeralExpiration = 2;
optional int64 ephemeralSettingTimestamp = 3;
}
message DeviceSentMessage {
optional string destinationJid = 1;
optional Message message = 2;
optional string phash = 3;
repeated EphemeralSetting broadcastEphemeralSettings = 4;
}
message DeviceSyncMessage {
optional bytes serializedXmlBytes = 1;
message FutureProofMessage {
optional Message message = 1;
}
message ButtonText {
optional string displayText = 1;
}
message Button {
optional string buttonId = 1;
optional ButtonText buttonText = 2;
}
message ButtonsMessage {
optional string contentText = 6;
optional string footerText = 7;
optional ContextInfo contextInfo = 8;
repeated Button buttons = 9;
oneof title {
string titleText = 1;
DocumentMessage documentMessage = 2;
ImageMessage imageMessage = 3;
VideoMessage videoMessage = 4;
LocationMessage locationMessage = 5;
}
}
message ButtonsResponseMessage {
optional string selectedButtonId = 1;
optional string selectedDisplayText = 2;
optional ContextInfo contextInfo = 3;
}
message Message {
@ -503,7 +702,15 @@ message Message {
optional TemplateButtonReplyMessage templateButtonReplyMessage = 29;
optional ProductMessage productMessage = 30;
optional DeviceSentMessage deviceSentMessage = 31;
optional DeviceSyncMessage deviceSyncMessage = 32;
optional MessageContextInfo messageContextInfo = 35;
optional ListMessage listMessage = 36;
optional FutureProofMessage viewOnceMessage = 37;
optional OrderMessage orderMessage = 38;
optional ListResponseMessage listResponseMessage = 39;
optional FutureProofMessage ephemeralMessage = 40;
optional InvoiceMessage invoiceMessage = 41;
optional ButtonsMessage buttonsMessage = 42;
optional ButtonsResponseMessage buttonsResponseMessage = 43;
}
message MessageKey {
@ -514,51 +721,52 @@ message MessageKey {
}
message WebFeatures {
enum WEB_FEATURES_FLAG {
enum WebFeaturesFlag {
NOT_STARTED = 0;
FORCE_UPGRADE = 1;
DEVELOPMENT = 2;
PRODUCTION = 3;
}
optional WEB_FEATURES_FLAG labelsDisplay = 1;
optional WEB_FEATURES_FLAG voipIndividualOutgoing = 2;
optional WEB_FEATURES_FLAG groupsV3 = 3;
optional WEB_FEATURES_FLAG groupsV3Create = 4;
optional WEB_FEATURES_FLAG changeNumberV2 = 5;
optional WEB_FEATURES_FLAG queryStatusV3Thumbnail = 6;
optional WEB_FEATURES_FLAG liveLocations = 7;
optional WEB_FEATURES_FLAG queryVname = 8;
optional WEB_FEATURES_FLAG voipIndividualIncoming = 9;
optional WEB_FEATURES_FLAG quickRepliesQuery = 10;
optional WEB_FEATURES_FLAG payments = 11;
optional WEB_FEATURES_FLAG stickerPackQuery = 12;
optional WEB_FEATURES_FLAG liveLocationsFinal = 13;
optional WEB_FEATURES_FLAG labelsEdit = 14;
optional WEB_FEATURES_FLAG mediaUpload = 15;
optional WEB_FEATURES_FLAG mediaUploadRichQuickReplies = 18;
optional WEB_FEATURES_FLAG vnameV2 = 19;
optional WEB_FEATURES_FLAG videoPlaybackUrl = 20;
optional WEB_FEATURES_FLAG statusRanking = 21;
optional WEB_FEATURES_FLAG voipIndividualVideo = 22;
optional WEB_FEATURES_FLAG thirdPartyStickers = 23;
optional WEB_FEATURES_FLAG frequentlyForwardedSetting = 24;
optional WEB_FEATURES_FLAG groupsV4JoinPermission = 25;
optional WEB_FEATURES_FLAG recentStickers = 26;
optional WEB_FEATURES_FLAG catalog = 27;
optional WEB_FEATURES_FLAG starredStickers = 28;
optional WEB_FEATURES_FLAG voipGroupCall = 29;
optional WEB_FEATURES_FLAG templateMessage = 30;
optional WEB_FEATURES_FLAG templateMessageInteractivity = 31;
optional WEB_FEATURES_FLAG ephemeralMessages = 32;
optional WEB_FEATURES_FLAG e2ENotificationSync = 33;
optional WEB_FEATURES_FLAG recentStickersV2 = 34;
}
message TabletNotificationsInfo {
optional uint64 timestamp = 2;
optional uint32 unreadChats = 3;
optional uint32 notifyMessageCount = 4;
repeated NotificationMessageInfo notifyMessage = 5;
optional WebFeaturesFlag labelsDisplay = 1;
optional WebFeaturesFlag voipIndividualOutgoing = 2;
optional WebFeaturesFlag groupsV3 = 3;
optional WebFeaturesFlag groupsV3Create = 4;
optional WebFeaturesFlag changeNumberV2 = 5;
optional WebFeaturesFlag queryStatusV3Thumbnail = 6;
optional WebFeaturesFlag liveLocations = 7;
optional WebFeaturesFlag queryVname = 8;
optional WebFeaturesFlag voipIndividualIncoming = 9;
optional WebFeaturesFlag quickRepliesQuery = 10;
optional WebFeaturesFlag payments = 11;
optional WebFeaturesFlag stickerPackQuery = 12;
optional WebFeaturesFlag liveLocationsFinal = 13;
optional WebFeaturesFlag labelsEdit = 14;
optional WebFeaturesFlag mediaUpload = 15;
optional WebFeaturesFlag mediaUploadRichQuickReplies = 18;
optional WebFeaturesFlag vnameV2 = 19;
optional WebFeaturesFlag videoPlaybackUrl = 20;
optional WebFeaturesFlag statusRanking = 21;
optional WebFeaturesFlag voipIndividualVideo = 22;
optional WebFeaturesFlag thirdPartyStickers = 23;
optional WebFeaturesFlag frequentlyForwardedSetting = 24;
optional WebFeaturesFlag groupsV4JoinPermission = 25;
optional WebFeaturesFlag recentStickers = 26;
optional WebFeaturesFlag catalog = 27;
optional WebFeaturesFlag starredStickers = 28;
optional WebFeaturesFlag voipGroupCall = 29;
optional WebFeaturesFlag templateMessage = 30;
optional WebFeaturesFlag templateMessageInteractivity = 31;
optional WebFeaturesFlag ephemeralMessages = 32;
optional WebFeaturesFlag e2ENotificationSync = 33;
optional WebFeaturesFlag recentStickersV2 = 34;
optional WebFeaturesFlag syncdRelease1 = 35;
optional WebFeaturesFlag recentStickersV3 = 36;
optional WebFeaturesFlag userNotice = 37;
optional WebFeaturesFlag syncdRelease11 = 38;
optional WebFeaturesFlag support = 39;
optional WebFeaturesFlag groupUiiCleanup = 40;
optional WebFeaturesFlag groupDogfoodingInternalOnly = 41;
optional WebFeaturesFlag settingsSync = 42;
}
message NotificationMessageInfo {
@ -576,14 +784,14 @@ message WebNotificationsInfo {
}
message PaymentInfo {
enum PAYMENT_INFO_CURRENCY {
enum PaymentInfoCurrency {
UNKNOWN_CURRENCY = 0;
INR = 1;
}
optional PAYMENT_INFO_CURRENCY currencyDeprecated = 1;
optional PaymentInfoCurrency currencyDeprecated = 1;
optional uint64 amount1000 = 2;
optional string receiverJid = 3;
enum PAYMENT_INFO_STATUS {
enum PaymentInfoStatus {
UNKNOWN_STATUS = 0;
PROCESSING = 1;
SENT = 2;
@ -597,13 +805,13 @@ message PaymentInfo {
WAITING_FOR_PAYER = 10;
WAITING = 11;
}
optional PAYMENT_INFO_STATUS status = 4;
optional PaymentInfoStatus status = 4;
optional uint64 transactionTimestamp = 5;
optional MessageKey requestMessageKey = 6;
optional uint64 expiryTimestamp = 7;
optional bool futureproofed = 8;
optional string currency = 9;
enum PAYMENT_INFO_TXNSTATUS {
enum PaymentInfoTxnStatus {
UNKNOWN = 0;
PENDING_SETUP = 1;
PENDING_RECEIVER_SETUP = 2;
@ -633,14 +841,14 @@ message PaymentInfo {
COLLECT_CANCELED = 26;
COLLECT_CANCELLING = 27;
}
optional PAYMENT_INFO_TXNSTATUS txnStatus = 10;
optional PaymentInfoTxnStatus txnStatus = 10;
}
message WebMessageInfo {
required MessageKey key = 1;
optional Message message = 2;
optional uint64 messageTimestamp = 3;
enum WEB_MESSAGE_INFO_STATUS {
enum WebMessageInfoStatus {
ERROR = 0;
PENDING = 1;
SERVER_ACK = 2;
@ -648,7 +856,7 @@ message WebMessageInfo {
READ = 4;
PLAYED = 5;
}
optional WEB_MESSAGE_INFO_STATUS status = 4;
optional WebMessageInfoStatus status = 4;
optional string participant = 5;
optional bool ignore = 16;
optional bool starred = 17;
@ -658,7 +866,7 @@ message WebMessageInfo {
optional bool multicast = 21;
optional bool urlText = 22;
optional bool urlNumber = 23;
enum WEB_MESSAGE_INFO_STUBTYPE {
enum WebMessageInfoStubType {
UNKNOWN = 0;
REVOKE = 1;
CIPHERTEXT = 2;
@ -732,8 +940,54 @@ message WebMessageInfo {
GROUP_V4_ADD_INVITE_SENT = 70;
GROUP_PARTICIPANT_ADD_REQUEST_JOIN = 71;
CHANGE_EPHEMERAL_SETTING = 72;
E2E_DEVICE_CHANGED = 73;
VIEWED_ONCE = 74;
E2E_ENCRYPTED_NOW = 75;
BLUE_MSG_BSP_FB_TO_BSP_PREMISE = 76;
BLUE_MSG_BSP_FB_TO_SELF_FB = 77;
BLUE_MSG_BSP_FB_TO_SELF_PREMISE = 78;
BLUE_MSG_BSP_FB_UNVERIFIED = 79;
BLUE_MSG_BSP_FB_UNVERIFIED_TO_SELF_PREMISE_VERIFIED = 80;
BLUE_MSG_BSP_FB_VERIFIED = 81;
BLUE_MSG_BSP_FB_VERIFIED_TO_SELF_PREMISE_UNVERIFIED = 82;
BLUE_MSG_BSP_PREMISE_TO_SELF_PREMISE = 83;
BLUE_MSG_BSP_PREMISE_UNVERIFIED = 84;
BLUE_MSG_BSP_PREMISE_UNVERIFIED_TO_SELF_PREMISE_VERIFIED = 85;
BLUE_MSG_BSP_PREMISE_VERIFIED = 86;
BLUE_MSG_BSP_PREMISE_VERIFIED_TO_SELF_PREMISE_UNVERIFIED = 87;
BLUE_MSG_CONSUMER_TO_BSP_FB_UNVERIFIED = 88;
BLUE_MSG_CONSUMER_TO_BSP_PREMISE_UNVERIFIED = 89;
BLUE_MSG_CONSUMER_TO_SELF_FB_UNVERIFIED = 90;
BLUE_MSG_CONSUMER_TO_SELF_PREMISE_UNVERIFIED = 91;
BLUE_MSG_SELF_FB_TO_BSP_PREMISE = 92;
BLUE_MSG_SELF_FB_TO_SELF_PREMISE = 93;
BLUE_MSG_SELF_FB_UNVERIFIED = 94;
BLUE_MSG_SELF_FB_UNVERIFIED_TO_SELF_PREMISE_VERIFIED = 95;
BLUE_MSG_SELF_FB_VERIFIED = 96;
BLUE_MSG_SELF_FB_VERIFIED_TO_SELF_PREMISE_UNVERIFIED = 97;
BLUE_MSG_SELF_PREMISE_TO_BSP_PREMISE = 98;
BLUE_MSG_SELF_PREMISE_UNVERIFIED = 99;
BLUE_MSG_SELF_PREMISE_VERIFIED = 100;
BLUE_MSG_TO_BSP_FB = 101;
BLUE_MSG_TO_CONSUMER = 102;
BLUE_MSG_TO_SELF_FB = 103;
BLUE_MSG_UNVERIFIED_TO_BSP_FB_VERIFIED = 104;
BLUE_MSG_UNVERIFIED_TO_BSP_PREMISE_VERIFIED = 105;
BLUE_MSG_UNVERIFIED_TO_SELF_FB_VERIFIED = 106;
BLUE_MSG_UNVERIFIED_TO_VERIFIED = 107;
BLUE_MSG_VERIFIED_TO_BSP_FB_UNVERIFIED = 108;
BLUE_MSG_VERIFIED_TO_BSP_PREMISE_UNVERIFIED = 109;
BLUE_MSG_VERIFIED_TO_SELF_FB_UNVERIFIED = 110;
BLUE_MSG_VERIFIED_TO_UNVERIFIED = 111;
BLUE_MSG_BSP_FB_UNVERIFIED_TO_BSP_PREMISE_VERIFIED = 112;
BLUE_MSG_BSP_FB_UNVERIFIED_TO_SELF_FB_VERIFIED = 113;
BLUE_MSG_BSP_FB_VERIFIED_TO_BSP_PREMISE_UNVERIFIED = 114;
BLUE_MSG_BSP_FB_VERIFIED_TO_SELF_FB_UNVERIFIED = 115;
BLUE_MSG_SELF_FB_UNVERIFIED_TO_BSP_PREMISE_VERIFIED = 116;
BLUE_MSG_SELF_FB_VERIFIED_TO_BSP_PREMISE_UNVERIFIED = 117;
E2E_IDENTITY_UNAVAILABLE = 118;
}
optional WEB_MESSAGE_INFO_STUBTYPE messageStubType = 24;
optional WebMessageInfoStubType messageStubType = 24;
optional bool clearMedia = 25;
repeated string messageStubParameters = 26;
optional uint32 duration = 27;
@ -743,5 +997,15 @@ message WebMessageInfo {
optional PaymentInfo quotedPaymentInfo = 31;
optional uint64 ephemeralStartTimestamp = 32;
optional uint32 ephemeralDuration = 33;
optional bool ephemeralOffToOn = 34;
optional bool ephemeralOutOfSync = 35;
enum WebMessageInfoBizPrivacyStatus {
E2EE = 0;
FB = 2;
BSP = 1;
BSP_AND_FB = 3;
}
optional WebMessageInfoBizPrivacyStatus bizPrivacyStatus = 36;
optional string verifiedBizName = 37;
}

View File

@ -2,6 +2,7 @@
package whatsapp
import (
"fmt"
"math/rand"
"net/http"
"net/url"
@ -9,7 +10,6 @@ import (
"time"
"github.com/gorilla/websocket"
"github.com/pkg/errors"
)
type metric byte
@ -195,7 +195,7 @@ func (wac *Conn) connect() (err error) {
headers := http.Header{"Origin": []string{"https://web.whatsapp.com"}}
wsConn, _, err := dialer.Dial("wss://web.whatsapp.com/ws", headers)
if err != nil {
return errors.Wrap(err, "couldn't dial whatsapp web websocket")
return fmt.Errorf("couldn't dial whatsapp web websocket: %w", err)
}
wsConn.SetCloseHandler(func(code int, text string) error {
@ -221,7 +221,7 @@ func (wac *Conn) connect() (err error) {
wac.wg = &sync.WaitGroup{}
wac.wg.Add(2)
go wac.readPump()
go wac.keepAlive(20000, 60000)
go wac.keepAlive(20000, 55000)
wac.loggedIn = false
return nil
@ -237,7 +237,10 @@ func (wac *Conn) Disconnect() (Session, error) {
close(wac.ws.close) //signal close
wac.wg.Wait() //wait for close
err := wac.ws.conn.Close()
var err error
if wac.ws != nil && wac.ws.conn != nil {
err = wac.ws.conn.Close()
}
wac.ws = nil
if wac.session == nil {
@ -246,17 +249,20 @@ func (wac *Conn) Disconnect() (Session, error) {
return *wac.session, err
}
func (wac *Conn) AdminTest() (bool, error) {
func (wac *Conn) IsLoginInProgress() bool {
return wac.sessionLock == 1
}
func (wac *Conn) AdminTest() error {
if !wac.connected {
return false, ErrNotConnected
return ErrNotConnected
}
if !wac.loggedIn {
return false, ErrInvalidSession
return ErrInvalidSession
}
result, err := wac.sendAdminTest()
return result, err
return wac.sendAdminTest()
}
func (wac *Conn) keepAlive(minIntervalMs int, maxIntervalMs int) {
@ -265,7 +271,7 @@ func (wac *Conn) keepAlive(minIntervalMs int, maxIntervalMs int) {
for {
err := wac.sendKeepAlive()
if err != nil {
wac.handle(errors.Wrap(err, "keepAlive failed"))
wac.handle(fmt.Errorf("keepAlive failed: %w", err))
//TODO: Consequences?
}
interval := rand.Intn(maxIntervalMs-minIntervalMs) + minIntervalMs

View File

@ -39,20 +39,20 @@ func (wac *Conn) Search(search string, count, page int) (*binary.Node, error) {
return wac.query("search", "", "", "", "", search, count, page)
}
func (wac *Conn) LoadMessages(jid, messageId string, count int) (*binary.Node, error) {
func (wac *Conn) LoadMessages(jid string, count int) (*binary.Node, error) {
return wac.query("message", jid, "", "before", "true", "", count, 0)
}
func (wac *Conn) LoadMessagesBefore(jid, messageId string, count int) (*binary.Node, error) {
return wac.query("message", jid, messageId, "before", "true", "", count, 0)
func (wac *Conn) LoadMessagesBefore(jid, messageId string, fromMe bool, count int) (*binary.Node, error) {
return wac.query("message", jid, messageId, "before", strconv.FormatBool(fromMe), "", count, 0)
}
func (wac *Conn) LoadMessagesAfter(jid, messageId string, count int) (*binary.Node, error) {
return wac.query("message", jid, messageId, "after", "true", "", count, 0)
func (wac *Conn) LoadMessagesAfter(jid, messageId string, fromMe bool, count int) (*binary.Node, error) {
return wac.query("message", jid, messageId, "after", strconv.FormatBool(fromMe), "", count, 0)
}
func (wac *Conn) LoadMediaInfo(jid, messageId, owner string) (*binary.Node, error) {
return wac.query("media", jid, messageId, "", owner, "", 0, 0)
func (wac *Conn) LoadMediaInfo(jid, messageId string, fromMe bool) (*binary.Node, error) {
return wac.query("media", jid, messageId, "", strconv.FormatBool(fromMe), "", 0, 0)
}
func (wac *Conn) Presence(jid string, presence Presence) (<-chan string, error) {
@ -96,11 +96,19 @@ func (wac *Conn) Emoji() (*binary.Node, error) {
}
func (wac *Conn) Contacts() (*binary.Node, error) {
return wac.query("contacts", "", "", "", "", "", 0, 0)
node, err := wac.query("contacts", "", "", "", "", "", 0, 0)
if node != nil && node.Description == "response" && node.Attributes["type"] == "contacts" {
wac.updateContacts(node.Content)
}
return node, err
}
func (wac *Conn) Chats() (*binary.Node, error) {
return wac.query("chat", "", "", "", "", "", 0, 0)
node, err := wac.query("chat", "", "", "", "", "", 0, 0)
if node != nil && node.Description == "response" && node.Attributes["type"] == "chat" {
wac.updateChats(node.Content)
}
return node, err
}
func (wac *Conn) Read(jid, id string) (<-chan string, error) {
@ -177,13 +185,18 @@ func (wac *Conn) query(t, jid, messageId, kind, owner, search string, count, pag
return nil, err
}
msg, err := wac.decryptBinaryMessage([]byte(<-ch))
if err != nil {
return nil, err
}
select {
case response := <-ch:
msg, err := wac.decryptBinaryMessage([]byte(response))
if err != nil {
return nil, err
}
//TODO: use parseProtoMessage
return msg, nil
//TODO: use parseProtoMessage
return msg, nil
case <-time.After(3 * time.Minute):
return nil, ErrQueryTimeout
}
}
func (wac *Conn) setGroup(t, jid, subject string, participants []string) (<-chan string, error) {

View File

@ -1,25 +1,46 @@
package whatsapp
import (
"encoding/json"
"errors"
"fmt"
"github.com/pkg/errors"
)
var (
ErrAlreadyConnected = errors.New("already connected")
ErrAlreadyLoggedIn = errors.New("already logged in")
ErrInvalidSession = errors.New("invalid session")
ErrLoginInProgress = errors.New("login or restore already running")
ErrNotConnected = errors.New("not connected")
ErrInvalidWsData = errors.New("received invalid data")
ErrInvalidWsState = errors.New("can't handle binary data when not logged in")
ErrConnectionTimeout = errors.New("connection timed out")
ErrMissingMessageTag = errors.New("no messageTag specified or to short")
ErrInvalidHmac = errors.New("invalid hmac")
ErrInvalidServerResponse = errors.New("invalid response received from server")
ErrServerRespondedWith404 = errors.New("server responded with status 404")
ErrInvalidWebsocket = errors.New("invalid websocket")
ErrAlreadyConnected = errors.New("already connected")
ErrAlreadyLoggedIn = errors.New("already logged in")
ErrInvalidSession = errors.New("invalid session")
ErrLoginInProgress = errors.New("login or restore already running")
ErrNotConnected = errors.New("not connected")
ErrInvalidWsData = errors.New("received invalid data")
ErrInvalidWsState = errors.New("can't handle binary data when not logged in")
ErrConnectionTimeout = errors.New("connection timed out")
ErrMissingMessageTag = errors.New("no messageTag specified or to short")
ErrInvalidHmac = errors.New("invalid hmac")
ErrInvalidServerResponse = errors.New("invalid response received from server")
ErrServerRespondedWith404 = errors.New("server responded with status 404")
ErrMediaDownloadFailedWith404 = errors.New("download failed with status code 404")
ErrMediaDownloadFailedWith410 = errors.New("download failed with status code 410")
ErrLoginTimedOut = errors.New("login timed out")
ErrQueryTimeout = errors.New("query timed out")
ErrBadRequest = errors.New("400 (bad request)")
ErrUnpaired = errors.New("401 (unpaired from phone)")
ErrAccessDenied = errors.New("403 (access denied)")
ErrLoggedIn = errors.New("405 (already logged in)")
ErrReplaced = errors.New("409 (logged in from another location)")
ErrNoURLPresent = errors.New("no url present")
ErrFileLengthMismatch = errors.New("file length does not match")
ErrInvalidHashLength = errors.New("hash too short")
ErrTooShortFile = errors.New("file too short")
ErrInvalidMediaHMAC = errors.New("invalid media hmac")
ErrCantGetInviteLink = errors.New("you don't have the permission to view the invite link")
ErrJoinUnauthorized = errors.New("you're not allowed to join that group")
ErrInvalidWebsocket = errors.New("invalid websocket")
ErrMessageTypeNotImplemented = errors.New("message type not implemented")
ErrOptionsNotProvided = errors.New("new conn options not provided")
)
@ -40,3 +61,31 @@ type ErrConnectionClosed struct {
func (e *ErrConnectionClosed) Error() string {
return fmt.Sprintf("server closed connection,code: %d,text: %s", e.Code, e.Text)
}
type StatusResponseFields struct {
// The response status code. This is always expected to be present.
Status int `json:"status"`
// Some error messages include a "tos" value. If it's higher than 0, it
// might mean the user has been banned for breaking the terms of service.
TermsOfService int `json:"tos,omitempty"`
// This is a timestamp that's at least present in message send responses.
Timestamp int64 `json:"t,omitempty"`
}
type StatusResponse struct {
StatusResponseFields
RequestType string `json:"-"`
Extra map[string]interface{} `json:"-"`
}
func (sr *StatusResponse) UnmarshalJSON(data []byte) error {
err := json.Unmarshal(data, &sr.Extra)
if err != nil {
return err
}
return json.Unmarshal(data, &sr.StatusResponseFields)
}
func (sr StatusResponse) Error() string {
return fmt.Sprintf("%s responded with %d", sr.RequestType, sr.Status)
}

View File

@ -1,14 +1,10 @@
module github.com/Rhymen/go-whatsapp
require (
github.com/Rhymen/go-whatsapp/examples/echo v0.0.0-20190325075644-cc2581bbf24d // indirect
github.com/Rhymen/go-whatsapp/examples/restoreSession v0.0.0-20190325075644-cc2581bbf24d // indirect
github.com/Rhymen/go-whatsapp/examples/sendImage v0.0.0-20190325075644-cc2581bbf24d // indirect
github.com/Rhymen/go-whatsapp/examples/sendTextMessages v0.0.0-20190325075644-cc2581bbf24d // indirect
github.com/golang/protobuf v1.3.0
github.com/gorilla/websocket v1.4.1
github.com/pkg/errors v0.8.1
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2
)
go 1.11
go 1.13
require (
github.com/golang/protobuf v1.4.2
github.com/gorilla/websocket v1.4.2
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9
google.golang.org/protobuf v1.24.0
)

View File

@ -1,37 +1,70 @@
github.com/Baozisoftware/qrcode-terminal-go v0.0.0-20170407111555-c0650d8dff0f h1:2dk3eOnYllh+wUOuDhOoC2vUVoJF/5z478ryJ+wzEII=
github.com/Baozisoftware/qrcode-terminal-go v0.0.0-20170407111555-c0650d8dff0f/go.mod h1:4a58ifQTEe2uwwsaqbh3i2un5/CBPg+At/qHpt18Tmk=
github.com/Rhymen/go-whatsapp v0.0.0/go.mod h1:rdQr95g2C1xcOfM7QGOhza58HeI3I+tZ/bbluv7VazA=
github.com/Rhymen/go-whatsapp/examples/echo v0.0.0-20190325075644-cc2581bbf24d h1:m3wkrunHupL9XzzM+JZu1pgoDV1d9LFtD0gedNTHVDU=
github.com/Rhymen/go-whatsapp/examples/echo v0.0.0-20190325075644-cc2581bbf24d/go.mod h1:zgCiQtBtZ4P4gFWvwl9aashsdwOcbb/EHOGRmSzM8ME=
github.com/Rhymen/go-whatsapp/examples/restoreSession v0.0.0-20190325075644-cc2581bbf24d h1:muQlzqfZxjptOBjPdv+UoxVMr8Y1rPx7VMGPJIAFc5w=
github.com/Rhymen/go-whatsapp/examples/restoreSession v0.0.0-20190325075644-cc2581bbf24d/go.mod h1:5sCUSpG616ZoSJhlt9iBNI/KXBqrVLcNUJqg7J9+8pU=
github.com/Rhymen/go-whatsapp/examples/sendImage v0.0.0-20190325075644-cc2581bbf24d h1:xP//3V77YvHd1cj2Z3ttuQWAvs5WmIwBbjKe/t0g/tM=
github.com/Rhymen/go-whatsapp/examples/sendImage v0.0.0-20190325075644-cc2581bbf24d/go.mod h1:RdiyhanVEGXTam+mZ3k6Y3VDCCvXYCwReOoxGozqhHw=
github.com/Rhymen/go-whatsapp/examples/sendTextMessages v0.0.0-20190325075644-cc2581bbf24d h1:IRmRE0SPMByczwE2dhnTcVojje3w2TCSKwFrboLUbDg=
github.com/Rhymen/go-whatsapp/examples/sendTextMessages v0.0.0-20190325075644-cc2581bbf24d/go.mod h1:suwzklatySS3Q0+NCxCDh5hYfgXdQUWU1DNcxwAxStM=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.0 h1:kbxbvI4Un1LUWKxufD+BiE6AEExYYgkQLQmLFqA1LFk=
github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-isatty v0.0.5 h1:tHXDdz1cpzGaovsTB+TVB8q90WEokoVmfMqoVcrLUgw=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/skip2/go-qrcode v0.0.0-20190110000554-dc11ecdae0a9 h1:lpEzuenPuO1XNTeikEmvqYFcU37GVLl8SRNblzyvGBE=
github.com/skip2/go-qrcode v0.0.0-20190110000554-dc11ecdae0a9/go.mod h1:PLPIyL7ikehBD1OAjmKKiOEhbvWyHGaNDjquXMcYABo=
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf3coIG4HqZElCPehJsfAYM=
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@ -3,7 +3,10 @@ package whatsapp
import (
"encoding/json"
"fmt"
"strconv"
"time"
"github.com/Rhymen/go-whatsapp/binary"
)
func (wac *Conn) GetGroupMetaData(jid string) (<-chan string, error) {
@ -57,8 +60,11 @@ func (wac *Conn) GroupInviteLink(jid string) (string, error) {
return "", fmt.Errorf("request timed out")
}
if int(response["status"].(float64)) != 200 {
return "", fmt.Errorf("request responded with %d", response["status"])
status := int(response["status"].(float64))
if status == 401 {
return "", ErrCantGetInviteLink
} else if status != 200 {
return "", fmt.Errorf("request responded with %d", status)
}
return response["code"].(string), nil
@ -82,9 +88,77 @@ func (wac *Conn) GroupAcceptInviteCode(code string) (jid string, err error) {
return "", fmt.Errorf("request timed out")
}
if int(response["status"].(float64)) != 200 {
return "", fmt.Errorf("request responded with %d", response["status"])
status := int(response["status"].(float64))
if status == 401 {
return "", ErrJoinUnauthorized
} else if status != 200 {
return "", fmt.Errorf("request responded with %d", status)
}
return response["gid"].(string), nil
}
type descriptionID struct {
DescID string `json:"descId"`
}
func (wac *Conn) getDescriptionID(jid string) (string, error) {
data, err := wac.GetGroupMetaData(jid)
if err != nil {
return "none", err
}
var oldData descriptionID
err = json.Unmarshal([]byte(<-data), &oldData)
if err != nil {
return "none", err
}
if oldData.DescID == "" {
return "none", nil
}
return oldData.DescID, nil
}
func (wac *Conn) UpdateGroupDescription(jid, description string) (<-chan string, error) {
prevID, err := wac.getDescriptionID(jid)
if err != nil {
return nil, err
}
newData := map[string]string{
"prev": prevID,
}
var desc interface{} = description
if description == "" {
newData["delete"] = "true"
desc = nil
} else {
newData["id"] = fmt.Sprintf("%d-%d", time.Now().Unix(), wac.msgCount*19)
}
tag := fmt.Sprintf("%d.--%d", time.Now().Unix(), wac.msgCount*19)
n := binary.Node{
Description: "action",
Attributes: map[string]string{
"type": "set",
"epoch": strconv.Itoa(wac.msgCount),
},
Content: []interface{}{
binary.Node{
Description: "group",
Attributes: map[string]string{
"id": tag,
"jid": jid,
"type": "description",
"author": wac.Info.Wid,
},
Content: []binary.Node{
{
Description: "description",
Attributes: newData,
Content: desc,
},
},
},
},
}
return wac.writeBinary(n, group, 136, tag)
}

View File

@ -117,6 +117,12 @@ type RawMessageHandler interface {
HandleRawMessage(message *proto.WebMessageInfo)
}
// The UnknownBinaryHandler interface needs to be implemented to receive unhandled binary messages.
type UnknownBinaryHandler interface {
Handler
HandleUnknownBinaryNode(message *binary.Node)
}
/**
The ContactListHandler interface needs to be implemented to applky custom actions to contact lists dispatched by the dispatcher.
*/
@ -141,6 +147,16 @@ type BatteryMessageHandler interface {
HandleBatteryMessage(battery BatteryMessage)
}
type ReadMessageHandler interface {
Handler
HandleReadMessage(read ReadMessage)
}
type ReceivedMessageHandler interface {
Handler
HandleReceivedMessage(received ReceivedMessage)
}
/**
The NewContactHandler interface needs to be implemented to receive the contact's name for the first time.
*/
@ -186,6 +202,19 @@ func (wac *Conn) shouldCallSynchronously(handler Handler) bool {
}
func (wac *Conn) handle(message interface{}) {
defer func() {
if errIfc := recover(); errIfc != nil {
if err, ok := errIfc.(error); ok {
wac.unsafeHandle(fmt.Errorf("panic in WhatsApp handler: %w", err))
} else {
wac.unsafeHandle(fmt.Errorf("panic in WhatsApp handler: %v", errIfc))
}
}
}()
wac.unsafeHandle(message)
}
func (wac *Conn) unsafeHandle(message interface{}) {
wac.handleWithCustomHandlers(message, wac.handler)
}
@ -324,6 +353,28 @@ func (wac *Conn) handleWithCustomHandlers(message interface{}, handlers []Handle
}
}
case ReadMessage:
for _, h := range handlers {
if x, ok := h.(ReadMessageHandler); ok {
if wac.shouldCallSynchronously(h) {
x.HandleReadMessage(m)
} else {
go x.HandleReadMessage(m)
}
}
}
case ReceivedMessage:
for _, h := range handlers {
if x, ok := h.(ReceivedMessageHandler); ok {
if wac.shouldCallSynchronously(h) {
x.HandleReceivedMessage(m)
} else {
go x.HandleReceivedMessage(m)
}
}
}
case *proto.WebMessageInfo:
for _, h := range handlers {
if x, ok := h.(RawMessageHandler); ok {
@ -334,6 +385,17 @@ func (wac *Conn) handleWithCustomHandlers(message interface{}, handlers []Handle
}
}
}
case *binary.Node:
for _, h := range handlers {
if x, ok := h.(UnknownBinaryHandler); ok {
if wac.shouldCallSynchronously(h) {
x.HandleUnknownBinaryNode(m)
} else {
go x.HandleUnknownBinaryNode(m)
}
}
}
}
}
@ -425,6 +487,8 @@ func (wac *Conn) dispatch(msg interface{}) {
for a := range con {
wac.handle(ParseNodeMessage(con[a]))
}
} else {
wac.handle(message)
}
} else if message.Description == "response" && message.Attributes["type"] == "contacts" {
wac.updateContacts(message.Content)
@ -432,6 +496,8 @@ func (wac *Conn) dispatch(msg interface{}) {
} else if message.Description == "response" && message.Attributes["type"] == "chat" {
wac.updateChats(message.Content)
wac.handleChats(message.Content)
} else {
wac.handle(message)
}
case error:
wac.handle(message)

View File

@ -21,7 +21,7 @@ import (
func Download(url string, mediaKey []byte, appInfo MediaType, fileLength int) ([]byte, error) {
if url == "" {
return nil, fmt.Errorf("no url present")
return nil, ErrNoURLPresent
}
file, mac, err := downloadMedia(url)
if err != nil {
@ -39,7 +39,7 @@ func Download(url string, mediaKey []byte, appInfo MediaType, fileLength int) ([
return nil, err
}
if len(data) != fileLength {
return nil, fmt.Errorf("file length does not match. Expected: %v, got: %v", fileLength, len(data))
return nil, ErrFileLengthMismatch
}
return data, nil
}
@ -51,10 +51,10 @@ func validateMedia(iv []byte, file []byte, macKey []byte, mac []byte) error {
return err
}
if n < 10 {
return fmt.Errorf("hash to short")
return ErrInvalidHashLength
}
if !hmac.Equal(h.Sum(nil)[:10], mac) {
return fmt.Errorf("invalid media hmac")
return ErrInvalidMediaHMAC
}
return nil
}
@ -74,10 +74,16 @@ func downloadMedia(url string) (file []byte, mac []byte, err error) {
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
if resp.StatusCode == http.StatusNotFound {
return nil, nil, ErrMediaDownloadFailedWith404
}
if resp.StatusCode == http.StatusGone {
return nil, nil, ErrMediaDownloadFailedWith410
}
return nil, nil, fmt.Errorf("download failed with status code %d", resp.StatusCode)
}
if resp.ContentLength <= 10 {
return nil, nil, fmt.Errorf("file to short")
return nil, nil, ErrTooShortFile
}
data, err := ioutil.ReadAll(resp.Body)
if err != nil {

View File

@ -12,6 +12,7 @@ import (
"github.com/Rhymen/go-whatsapp/binary"
"github.com/Rhymen/go-whatsapp/binary/proto"
"github.com/davecgh/go-spew/spew"
)
type MediaType string
@ -23,6 +24,23 @@ const (
MediaDocument MediaType = "WhatsApp Document Keys"
)
func (wac *Conn) SendRaw(msg *proto.WebMessageInfo, output chan<- error) {
ch, err := wac.sendProto(msg)
if err != nil {
output <- fmt.Errorf("could not send proto: %w", err)
return
}
response := <-ch
resp := StatusResponse{RequestType: "message sending"}
if err = json.Unmarshal([]byte(response), &resp); err != nil {
output <- fmt.Errorf("error decoding sending response: %w", err)
} else if resp.Status != 200 {
output <- resp
} else {
output <- nil
}
}
func (wac *Conn) Send(msg interface{}) (string, error) {
var msgProto *proto.WebMessageInfo
@ -76,21 +94,16 @@ func (wac *Conn) Send(msg interface{}) (string, error) {
select {
case response := <-ch:
var resp map[string]interface{}
resp := StatusResponse{RequestType: "message sending"}
if err = json.Unmarshal([]byte(response), &resp); err != nil {
return "ERROR", fmt.Errorf("error decoding sending response: %v\n", err)
} else if resp.Status != 200 {
return "ERROR", resp
}
if int(resp["status"].(float64)) != 200 {
return "ERROR", fmt.Errorf("message sending responded with %v", resp["status"])
}
if int(resp["status"].(float64)) == 200 {
return getMessageInfo(msgProto).Id, nil
}
return getMessageInfo(msgProto).Id, nil
case <-time.After(wac.msgTimeout):
return "ERROR", fmt.Errorf("sending message timed out")
}
return "ERROR", nil
}
func (wac *Conn) sendProto(p *proto.WebMessageInfo) (<-chan string, error) {
@ -151,21 +164,16 @@ func (wac *Conn) DeleteMessage(remotejid, msgid string, fromMe bool) error {
select {
case response := <-ch:
var resp map[string]interface{}
resp := StatusResponse{RequestType: "message deletion"}
if err = json.Unmarshal([]byte(response), &resp); err != nil {
return fmt.Errorf("error decoding deletion response: %v", err)
} else if resp.Status != 200 {
return resp
}
if int(resp["status"].(float64)) != 200 {
return fmt.Errorf("message deletion responded with %v", resp["status"])
}
if int(resp["status"].(float64)) == 200 {
return nil
}
return nil
case <-time.After(wac.msgTimeout):
return fmt.Errorf("deleting message timed out")
}
return nil
}
func (wac *Conn) deleteChatProto(remotejid, msgid string, fromMe bool) (<-chan string, error) {
@ -258,7 +266,7 @@ func getInfoProto(info *MessageInfo) *proto.WebMessageInfo {
}
info.FromMe = true
status := proto.WebMessageInfo_WEB_MESSAGE_INFO_STATUS(info.Status)
status := proto.WebMessageInfo_WebMessageInfoStatus(info.Status)
return &proto.WebMessageInfo{
Key: &proto.MessageKey{
@ -275,19 +283,20 @@ func getInfoProto(info *MessageInfo) *proto.WebMessageInfo {
ContextInfo represents contextinfo of every message
*/
type ContextInfo struct {
QuotedMessageID string //StanzaId
QuotedMessageID string // StanzaId
QuotedMessage *proto.Message
Participant string
IsForwarded bool
MentionedJID []string
}
func getMessageContext(msg *proto.ContextInfo) ContextInfo {
return ContextInfo{
QuotedMessageID: msg.GetStanzaId(), //StanzaId
QuotedMessageID: msg.GetStanzaId(), // StanzaId
QuotedMessage: msg.GetQuotedMessage(),
Participant: msg.GetParticipant(),
IsForwarded: msg.GetIsForwarded(),
MentionedJID: msg.GetMentionedJid(),
}
}
@ -325,7 +334,6 @@ func getTextMessage(msg *proto.WebMessageInfo) TextMessage {
text.ContextInfo = getMessageContext(m.GetContextInfo())
} else {
text.Text = msg.GetMessage().GetConversation()
}
return text
@ -803,7 +811,6 @@ func getContactMessageProto(msg ContactMessage) *proto.WebMessageInfo {
}
func ParseProtoMessage(msg *proto.WebMessageInfo) interface{} {
switch {
case msg.GetMessage().GetAudioMessage() != nil:
@ -837,7 +844,8 @@ func ParseProtoMessage(msg *proto.WebMessageInfo) interface{} {
return getContactMessage(msg)
default:
//cannot match message
// cannot match message
spew.Dump(msg)
return ErrMessageTypeNotImplemented
}
}
@ -873,15 +881,50 @@ func getNewContact(msg map[string]string) Contact {
return contact
}
// ReadMessage represents a chat that the user read on the WhatsApp mobile app.
type ReadMessage struct {
Jid string
}
func getReadMessage(msg map[string]string) ReadMessage {
return ReadMessage{
Jid: msg["jid"],
}
}
// ReceivedMessage probably represents a message that the user read on the WhatsApp mobile app.
type ReceivedMessage struct {
Index string
Jid string
Owner bool
Participant string
Type string
}
func getReceivedMessage(msg map[string]string) ReceivedMessage {
owner, _ := strconv.ParseBool(msg["owner"])
// This field might not exist
participant, _ := msg["participant"]
return ReceivedMessage{
Index: msg["index"],
Jid: msg["jid"],
Owner: owner,
Participant: participant,
Type: msg["type"],
}
}
func ParseNodeMessage(msg binary.Node) interface{} {
switch msg.Description {
case "battery":
return getBatteryMessage(msg.Attributes)
case "user":
return getNewContact(msg.Attributes)
case "read":
return getReadMessage(msg.Attributes)
case "received":
return getReceivedMessage(msg.Attributes)
default:
//cannot match message
return &msg
}
return nil
}

View File

@ -10,10 +10,10 @@ import (
"net/http"
"strings"
"github.com/gorilla/websocket"
"github.com/Rhymen/go-whatsapp/binary"
"github.com/Rhymen/go-whatsapp/crypto/cbc"
"github.com/gorilla/websocket"
"github.com/pkg/errors"
)
func (wac *Conn) readPump() {
@ -42,12 +42,12 @@ func (wac *Conn) readPump() {
}
msg, err := ioutil.ReadAll(reader)
if err != nil {
wac.handle(errors.Wrap(err, "error reading message from Reader"))
wac.handle(fmt.Errorf("error reading message from Reader: %w", err))
continue
}
err = wac.processReadData(msgType, msg)
if err != nil {
wac.handle(errors.Wrap(err, "error processing data"))
wac.handle(fmt.Errorf("error processing data: %w", err))
}
case <-wac.ws.close:
return
@ -96,7 +96,7 @@ func (wac *Conn) processReadData(msgType int, msg []byte) error {
}
message, err := wac.decryptBinaryMessage([]byte(data[1]))
if err != nil {
return errors.Wrap(err, "error decoding binary")
return fmt.Errorf("error decoding binary: %w", err)
}
wac.dispatch(message)
} else { //RAW json status updates
@ -117,7 +117,9 @@ func (wac *Conn) decryptBinaryMessage(msg []byte) (*binary.Node, error) {
if response.Status == http.StatusNotFound {
return nil, ErrServerRespondedWith404
}
return nil, errors.New(fmt.Sprintf("server responded with %d", response.Status))
return nil, fmt.Errorf("server responded with %d", response.Status)
} else {
return nil, ErrInvalidServerResponse
}
return nil, ErrInvalidServerResponse
@ -131,13 +133,13 @@ func (wac *Conn) decryptBinaryMessage(msg []byte) (*binary.Node, error) {
// message decrypt
d, err := cbc.Decrypt(wac.session.EncKey, nil, msg[32:])
if err != nil {
return nil, errors.Wrap(err, "decrypting message with AES-CBC failed")
return nil, fmt.Errorf("decrypting message with AES-CBC failed: %w", err)
}
// message unmarshal
message, err := binary.Unmarshal(d)
if err != nil {
return nil, errors.Wrap(err, "could not decode binary")
return nil, fmt.Errorf("could not decode binary: %w", err)
}
return message, nil

View File

@ -18,7 +18,7 @@ import (
)
//represents the WhatsAppWeb client version
var waVersion = []int{2, 2039, 9}
var waVersion = []int{2, 2100, 6}
/*
Session contains session individual information. To be able to resume the connection without scanning the qr code
@ -141,7 +141,7 @@ func CheckCurrentServerVersion() ([]int, error) {
SetClientName sets the long and short client names that are sent to WhatsApp when logging in and displayed in the
WhatsApp Web device list. As the values are only sent when logging in, changing them after logging in is not possible.
*/
func (wac *Conn) SetClientName(long, short string, version string) error {
func (wac *Conn) SetClientName(long, short, version string) error {
if wac.session != nil && (wac.session.EncKey != nil || wac.session.MacKey != nil) {
return fmt.Errorf("cannot change client name after logging in")
}
@ -157,6 +157,28 @@ func (wac *Conn) SetClientVersion(major int, minor int, patch int) {
waVersion = []int{major, minor, patch}
}
func (wac *Conn) adminInitRequest(clientId string) (string, time.Duration, error) {
login := []interface{}{"admin", "init", waVersion, []string{wac.longClientName, wac.shortClientName, wac.clientVersion}, clientId, true}
loginChan, err := wac.writeJson(login)
if err != nil {
return "", 0, fmt.Errorf("error writing login: %v\n", err)
}
var r string
select {
case r = <-loginChan:
case <-time.After(wac.msgTimeout):
return "", 0, fmt.Errorf("login connection timed out")
}
var resp map[string]interface{}
if err = json.Unmarshal([]byte(r), &resp); err != nil {
return "", 0, fmt.Errorf("error decoding login resp: %v\n", err)
}
return resp["ref"].(string), time.Duration(resp["ttl"].(float64)) * time.Millisecond, nil
}
// GetClientVersion returns WhatsApp client version
func (wac *Conn) GetClientVersion() []int {
return waVersion
@ -186,6 +208,10 @@ github.com/Baozisoftware/qrcode-terminal-go Example login procedure:
fmt.Printf("login successful, session: %v\n", session)
*/
func (wac *Conn) Login(qrChan chan<- string) (Session, error) {
return wac.LoginWithRetry(qrChan, 0)
}
func (wac *Conn) LoginWithRetry(qrChan chan<- string, maxRetries int) (Session, error) {
session := Session{}
//Makes sure that only a single Login or Restore can happen at the same time
if !atomic.CompareAndSwapUint32(&wac.sessionLock, 0, 1) {
@ -213,30 +239,6 @@ func (wac *Conn) Login(qrChan chan<- string) (Session, error) {
}
session.ClientId = base64.StdEncoding.EncodeToString(clientId)
login := []interface{}{"admin", "init", waVersion, []string{wac.longClientName, wac.shortClientName, wac.clientVersion}, session.ClientId, true}
loginChan, err := wac.writeJson(login)
if err != nil {
return session, fmt.Errorf("error writing login: %v\n", err)
}
var r string
select {
case r = <-loginChan:
case <-time.After(wac.msgTimeout):
return session, fmt.Errorf("login connection timed out")
}
var resp map[string]interface{}
if err = json.Unmarshal([]byte(r), &resp); err != nil {
return session, fmt.Errorf("error decoding login resp: %v\n", err)
}
var ref string
if rref, ok := resp["ref"].(string); ok {
ref = rref
} else {
return session, fmt.Errorf("error decoding login resp: invalid resp['ref']\n")
}
priv, pub, err := curve25519.GenerateKey()
if err != nil {
@ -249,18 +251,35 @@ func (wac *Conn) Login(qrChan chan<- string) (Session, error) {
wac.listener.m["s1"] = s1
wac.listener.Unlock()
ref, ttl, err := wac.adminInitRequest(session.ClientId)
if err != nil {
return session, err
}
qrChan <- fmt.Sprintf("%v,%v,%v", ref, base64.StdEncoding.EncodeToString(pub[:]), session.ClientId)
wac.loginSessionLock.Lock()
defer wac.loginSessionLock.Unlock()
var resp2 []interface{}
select {
case r1 := <-s1:
wac.loginSessionLock.Lock()
defer wac.loginSessionLock.Unlock()
if err := json.Unmarshal([]byte(r1), &resp2); err != nil {
return session, fmt.Errorf("error decoding qr code resp: %v", err)
For:
for {
select {
case r1 := <-s1:
if err := json.Unmarshal([]byte(r1), &resp2); err != nil {
return session, fmt.Errorf("error decoding qr code resp: %v", err)
}
break For
case <-time.After(ttl):
maxRetries--
if maxRetries < 0 {
_, _ = wac.Disconnect()
return session, ErrLoginTimedOut
}
ref, ttl, err = wac.adminInitRequest(session.ClientId)
if err != nil {
return session, err
}
qrChan <- fmt.Sprintf("%v,%v,%v", ref, base64.StdEncoding.EncodeToString(pub[:]), session.ClientId)
}
case <-time.After(time.Duration(resp["ttl"].(float64)) * time.Millisecond):
return session, fmt.Errorf("qr code scan timed out")
}
info := resp2[1].(map[string]interface{})
@ -389,14 +408,12 @@ func (wac *Conn) Restore() error {
select {
case r := <-initChan:
var resp map[string]interface{}
resp := StatusResponse{RequestType: "init"}
if err = json.Unmarshal([]byte(r), &resp); err != nil {
return fmt.Errorf("error decoding login connResp: %v\n", err)
}
if int(resp["status"].(float64)) != 200 {
} else if resp.Status != 200 {
wac.timeTag = ""
return fmt.Errorf("init responded with %d", resp["status"])
return resp
}
case <-time.After(wac.msgTimeout):
wac.timeTag = ""
@ -416,12 +433,11 @@ func (wac *Conn) Restore() error {
//check for an error message
select {
case r := <-loginChan:
var resp map[string]interface{}
resp := StatusResponse{RequestType: "admin login"}
if err = json.Unmarshal([]byte(r), &resp); err != nil {
return fmt.Errorf("error decoding login connResp: %v\n", err)
}
if int(resp["status"].(float64)) != 200 {
return fmt.Errorf("admin login responded with %d", int(resp["status"].(float64)))
} else if resp.Status != 200 {
return fmt.Errorf("admin login errored: %w", wac.getAdminLoginResponseError(resp))
}
default:
// not even an error message assume timeout
@ -456,15 +472,13 @@ func (wac *Conn) Restore() error {
//check for login 200 --> login success
select {
case r := <-loginChan:
var resp map[string]interface{}
resp := StatusResponse{RequestType: "admin login"}
if err = json.Unmarshal([]byte(r), &resp); err != nil {
wac.timeTag = ""
return fmt.Errorf("error decoding login connResp: %v\n", err)
}
if int(resp["status"].(float64)) != 200 {
} else if resp.Status != 200 {
wac.timeTag = ""
return fmt.Errorf("admin login responded with %d", resp["status"])
return fmt.Errorf("admin login errored: %w", wac.getAdminLoginResponseError(resp))
}
case <-time.After(wac.msgTimeout):
wac.timeTag = ""
@ -484,6 +498,22 @@ func (wac *Conn) Restore() error {
return nil
}
func (wac *Conn) getAdminLoginResponseError(resp StatusResponse) error {
switch resp.Status {
case 400:
return ErrBadRequest
case 401:
return ErrUnpaired
case 403:
return fmt.Errorf("%w - tos: %d", ErrAccessDenied, resp.TermsOfService)
case 405:
return ErrLoggedIn
case 409:
return ErrReplaced
}
return fmt.Errorf("%d (unknown error)", status)
}
func (wac *Conn) resolveChallenge(challenge string) error {
decoded, err := base64.StdEncoding.DecodeString(challenge)
if err != nil {
@ -501,12 +531,11 @@ func (wac *Conn) resolveChallenge(challenge string) error {
select {
case r := <-challengeChan:
var resp map[string]interface{}
resp := StatusResponse{RequestType: "login challenge"}
if err := json.Unmarshal([]byte(r), &resp); err != nil {
return fmt.Errorf("error decoding login resp: %v\n", err)
}
if int(resp["status"].(float64)) != 200 {
return fmt.Errorf("challenge responded with %d\n", resp["status"])
} else if resp.Status != 200 {
return resp
}
case <-time.After(wac.msgTimeout):
return fmt.Errorf("connection timed out")

View File

@ -6,13 +6,12 @@ import (
"encoding/json"
"fmt"
"strconv"
"time"
"github.com/gorilla/websocket"
"github.com/Rhymen/go-whatsapp/binary"
"github.com/Rhymen/go-whatsapp/crypto/cbc"
"github.com/gorilla/websocket"
"github.com/pkg/errors"
)
//writeJson enqueues a json message into the writeChan
@ -54,7 +53,7 @@ func (wac *Conn) writeBinary(node binary.Node, metric metric, flag flag, message
data, err := wac.encryptBinaryMessage(node)
if err != nil {
return nil, errors.Wrap(err, "encryptBinaryMessage(node) failed")
return nil, fmt.Errorf("encryptBinaryMessage(node) failed: %w", err)
}
bytes := []byte(messageTag + ",")
@ -63,7 +62,7 @@ func (wac *Conn) writeBinary(node binary.Node, metric metric, flag flag, message
ch, err := wac.write(websocket.BinaryMessage, messageTag, bytes)
if err != nil {
return nil, errors.Wrap(err, "failed to write message")
return nil, fmt.Errorf("failed to write message: %w", err)
}
wac.msgCount++
@ -74,14 +73,14 @@ func (wac *Conn) sendKeepAlive() error {
bytes := []byte("?,,")
respChan, err := wac.write(websocket.TextMessage, "!", bytes)
if err != nil {
return errors.Wrap(err, "error sending keepAlive")
return fmt.Errorf("error sending keepAlive: %w", err)
}
select {
case resp := <-respChan:
msecs, err := strconv.ParseInt(resp, 10, 64)
if err != nil {
return errors.Wrap(err, "Error converting time string to uint")
return fmt.Errorf("Error converting time string to uint: %w", err)
}
wac.ServerLastSeen = time.Unix(msecs/1000, (msecs%1000)*int64(time.Millisecond))
@ -96,29 +95,30 @@ func (wac *Conn) sendKeepAlive() error {
When phone is unreachable, WhatsAppWeb sends ["admin","test"] time after time to try a successful contact.
Tested with Airplane mode and no connection at all.
*/
func (wac *Conn) sendAdminTest() (bool, error) {
func (wac *Conn) sendAdminTest() error {
data := []interface{}{"admin", "test"}
r, err := wac.writeJson(data)
if err != nil {
return false, errors.Wrap(err, "error sending admin test")
return fmt.Errorf("error sending admin test: %w", err)
}
var response []interface{}
var resp string
select {
case resp := <-r:
case resp = <-r:
if err := json.Unmarshal([]byte(resp), &response); err != nil {
return false, fmt.Errorf("error decoding response message: %v\n", err)
return fmt.Errorf("error decoding response message: %v\n", err)
}
case <-time.After(wac.msgTimeout):
return false, ErrConnectionTimeout
return ErrConnectionTimeout
}
if len(response) == 2 && response[0].(string) == "Pong" && response[1].(bool) == true {
return true, nil
return nil
} else {
return false, nil
return fmt.Errorf("unexpected ping response: %s", resp)
}
}
@ -145,7 +145,7 @@ func (wac *Conn) write(messageType int, answerMessageTag string, data []byte) (<
delete(wac.listener.m, answerMessageTag)
wac.listener.Unlock()
}
return nil, errors.Wrap(err, "error writing to websocket")
return nil, fmt.Errorf("error writing to websocket: %w", err)
}
return ch, nil
}
@ -153,12 +153,12 @@ func (wac *Conn) write(messageType int, answerMessageTag string, data []byte) (<
func (wac *Conn) encryptBinaryMessage(node binary.Node) (data []byte, err error) {
b, err := binary.Marshal(node)
if err != nil {
return nil, errors.Wrap(err, "binary node marshal failed")
return nil, fmt.Errorf("binary node marshal failed: %w", err)
}
cipher, err := cbc.Encrypt(wac.session.EncKey, nil, b)
if err != nil {
return nil, errors.Wrap(err, "encrypt failed")
return nil, fmt.Errorf("encrypt failed: %w", err)
}
h := hmac.New(sha256.New, wac.session.MacKey)

View File

@ -18,26 +18,20 @@ A command-line tool `qrcode` will be built into `$GOPATH/bin/`.
import qrcode "github.com/skip2/go-qrcode"
- **Create a PNG image:**
- **Create a 256x256 PNG image:**
var png []byte
png, err := qrcode.Encode("https://example.org", qrcode.Medium, 256)
- **Create a PNG image and write to a file:**
- **Create a 256x256 PNG image and write to a file:**
err := qrcode.WriteFile("https://example.org", qrcode.Medium, 256, "qr.png")
- **Create a PNG image with custom colors and write to file:**
- **Create a 256x256 PNG image with custom colors and write to file:**
err := qrcode.WriteColorFile("https://example.org", qrcode.Medium, 256, color.Black, color.White, "qr.png")
All examples use the qrcode.Medium error Recovery Level and create a fixed
256x256px size QR Code. The last function creates a white on black instead of black
on white QR Code.
The maximum capacity of a QR Code varies according to the content encoded and
the error recovery level. The maximum capacity is 2,953 bytes, 4,296
alphanumeric characters, 7,089 numeric digits, or a combination of these.
All examples use the qrcode.Medium error Recovery Level and create a fixed 256x256px size QR Code. The last function creates a white on black instead of black on white QR Code.
## Documentation
@ -56,10 +50,13 @@ qrcode -- QR Code encoder in Go
https://github.com/skip2/go-qrcode
Flags:
-d disable QR Code border
-i invert black and white
-o string
out PNG file prefix, empty for stdout
out PNG file prefix, empty for stdout
-s int
image size (pixel) (default 256)
image size (pixel) (default 256)
-t print as text-art on stdout
Usage:
1. Arguments except for flags are joined by " " and used to generate QR code.
@ -71,7 +68,16 @@ Usage:
2. Save to file if "display" not available:
qrcode "homepage: https://github.com/skip2/go-qrcode" > out.png
```
## Maximum capacity
The maximum capacity of a QR Code varies according to the content encoded and the error recovery level. The maximum capacity is 2,953 bytes, 4,296 alphanumeric characters, 7,089 numeric digits, or a combination of these.
## Borderless QR Codes
To aid QR Code reading software, QR codes have a built in whitespace border.
If you know what you're doing, and don't want a border, see https://gist.github.com/skip2/7e3d8a82f5317df9be437f8ec8ec0b7d for how to do it. It's still recommended you include a border manually.
## Links

View File

@ -172,7 +172,7 @@ func (d *dataEncoder) encode(data []byte) (*bitset.Bitset, error) {
}
// Classify data into unoptimised segments.
d.classifyDataModes()
highestRequiredMode := d.classifyDataModes()
// Optimise segments.
err := d.optimiseDataModes()
@ -180,6 +180,25 @@ func (d *dataEncoder) encode(data []byte) (*bitset.Bitset, error) {
return nil, err
}
// Check if a single byte encoded segment would be more efficient.
optimizedLength := 0
for _, s := range d.optimised {
length, err := d.encodedLength(s.dataMode, len(s.data))
if err != nil {
return nil, err
}
optimizedLength += length
}
singleByteSegmentLength, err := d.encodedLength(highestRequiredMode, len(d.data))
if err != nil {
return nil, err
}
if singleByteSegmentLength <= optimizedLength {
d.optimised = []segment{segment{dataMode: highestRequiredMode, data: d.data}}
}
// Encode data.
encoded := bitset.New()
for _, s := range d.optimised {
@ -192,9 +211,15 @@ func (d *dataEncoder) encode(data []byte) (*bitset.Bitset, error) {
// classifyDataModes classifies the raw data into unoptimised segments.
// e.g. "123ZZ#!#!" =>
// [numeric, 3, "123"] [alphanumeric, 2, "ZZ"] [byte, 4, "#!#!"].
func (d *dataEncoder) classifyDataModes() {
//
// Returns the highest data mode needed to encode the data. e.g. for a mixed
// numeric/alphanumeric input, the highest is alphanumeric.
//
// dataModeNone < dataModeNumeric < dataModeAlphanumeric < dataModeByte
func (d *dataEncoder) classifyDataModes() dataMode {
var start int
mode := dataModeNone
highestRequiredMode := mode
for i, v := range d.data {
newMode := dataModeNone
@ -217,9 +242,15 @@ func (d *dataEncoder) classifyDataModes() {
mode = newMode
}
if newMode > highestRequiredMode {
highestRequiredMode = newMode
}
}
d.actual = append(d.actual, segment{dataMode: mode, data: d.data[start:len(d.data)]})
return highestRequiredMode
}
// optimiseDataModes optimises the list of segments to reduce the overall output

3
vendor/github.com/skip2/go-qrcode/go.mod generated vendored Normal file
View File

@ -0,0 +1,3 @@
module github.com/skip2/go-qrcode
go 1.13

View File

@ -51,6 +51,7 @@ package qrcode
import (
"bytes"
"errors"
"fmt"
"image"
"image/color"
"image/png"
@ -135,6 +136,9 @@ type QRCode struct {
ForegroundColor color.Color
BackgroundColor color.Color
// Disable the QR Code border.
DisableBorder bool
encoder *dataEncoder
version qrCodeVersion
@ -193,12 +197,16 @@ func New(content string, level RecoveryLevel) (*QRCode, error) {
version: *chosenVersion,
}
q.encode(chosenVersion.numTerminatorBitsRequired(encoded.Len()))
return q, nil
}
func newWithForcedVersion(content string, version int, level RecoveryLevel) (*QRCode, error) {
// NewWithForcedVersion constructs a QRCode of a specific version.
//
// var q *qrcode.QRCode
// q, err := qrcode.NewWithForcedVersion("my content", 25, qrcode.Medium)
//
// An error occurs in case of invalid version.
func NewWithForcedVersion(content string, version int, level RecoveryLevel) (*QRCode, error) {
var encoder *dataEncoder
switch {
@ -209,7 +217,7 @@ func newWithForcedVersion(content string, version int, level RecoveryLevel) (*QR
case version >= 27 && version <= 40:
encoder = newDataEncoder(dataEncoderType27To40)
default:
log.Fatalf("Invalid version %d (expected 1-40 inclusive)", version)
return nil, fmt.Errorf("Invalid version %d (expected 1-40 inclusive)", version)
}
var encoded *bitset.Bitset
@ -225,6 +233,13 @@ func newWithForcedVersion(content string, version int, level RecoveryLevel) (*QR
return nil, errors.New("cannot find QR Code version")
}
if encoded.Len() > chosenVersion.numDataBits() {
return nil, fmt.Errorf("Cannot encode QR code: content too large for fixed size QR Code version %d (encoded length is %d bits, maximum length is %d bits)",
version,
encoded.Len(),
chosenVersion.numDataBits())
}
q := &QRCode{
Content: content,
@ -239,8 +254,6 @@ func newWithForcedVersion(content string, version int, level RecoveryLevel) (*QR
version: *chosenVersion,
}
q.encode(chosenVersion.numTerminatorBitsRequired(encoded.Len()))
return q, nil
}
@ -251,6 +264,9 @@ func newWithForcedVersion(content string, version int, level RecoveryLevel) (*QR
// The bitmap includes the required "quiet zone" around the QR Code to aid
// decoding.
func (q *QRCode) Bitmap() [][]bool {
// Build QR code.
q.encode()
return q.symbol.bitmap()
}
@ -268,6 +284,9 @@ func (q *QRCode) Bitmap() [][]bool {
// negative number to increase the scale of the image. e.g. a size of -5 causes
// each module (QR Code "pixel") to be 5px in size.
func (q *QRCode) Image(size int) image.Image {
// Build QR code.
q.encode()
// Minimum pixels (both width and height) required.
realSize := q.symbol.size
@ -282,12 +301,7 @@ func (q *QRCode) Image(size int) image.Image {
size = realSize
}
// Size of each module drawn.
pixelsPerModule := size / realSize
// Center the symbol within the image.
offset := (size - realSize*pixelsPerModule) / 2
// Output image.
rect := image.Rectangle{Min: image.Point{0, 0}, Max: image.Point{size, size}}
// Saves a few bytes to have them in this order
@ -295,18 +309,21 @@ func (q *QRCode) Image(size int) image.Image {
img := image.NewPaletted(rect, p)
fgClr := uint8(img.Palette.Index(q.ForegroundColor))
// QR code bitmap.
bitmap := q.symbol.bitmap()
for y, row := range bitmap {
for x, v := range row {
// Map each image pixel to the nearest QR code module.
modulesPerPixel := float64(realSize) / float64(size)
for y := 0; y < size; y++ {
y2 := int(float64(y) * modulesPerPixel)
for x := 0; x < size; x++ {
x2 := int(float64(x) * modulesPerPixel)
v := bitmap[y2][x2]
if v {
startX := x*pixelsPerModule + offset
startY := y*pixelsPerModule + offset
for i := startX; i < startX+pixelsPerModule; i++ {
for j := startY; j < startY+pixelsPerModule; j++ {
pos := img.PixOffset(i, j)
img.Pix[pos] = fgClr
}
}
pos := img.PixOffset(x, y)
img.Pix[pos] = fgClr
}
}
}
@ -371,7 +388,9 @@ func (q *QRCode) WriteFile(size int, filename string) error {
// encode completes the steps required to encode the QR Code. These include
// adding the terminator bits and padding, splitting the data into blocks and
// applying the error correction, and selecting the best data mask.
func (q *QRCode) encode(numTerminatorBits int) {
func (q *QRCode) encode() {
numTerminatorBits := q.version.numTerminatorBitsRequired(q.data.Len())
q.addTerminatorBits(numTerminatorBits)
q.addPadding()
@ -384,7 +403,7 @@ func (q *QRCode) encode(numTerminatorBits int) {
var s *symbol
var err error
s, err = buildRegularSymbol(q.version, mask, encoded)
s, err = buildRegularSymbol(q.version, mask, encoded, !q.DisableBorder)
if err != nil {
log.Panic(err.Error())

View File

@ -105,13 +105,19 @@ var (
)
func buildRegularSymbol(version qrCodeVersion, mask int,
data *bitset.Bitset) (*symbol, error) {
data *bitset.Bitset, includeQuietZone bool) (*symbol, error) {
quietZoneSize := 0
if includeQuietZone {
quietZoneSize = version.quietZoneSize()
}
m := &regularSymbol{
version: version,
mask: mask,
data: data,
symbol: newSymbol(version.symbolSize(), version.quietZoneSize()),
symbol: newSymbol(version.symbolSize(), quietZoneSize),
size: version.symbolSize(),
}

6
vendor/modules.txt vendored
View File

@ -18,7 +18,7 @@ github.com/Philipp15b/go-steam/protocol/steamlang
github.com/Philipp15b/go-steam/rwu
github.com/Philipp15b/go-steam/socialcache
github.com/Philipp15b/go-steam/steamid
# github.com/Rhymen/go-whatsapp v0.1.2-0.20201226125722-8029c28f5c5a
# github.com/Rhymen/go-whatsapp v0.1.2-0.20201226125722-8029c28f5c5a => github.com/tulir/go-whatsapp v0.3.16
## explicit
github.com/Rhymen/go-whatsapp
github.com/Rhymen/go-whatsapp/binary
@ -231,7 +231,8 @@ github.com/shazow/ssh-chat/sshd/terminal
# github.com/sirupsen/logrus v1.7.0
## explicit
github.com/sirupsen/logrus
# github.com/skip2/go-qrcode v0.0.0-20190110000554-dc11ecdae0a9
# github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
## explicit
github.com/skip2/go-qrcode
github.com/skip2/go-qrcode/bitset
github.com/skip2/go-qrcode/reedsolomon
@ -431,3 +432,4 @@ layeh.com/gumble/gumble
layeh.com/gumble/gumble/MumbleProto
layeh.com/gumble/gumble/varint
layeh.com/gumble/gumbleutil
# github.com/Rhymen/go-whatsapp => github.com/tulir/go-whatsapp v0.3.16