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/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca
github.com/shazow/ssh-chat v1.10.1 github.com/shazow/ssh-chat v1.10.1
github.com/sirupsen/logrus v1.7.0 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/slack-go/slack v0.7.4
github.com/spf13/afero v1.3.4 // indirect github.com/spf13/afero v1.3.4 // indirect
github.com/spf13/cast v1.3.1 // indirect github.com/spf13/cast v1.3.1 // indirect
@ -58,4 +59,6 @@ require (
layeh.com/gumble v0.0.0-20200818122324-146f9205029b 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 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/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.4.1/go.mod h1:T9ezsOHcCrDCgA8aF1Cqr3sSYbO/xgdy8/R/XiIMAhA=
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= 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.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo=
github.com/RoaringBitmap/roaring v0.5.1/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= 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.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= 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.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.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 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/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 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.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 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 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= 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/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/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.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.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.4/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= 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-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.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.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.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.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= 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.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 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-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
github.com/skip2/go-qrcode v0.0.0-20190110000554-dc11ecdae0a9/go.mod h1:PLPIyL7ikehBD1OAjmKKiOEhbvWyHGaNDjquXMcYABo= 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 h1:Z+7CmUDV+ym4lYLA4NNLFIpr3+nDgViHrx8xsuXgrYs=
github.com/slack-go/slack v0.7.4/go.mod h1:FGqNzJBmxIsZURAxh2a8D21AnOVvvXZvGligs4npPUM= 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= 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/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-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/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/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/tylerb/graceful v1.2.15/go.mod h1:LPYTbOYmUTdabwRt0TGhLllQ0MUNbs0Y5q1WXJOI9II=
github.com/uber/jaeger-client-go v2.25.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= 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-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-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-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-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-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 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-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-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-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-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 h1:DZhuSZLsGlFL4CmhA8BcRA0mnthyA/nZ00AqCUo7vHg=
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 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 { message AdReplyInfo {
optional string advertiserName = 1; optional string advertiserName = 1;
enum AD_REPLY_INFO_MEDIATYPE { enum AdReplyInfoMediaType {
NONE = 0; NONE = 0;
IMAGE = 1; IMAGE = 1;
VIDEO = 2; VIDEO = 2;
} }
optional AD_REPLY_INFO_MEDIATYPE mediaType = 2; optional AdReplyInfoMediaType mediaType = 2;
optional bytes jpegThumbnail = 16; optional bytes jpegThumbnail = 16;
optional string caption = 17; 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 { message ContextInfo {
optional string stanzaId = 1; optional string stanzaId = 1;
optional string participant = 2; optional string participant = 2;
@ -96,6 +124,8 @@ message ContextInfo {
optional MessageKey placeholderKey = 24; optional MessageKey placeholderKey = 24;
optional uint32 expiration = 25; optional uint32 expiration = 25;
optional int64 ephemeralSettingTimestamp = 26; optional int64 ephemeralSettingTimestamp = 26;
optional bytes ephemeralSharedSecret = 27;
optional ExternalAdReplyInfo externalAdReply = 28;
} }
message SenderKeyDistributionMessage { message SenderKeyDistributionMessage {
@ -125,6 +155,24 @@ message ImageMessage {
repeated uint32 scanLengths = 22; repeated uint32 scanLengths = 22;
optional bytes midQualityFileSha256 = 23; optional bytes midQualityFileSha256 = 23;
optional bytes midQualityFileEncSha256 = 24; 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 { message ContactMessage {
@ -156,7 +204,7 @@ message ExtendedTextMessage {
optional string title = 6; optional string title = 6;
optional fixed32 textArgb = 7; optional fixed32 textArgb = 7;
optional fixed32 backgroundArgb = 8; optional fixed32 backgroundArgb = 8;
enum EXTENDED_TEXT_MESSAGE_FONTTYPE { enum ExtendedTextMessageFontType {
SANS_SERIF = 0; SANS_SERIF = 0;
SERIF = 1; SERIF = 1;
NORICAN_REGULAR = 2; NORICAN_REGULAR = 2;
@ -164,12 +212,12 @@ message ExtendedTextMessage {
BEBASNEUE_REGULAR = 4; BEBASNEUE_REGULAR = 4;
OSWALD_HEAVY = 5; OSWALD_HEAVY = 5;
} }
optional EXTENDED_TEXT_MESSAGE_FONTTYPE font = 9; optional ExtendedTextMessageFontType font = 9;
enum EXTENDED_TEXT_MESSAGE_PREVIEWTYPE { enum ExtendedTextMessagePreviewType {
NONE = 0; NONE = 0;
VIDEO = 1; VIDEO = 1;
} }
optional EXTENDED_TEXT_MESSAGE_PREVIEWTYPE previewType = 10; optional ExtendedTextMessagePreviewType previewType = 10;
optional bytes jpegThumbnail = 16; optional bytes jpegThumbnail = 16;
optional ContextInfo contextInfo = 17; optional ContextInfo contextInfo = 17;
optional bool doNotPlayInline = 18; optional bool doNotPlayInline = 18;
@ -187,8 +235,14 @@ message DocumentMessage {
optional bytes fileEncSha256 = 9; optional bytes fileEncSha256 = 9;
optional string directPath = 10; optional string directPath = 10;
optional int64 mediaKeyTimestamp = 11; 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 bytes jpegThumbnail = 16;
optional ContextInfo contextInfo = 17; optional ContextInfo contextInfo = 17;
optional uint32 thumbnailHeight = 18;
optional uint32 thumbnailWidth = 19;
} }
message AudioMessage { message AudioMessage {
@ -224,12 +278,13 @@ message VideoMessage {
optional bytes jpegThumbnail = 16; optional bytes jpegThumbnail = 16;
optional ContextInfo contextInfo = 17; optional ContextInfo contextInfo = 17;
optional bytes streamingSidecar = 18; optional bytes streamingSidecar = 18;
enum VIDEO_MESSAGE_ATTRIBUTION { enum VideoMessageAttribution {
NONE = 0; NONE = 0;
GIPHY = 1; GIPHY = 1;
TENOR = 2; TENOR = 2;
} }
optional VIDEO_MESSAGE_ATTRIBUTION gifAttribution = 19; optional VideoMessageAttribution gifAttribution = 19;
optional bool viewOnce = 20;
} }
message Call { message Call {
@ -243,16 +298,23 @@ message Chat {
message ProtocolMessage { message ProtocolMessage {
optional MessageKey key = 1; optional MessageKey key = 1;
enum PROTOCOL_MESSAGE_TYPE { enum ProtocolMessageType {
REVOKE = 0; REVOKE = 0;
EPHEMERAL_SETTING = 3; EPHEMERAL_SETTING = 3;
EPHEMERAL_SYNC_RESPONSE = 4; EPHEMERAL_SYNC_RESPONSE = 4;
HISTORY_SYNC_NOTIFICATION = 5; 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 uint32 ephemeralExpiration = 4;
optional int64 ephemeralSettingTimestamp = 5; optional int64 ephemeralSettingTimestamp = 5;
optional HistorySyncNotification historySyncNotification = 6; optional HistorySyncNotification historySyncNotification = 6;
optional AppStateSyncKeyShare appStateSyncKeyShare = 7;
optional AppStateSyncKeyRequest appStateSyncKeyRequest = 8;
optional InitialSecurityNotificationSettingSync initialSecurityNotificationSettingSync = 9;
} }
message HistorySyncNotification { message HistorySyncNotification {
@ -261,14 +323,49 @@ message HistorySyncNotification {
optional bytes mediaKey = 3; optional bytes mediaKey = 3;
optional bytes fileEncSha256 = 4; optional bytes fileEncSha256 = 4;
optional string directPath = 5; optional string directPath = 5;
enum HISTORY_SYNC_NOTIFICATION_HISTORYSYNCTYPE { enum HistorySyncNotificationHistorySyncType {
INITIAL_BOOTSTRAP = 0; INITIAL_BOOTSTRAP = 0;
INITIAL_STATUS_V3 = 1; INITIAL_STATUS_V3 = 1;
FULL = 2; FULL = 2;
RECENT = 3; RECENT = 3;
PUSH_NAME = 4;
} }
optional HISTORY_SYNC_NOTIFICATION_HISTORYSYNCTYPE syncType = 6; optional HistorySyncNotificationHistorySyncType syncType = 6;
optional uint32 chunkOrder = 7; 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 { message ContactsArrayMessage {
@ -283,7 +380,7 @@ message HSMCurrency {
} }
message HSMDateTimeComponent { message HSMDateTimeComponent {
enum HSM_DATE_TIME_COMPONENT_DAYOFWEEKTYPE { enum HSMDateTimeComponentDayOfWeekType {
MONDAY = 1; MONDAY = 1;
TUESDAY = 2; TUESDAY = 2;
WEDNESDAY = 3; WEDNESDAY = 3;
@ -292,17 +389,17 @@ message HSMDateTimeComponent {
SATURDAY = 6; SATURDAY = 6;
SUNDAY = 7; SUNDAY = 7;
} }
optional HSM_DATE_TIME_COMPONENT_DAYOFWEEKTYPE dayOfWeek = 1; optional HSMDateTimeComponentDayOfWeekType dayOfWeek = 1;
optional uint32 year = 2; optional uint32 year = 2;
optional uint32 month = 3; optional uint32 month = 3;
optional uint32 dayOfMonth = 4; optional uint32 dayOfMonth = 4;
optional uint32 hour = 5; optional uint32 hour = 5;
optional uint32 minute = 6; optional uint32 minute = 6;
enum HSM_DATE_TIME_COMPONENT_CALENDARTYPE { enum HSMDateTimeComponentCalendarType {
GREGORIAN = 1; GREGORIAN = 1;
SOLAR_HIJRI = 2; SOLAR_HIJRI = 2;
} }
optional HSM_DATE_TIME_COMPONENT_CALENDARTYPE calendar = 7; optional HSMDateTimeComponentCalendarType calendar = 7;
} }
message HSMDateTimeUnixEpoch { message HSMDateTimeUnixEpoch {
@ -347,6 +444,13 @@ message RequestPaymentMessage {
optional uint64 amount1000 = 2; optional uint64 amount1000 = 2;
optional string requestFrom = 3; optional string requestFrom = 3;
optional int64 expiryTimestamp = 5; optional int64 expiryTimestamp = 5;
optional PaymentMoney amount = 6;
}
message PaymentMoney {
optional int64 value = 1;
optional uint32 offset = 2;
optional string currencyCode = 3;
} }
message DeclinePaymentRequestMessage { message DeclinePaymentRequestMessage {
@ -457,6 +561,64 @@ message ProductMessage {
optional ContextInfo contextInfo = 17; 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 { message GroupInviteMessage {
optional string groupJid = 1; optional string groupJid = 1;
optional string inviteCode = 2; optional string inviteCode = 2;
@ -467,13 +629,50 @@ message GroupInviteMessage {
optional ContextInfo contextInfo = 7; optional ContextInfo contextInfo = 7;
} }
message EphemeralSetting {
optional string chatJid = 1;
optional uint32 ephemeralExpiration = 2;
optional int64 ephemeralSettingTimestamp = 3;
}
message DeviceSentMessage { message DeviceSentMessage {
optional string destinationJid = 1; optional string destinationJid = 1;
optional Message message = 2; optional Message message = 2;
optional string phash = 3;
repeated EphemeralSetting broadcastEphemeralSettings = 4;
} }
message DeviceSyncMessage { message FutureProofMessage {
optional bytes serializedXmlBytes = 1; 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 { message Message {
@ -503,7 +702,15 @@ message Message {
optional TemplateButtonReplyMessage templateButtonReplyMessage = 29; optional TemplateButtonReplyMessage templateButtonReplyMessage = 29;
optional ProductMessage productMessage = 30; optional ProductMessage productMessage = 30;
optional DeviceSentMessage deviceSentMessage = 31; 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 { message MessageKey {
@ -514,51 +721,52 @@ message MessageKey {
} }
message WebFeatures { message WebFeatures {
enum WEB_FEATURES_FLAG { enum WebFeaturesFlag {
NOT_STARTED = 0; NOT_STARTED = 0;
FORCE_UPGRADE = 1; FORCE_UPGRADE = 1;
DEVELOPMENT = 2; DEVELOPMENT = 2;
PRODUCTION = 3; PRODUCTION = 3;
} }
optional WEB_FEATURES_FLAG labelsDisplay = 1; optional WebFeaturesFlag labelsDisplay = 1;
optional WEB_FEATURES_FLAG voipIndividualOutgoing = 2; optional WebFeaturesFlag voipIndividualOutgoing = 2;
optional WEB_FEATURES_FLAG groupsV3 = 3; optional WebFeaturesFlag groupsV3 = 3;
optional WEB_FEATURES_FLAG groupsV3Create = 4; optional WebFeaturesFlag groupsV3Create = 4;
optional WEB_FEATURES_FLAG changeNumberV2 = 5; optional WebFeaturesFlag changeNumberV2 = 5;
optional WEB_FEATURES_FLAG queryStatusV3Thumbnail = 6; optional WebFeaturesFlag queryStatusV3Thumbnail = 6;
optional WEB_FEATURES_FLAG liveLocations = 7; optional WebFeaturesFlag liveLocations = 7;
optional WEB_FEATURES_FLAG queryVname = 8; optional WebFeaturesFlag queryVname = 8;
optional WEB_FEATURES_FLAG voipIndividualIncoming = 9; optional WebFeaturesFlag voipIndividualIncoming = 9;
optional WEB_FEATURES_FLAG quickRepliesQuery = 10; optional WebFeaturesFlag quickRepliesQuery = 10;
optional WEB_FEATURES_FLAG payments = 11; optional WebFeaturesFlag payments = 11;
optional WEB_FEATURES_FLAG stickerPackQuery = 12; optional WebFeaturesFlag stickerPackQuery = 12;
optional WEB_FEATURES_FLAG liveLocationsFinal = 13; optional WebFeaturesFlag liveLocationsFinal = 13;
optional WEB_FEATURES_FLAG labelsEdit = 14; optional WebFeaturesFlag labelsEdit = 14;
optional WEB_FEATURES_FLAG mediaUpload = 15; optional WebFeaturesFlag mediaUpload = 15;
optional WEB_FEATURES_FLAG mediaUploadRichQuickReplies = 18; optional WebFeaturesFlag mediaUploadRichQuickReplies = 18;
optional WEB_FEATURES_FLAG vnameV2 = 19; optional WebFeaturesFlag vnameV2 = 19;
optional WEB_FEATURES_FLAG videoPlaybackUrl = 20; optional WebFeaturesFlag videoPlaybackUrl = 20;
optional WEB_FEATURES_FLAG statusRanking = 21; optional WebFeaturesFlag statusRanking = 21;
optional WEB_FEATURES_FLAG voipIndividualVideo = 22; optional WebFeaturesFlag voipIndividualVideo = 22;
optional WEB_FEATURES_FLAG thirdPartyStickers = 23; optional WebFeaturesFlag thirdPartyStickers = 23;
optional WEB_FEATURES_FLAG frequentlyForwardedSetting = 24; optional WebFeaturesFlag frequentlyForwardedSetting = 24;
optional WEB_FEATURES_FLAG groupsV4JoinPermission = 25; optional WebFeaturesFlag groupsV4JoinPermission = 25;
optional WEB_FEATURES_FLAG recentStickers = 26; optional WebFeaturesFlag recentStickers = 26;
optional WEB_FEATURES_FLAG catalog = 27; optional WebFeaturesFlag catalog = 27;
optional WEB_FEATURES_FLAG starredStickers = 28; optional WebFeaturesFlag starredStickers = 28;
optional WEB_FEATURES_FLAG voipGroupCall = 29; optional WebFeaturesFlag voipGroupCall = 29;
optional WEB_FEATURES_FLAG templateMessage = 30; optional WebFeaturesFlag templateMessage = 30;
optional WEB_FEATURES_FLAG templateMessageInteractivity = 31; optional WebFeaturesFlag templateMessageInteractivity = 31;
optional WEB_FEATURES_FLAG ephemeralMessages = 32; optional WebFeaturesFlag ephemeralMessages = 32;
optional WEB_FEATURES_FLAG e2ENotificationSync = 33; optional WebFeaturesFlag e2ENotificationSync = 33;
optional WEB_FEATURES_FLAG recentStickersV2 = 34; optional WebFeaturesFlag recentStickersV2 = 34;
} optional WebFeaturesFlag syncdRelease1 = 35;
optional WebFeaturesFlag recentStickersV3 = 36;
message TabletNotificationsInfo { optional WebFeaturesFlag userNotice = 37;
optional uint64 timestamp = 2; optional WebFeaturesFlag syncdRelease11 = 38;
optional uint32 unreadChats = 3; optional WebFeaturesFlag support = 39;
optional uint32 notifyMessageCount = 4; optional WebFeaturesFlag groupUiiCleanup = 40;
repeated NotificationMessageInfo notifyMessage = 5; optional WebFeaturesFlag groupDogfoodingInternalOnly = 41;
optional WebFeaturesFlag settingsSync = 42;
} }
message NotificationMessageInfo { message NotificationMessageInfo {
@ -576,14 +784,14 @@ message WebNotificationsInfo {
} }
message PaymentInfo { message PaymentInfo {
enum PAYMENT_INFO_CURRENCY { enum PaymentInfoCurrency {
UNKNOWN_CURRENCY = 0; UNKNOWN_CURRENCY = 0;
INR = 1; INR = 1;
} }
optional PAYMENT_INFO_CURRENCY currencyDeprecated = 1; optional PaymentInfoCurrency currencyDeprecated = 1;
optional uint64 amount1000 = 2; optional uint64 amount1000 = 2;
optional string receiverJid = 3; optional string receiverJid = 3;
enum PAYMENT_INFO_STATUS { enum PaymentInfoStatus {
UNKNOWN_STATUS = 0; UNKNOWN_STATUS = 0;
PROCESSING = 1; PROCESSING = 1;
SENT = 2; SENT = 2;
@ -597,13 +805,13 @@ message PaymentInfo {
WAITING_FOR_PAYER = 10; WAITING_FOR_PAYER = 10;
WAITING = 11; WAITING = 11;
} }
optional PAYMENT_INFO_STATUS status = 4; optional PaymentInfoStatus status = 4;
optional uint64 transactionTimestamp = 5; optional uint64 transactionTimestamp = 5;
optional MessageKey requestMessageKey = 6; optional MessageKey requestMessageKey = 6;
optional uint64 expiryTimestamp = 7; optional uint64 expiryTimestamp = 7;
optional bool futureproofed = 8; optional bool futureproofed = 8;
optional string currency = 9; optional string currency = 9;
enum PAYMENT_INFO_TXNSTATUS { enum PaymentInfoTxnStatus {
UNKNOWN = 0; UNKNOWN = 0;
PENDING_SETUP = 1; PENDING_SETUP = 1;
PENDING_RECEIVER_SETUP = 2; PENDING_RECEIVER_SETUP = 2;
@ -633,14 +841,14 @@ message PaymentInfo {
COLLECT_CANCELED = 26; COLLECT_CANCELED = 26;
COLLECT_CANCELLING = 27; COLLECT_CANCELLING = 27;
} }
optional PAYMENT_INFO_TXNSTATUS txnStatus = 10; optional PaymentInfoTxnStatus txnStatus = 10;
} }
message WebMessageInfo { message WebMessageInfo {
required MessageKey key = 1; required MessageKey key = 1;
optional Message message = 2; optional Message message = 2;
optional uint64 messageTimestamp = 3; optional uint64 messageTimestamp = 3;
enum WEB_MESSAGE_INFO_STATUS { enum WebMessageInfoStatus {
ERROR = 0; ERROR = 0;
PENDING = 1; PENDING = 1;
SERVER_ACK = 2; SERVER_ACK = 2;
@ -648,7 +856,7 @@ message WebMessageInfo {
READ = 4; READ = 4;
PLAYED = 5; PLAYED = 5;
} }
optional WEB_MESSAGE_INFO_STATUS status = 4; optional WebMessageInfoStatus status = 4;
optional string participant = 5; optional string participant = 5;
optional bool ignore = 16; optional bool ignore = 16;
optional bool starred = 17; optional bool starred = 17;
@ -658,7 +866,7 @@ message WebMessageInfo {
optional bool multicast = 21; optional bool multicast = 21;
optional bool urlText = 22; optional bool urlText = 22;
optional bool urlNumber = 23; optional bool urlNumber = 23;
enum WEB_MESSAGE_INFO_STUBTYPE { enum WebMessageInfoStubType {
UNKNOWN = 0; UNKNOWN = 0;
REVOKE = 1; REVOKE = 1;
CIPHERTEXT = 2; CIPHERTEXT = 2;
@ -732,8 +940,54 @@ message WebMessageInfo {
GROUP_V4_ADD_INVITE_SENT = 70; GROUP_V4_ADD_INVITE_SENT = 70;
GROUP_PARTICIPANT_ADD_REQUEST_JOIN = 71; GROUP_PARTICIPANT_ADD_REQUEST_JOIN = 71;
CHANGE_EPHEMERAL_SETTING = 72; 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; optional bool clearMedia = 25;
repeated string messageStubParameters = 26; repeated string messageStubParameters = 26;
optional uint32 duration = 27; optional uint32 duration = 27;
@ -743,5 +997,15 @@ message WebMessageInfo {
optional PaymentInfo quotedPaymentInfo = 31; optional PaymentInfo quotedPaymentInfo = 31;
optional uint64 ephemeralStartTimestamp = 32; optional uint64 ephemeralStartTimestamp = 32;
optional uint32 ephemeralDuration = 33; 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 package whatsapp
import ( import (
"fmt"
"math/rand" "math/rand"
"net/http" "net/http"
"net/url" "net/url"
@ -9,7 +10,6 @@ import (
"time" "time"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
"github.com/pkg/errors"
) )
type metric byte type metric byte
@ -195,7 +195,7 @@ func (wac *Conn) connect() (err error) {
headers := http.Header{"Origin": []string{"https://web.whatsapp.com"}} headers := http.Header{"Origin": []string{"https://web.whatsapp.com"}}
wsConn, _, err := dialer.Dial("wss://web.whatsapp.com/ws", headers) wsConn, _, err := dialer.Dial("wss://web.whatsapp.com/ws", headers)
if err != nil { 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 { wsConn.SetCloseHandler(func(code int, text string) error {
@ -221,7 +221,7 @@ func (wac *Conn) connect() (err error) {
wac.wg = &sync.WaitGroup{} wac.wg = &sync.WaitGroup{}
wac.wg.Add(2) wac.wg.Add(2)
go wac.readPump() go wac.readPump()
go wac.keepAlive(20000, 60000) go wac.keepAlive(20000, 55000)
wac.loggedIn = false wac.loggedIn = false
return nil return nil
@ -237,7 +237,10 @@ func (wac *Conn) Disconnect() (Session, error) {
close(wac.ws.close) //signal close close(wac.ws.close) //signal close
wac.wg.Wait() //wait for 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 wac.ws = nil
if wac.session == nil { if wac.session == nil {
@ -246,17 +249,20 @@ func (wac *Conn) Disconnect() (Session, error) {
return *wac.session, err 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 { if !wac.connected {
return false, ErrNotConnected return ErrNotConnected
} }
if !wac.loggedIn { if !wac.loggedIn {
return false, ErrInvalidSession return ErrInvalidSession
} }
result, err := wac.sendAdminTest() return wac.sendAdminTest()
return result, err
} }
func (wac *Conn) keepAlive(minIntervalMs int, maxIntervalMs int) { func (wac *Conn) keepAlive(minIntervalMs int, maxIntervalMs int) {
@ -265,7 +271,7 @@ func (wac *Conn) keepAlive(minIntervalMs int, maxIntervalMs int) {
for { for {
err := wac.sendKeepAlive() err := wac.sendKeepAlive()
if err != nil { if err != nil {
wac.handle(errors.Wrap(err, "keepAlive failed")) wac.handle(fmt.Errorf("keepAlive failed: %w", err))
//TODO: Consequences? //TODO: Consequences?
} }
interval := rand.Intn(maxIntervalMs-minIntervalMs) + minIntervalMs 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) 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) return wac.query("message", jid, "", "before", "true", "", count, 0)
} }
func (wac *Conn) LoadMessagesBefore(jid, messageId string, count int) (*binary.Node, error) { func (wac *Conn) LoadMessagesBefore(jid, messageId string, fromMe bool, count int) (*binary.Node, error) {
return wac.query("message", jid, messageId, "before", "true", "", count, 0) return wac.query("message", jid, messageId, "before", strconv.FormatBool(fromMe), "", count, 0)
} }
func (wac *Conn) LoadMessagesAfter(jid, messageId string, count int) (*binary.Node, error) { func (wac *Conn) LoadMessagesAfter(jid, messageId string, fromMe bool, count int) (*binary.Node, error) {
return wac.query("message", jid, messageId, "after", "true", "", count, 0) return wac.query("message", jid, messageId, "after", strconv.FormatBool(fromMe), "", count, 0)
} }
func (wac *Conn) LoadMediaInfo(jid, messageId, owner string) (*binary.Node, error) { func (wac *Conn) LoadMediaInfo(jid, messageId string, fromMe bool) (*binary.Node, error) {
return wac.query("media", jid, messageId, "", owner, "", 0, 0) return wac.query("media", jid, messageId, "", strconv.FormatBool(fromMe), "", 0, 0)
} }
func (wac *Conn) Presence(jid string, presence Presence) (<-chan string, error) { 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) { 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) { 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) { 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 return nil, err
} }
msg, err := wac.decryptBinaryMessage([]byte(<-ch)) select {
case response := <-ch:
msg, err := wac.decryptBinaryMessage([]byte(response))
if err != nil { if err != nil {
return nil, err return nil, err
} }
//TODO: use parseProtoMessage //TODO: use parseProtoMessage
return msg, nil 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) { func (wac *Conn) setGroup(t, jid, subject string, participants []string) (<-chan string, error) {

View File

@ -1,9 +1,9 @@
package whatsapp package whatsapp
import ( import (
"encoding/json"
"errors"
"fmt" "fmt"
"github.com/pkg/errors"
) )
var ( var (
@ -19,7 +19,28 @@ var (
ErrInvalidHmac = errors.New("invalid hmac") ErrInvalidHmac = errors.New("invalid hmac")
ErrInvalidServerResponse = errors.New("invalid response received from server") ErrInvalidServerResponse = errors.New("invalid response received from server")
ErrServerRespondedWith404 = errors.New("server responded with status 404") 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") ErrInvalidWebsocket = errors.New("invalid websocket")
ErrMessageTypeNotImplemented = errors.New("message type not implemented") ErrMessageTypeNotImplemented = errors.New("message type not implemented")
ErrOptionsNotProvided = errors.New("new conn options not provided") ErrOptionsNotProvided = errors.New("new conn options not provided")
) )
@ -40,3 +61,31 @@ type ErrConnectionClosed struct {
func (e *ErrConnectionClosed) Error() string { func (e *ErrConnectionClosed) Error() string {
return fmt.Sprintf("server closed connection,code: %d,text: %s", e.Code, e.Text) 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 module github.com/Rhymen/go-whatsapp
require ( go 1.11
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.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= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/Baozisoftware/qrcode-terminal-go v0.0.0-20170407111555-c0650d8dff0f/go.mod h1:4a58ifQTEe2uwwsaqbh3i2un5/CBPg+At/qHpt18Tmk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Rhymen/go-whatsapp v0.0.0/go.mod h1:rdQr95g2C1xcOfM7QGOhza58HeI3I+tZ/bbluv7VazA= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/Rhymen/go-whatsapp/examples/echo v0.0.0-20190325075644-cc2581bbf24d h1:m3wkrunHupL9XzzM+JZu1pgoDV1d9LFtD0gedNTHVDU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/Rhymen/go-whatsapp/examples/echo v0.0.0-20190325075644-cc2581bbf24d/go.mod h1:zgCiQtBtZ4P4gFWvwl9aashsdwOcbb/EHOGRmSzM8ME= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/Rhymen/go-whatsapp/examples/restoreSession v0.0.0-20190325075644-cc2581bbf24d h1:muQlzqfZxjptOBjPdv+UoxVMr8Y1rPx7VMGPJIAFc5w= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/Rhymen/go-whatsapp/examples/restoreSession v0.0.0-20190325075644-cc2581bbf24d/go.mod h1:5sCUSpG616ZoSJhlt9iBNI/KXBqrVLcNUJqg7J9+8pU= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/Rhymen/go-whatsapp/examples/sendImage v0.0.0-20190325075644-cc2581bbf24d h1:xP//3V77YvHd1cj2Z3ttuQWAvs5WmIwBbjKe/t0g/tM= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
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=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 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.2/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.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/mattn/go-isatty v0.0.5 h1:tHXDdz1cpzGaovsTB+TVB8q90WEokoVmfMqoVcrLUgw= github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/skip2/go-qrcode v0.0.0-20190110000554-dc11ecdae0a9 h1:lpEzuenPuO1XNTeikEmvqYFcU37GVLl8SRNblzyvGBE= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/skip2/go-qrcode v0.0.0-20190110000554-dc11ecdae0a9/go.mod h1:PLPIyL7ikehBD1OAjmKKiOEhbvWyHGaNDjquXMcYABo= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= 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/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/crypto v0.0.0-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf3coIG4HqZElCPehJsfAYM=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= 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-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-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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 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 ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"strconv"
"time" "time"
"github.com/Rhymen/go-whatsapp/binary"
) )
func (wac *Conn) GetGroupMetaData(jid string) (<-chan string, error) { 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") return "", fmt.Errorf("request timed out")
} }
if int(response["status"].(float64)) != 200 { status := int(response["status"].(float64))
return "", fmt.Errorf("request responded with %d", response["status"]) if status == 401 {
return "", ErrCantGetInviteLink
} else if status != 200 {
return "", fmt.Errorf("request responded with %d", status)
} }
return response["code"].(string), nil 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") return "", fmt.Errorf("request timed out")
} }
if int(response["status"].(float64)) != 200 { status := int(response["status"].(float64))
return "", fmt.Errorf("request responded with %d", response["status"])
if status == 401 {
return "", ErrJoinUnauthorized
} else if status != 200 {
return "", fmt.Errorf("request responded with %d", status)
} }
return response["gid"].(string), nil 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) 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. 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) 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. 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{}) { 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) 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: case *proto.WebMessageInfo:
for _, h := range handlers { for _, h := range handlers {
if x, ok := h.(RawMessageHandler); ok { 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 { for a := range con {
wac.handle(ParseNodeMessage(con[a])) wac.handle(ParseNodeMessage(con[a]))
} }
} else {
wac.handle(message)
} }
} else if message.Description == "response" && message.Attributes["type"] == "contacts" { } else if message.Description == "response" && message.Attributes["type"] == "contacts" {
wac.updateContacts(message.Content) wac.updateContacts(message.Content)
@ -432,6 +496,8 @@ func (wac *Conn) dispatch(msg interface{}) {
} else if message.Description == "response" && message.Attributes["type"] == "chat" { } else if message.Description == "response" && message.Attributes["type"] == "chat" {
wac.updateChats(message.Content) wac.updateChats(message.Content)
wac.handleChats(message.Content) wac.handleChats(message.Content)
} else {
wac.handle(message)
} }
case error: case error:
wac.handle(message) wac.handle(message)

View File

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

View File

@ -12,6 +12,7 @@ import (
"github.com/Rhymen/go-whatsapp/binary" "github.com/Rhymen/go-whatsapp/binary"
"github.com/Rhymen/go-whatsapp/binary/proto" "github.com/Rhymen/go-whatsapp/binary/proto"
"github.com/davecgh/go-spew/spew"
) )
type MediaType string type MediaType string
@ -23,6 +24,23 @@ const (
MediaDocument MediaType = "WhatsApp Document Keys" 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) { func (wac *Conn) Send(msg interface{}) (string, error) {
var msgProto *proto.WebMessageInfo var msgProto *proto.WebMessageInfo
@ -76,21 +94,16 @@ func (wac *Conn) Send(msg interface{}) (string, error) {
select { select {
case response := <-ch: case response := <-ch:
var resp map[string]interface{} resp := StatusResponse{RequestType: "message sending"}
if err = json.Unmarshal([]byte(response), &resp); err != nil { if err = json.Unmarshal([]byte(response), &resp); err != nil {
return "ERROR", fmt.Errorf("error decoding sending response: %v\n", err) 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): case <-time.After(wac.msgTimeout):
return "ERROR", fmt.Errorf("sending message timed out") return "ERROR", fmt.Errorf("sending message timed out")
} }
return "ERROR", nil
} }
func (wac *Conn) sendProto(p *proto.WebMessageInfo) (<-chan string, error) { 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 { select {
case response := <-ch: case response := <-ch:
var resp map[string]interface{} resp := StatusResponse{RequestType: "message deletion"}
if err = json.Unmarshal([]byte(response), &resp); err != nil { if err = json.Unmarshal([]byte(response), &resp); err != nil {
return fmt.Errorf("error decoding deletion response: %v", err) 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): case <-time.After(wac.msgTimeout):
return fmt.Errorf("deleting message timed out") return fmt.Errorf("deleting message timed out")
} }
return nil
} }
func (wac *Conn) deleteChatProto(remotejid, msgid string, fromMe bool) (<-chan string, error) { 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 info.FromMe = true
status := proto.WebMessageInfo_WEB_MESSAGE_INFO_STATUS(info.Status) status := proto.WebMessageInfo_WebMessageInfoStatus(info.Status)
return &proto.WebMessageInfo{ return &proto.WebMessageInfo{
Key: &proto.MessageKey{ Key: &proto.MessageKey{
@ -279,15 +287,16 @@ type ContextInfo struct {
QuotedMessage *proto.Message QuotedMessage *proto.Message
Participant string Participant string
IsForwarded bool IsForwarded bool
MentionedJID []string
} }
func getMessageContext(msg *proto.ContextInfo) ContextInfo { func getMessageContext(msg *proto.ContextInfo) ContextInfo {
return ContextInfo{ return ContextInfo{
QuotedMessageID: msg.GetStanzaId(), // StanzaId QuotedMessageID: msg.GetStanzaId(), // StanzaId
QuotedMessage: msg.GetQuotedMessage(), QuotedMessage: msg.GetQuotedMessage(),
Participant: msg.GetParticipant(), Participant: msg.GetParticipant(),
IsForwarded: msg.GetIsForwarded(), IsForwarded: msg.GetIsForwarded(),
MentionedJID: msg.GetMentionedJid(),
} }
} }
@ -325,7 +334,6 @@ func getTextMessage(msg *proto.WebMessageInfo) TextMessage {
text.ContextInfo = getMessageContext(m.GetContextInfo()) text.ContextInfo = getMessageContext(m.GetContextInfo())
} else { } else {
text.Text = msg.GetMessage().GetConversation() text.Text = msg.GetMessage().GetConversation()
} }
return text return text
@ -803,7 +811,6 @@ func getContactMessageProto(msg ContactMessage) *proto.WebMessageInfo {
} }
func ParseProtoMessage(msg *proto.WebMessageInfo) interface{} { func ParseProtoMessage(msg *proto.WebMessageInfo) interface{} {
switch { switch {
case msg.GetMessage().GetAudioMessage() != nil: case msg.GetMessage().GetAudioMessage() != nil:
@ -838,6 +845,7 @@ func ParseProtoMessage(msg *proto.WebMessageInfo) interface{} {
default: default:
// cannot match message // cannot match message
spew.Dump(msg)
return ErrMessageTypeNotImplemented return ErrMessageTypeNotImplemented
} }
} }
@ -873,15 +881,50 @@ func getNewContact(msg map[string]string) Contact {
return 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{} { func ParseNodeMessage(msg binary.Node) interface{} {
switch msg.Description { switch msg.Description {
case "battery": case "battery":
return getBatteryMessage(msg.Attributes) return getBatteryMessage(msg.Attributes)
case "user": case "user":
return getNewContact(msg.Attributes) return getNewContact(msg.Attributes)
case "read":
return getReadMessage(msg.Attributes)
case "received":
return getReceivedMessage(msg.Attributes)
default: default:
//cannot match message return &msg
} }
return nil
} }

View File

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

View File

@ -18,7 +18,7 @@ import (
) )
//represents the WhatsAppWeb client version //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 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 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. 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) { if wac.session != nil && (wac.session.EncKey != nil || wac.session.MacKey != nil) {
return fmt.Errorf("cannot change client name after logging in") 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} 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 // GetClientVersion returns WhatsApp client version
func (wac *Conn) GetClientVersion() []int { func (wac *Conn) GetClientVersion() []int {
return waVersion return waVersion
@ -186,6 +208,10 @@ github.com/Baozisoftware/qrcode-terminal-go Example login procedure:
fmt.Printf("login successful, session: %v\n", session) fmt.Printf("login successful, session: %v\n", session)
*/ */
func (wac *Conn) Login(qrChan chan<- string) (Session, error) { 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{} session := Session{}
//Makes sure that only a single Login or Restore can happen at the same time //Makes sure that only a single Login or Restore can happen at the same time
if !atomic.CompareAndSwapUint32(&wac.sessionLock, 0, 1) { 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) 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() priv, pub, err := curve25519.GenerateKey()
if err != nil { if err != nil {
@ -249,18 +251,35 @@ func (wac *Conn) Login(qrChan chan<- string) (Session, error) {
wac.listener.m["s1"] = s1 wac.listener.m["s1"] = s1
wac.listener.Unlock() 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) qrChan <- fmt.Sprintf("%v,%v,%v", ref, base64.StdEncoding.EncodeToString(pub[:]), session.ClientId)
var resp2 []interface{}
select {
case r1 := <-s1:
wac.loginSessionLock.Lock() wac.loginSessionLock.Lock()
defer wac.loginSessionLock.Unlock() defer wac.loginSessionLock.Unlock()
var resp2 []interface{}
For:
for {
select {
case r1 := <-s1:
if err := json.Unmarshal([]byte(r1), &resp2); err != nil { if err := json.Unmarshal([]byte(r1), &resp2); err != nil {
return session, fmt.Errorf("error decoding qr code resp: %v", err) return session, fmt.Errorf("error decoding qr code resp: %v", err)
} }
case <-time.After(time.Duration(resp["ttl"].(float64)) * time.Millisecond): break For
return session, fmt.Errorf("qr code scan timed out") 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)
}
} }
info := resp2[1].(map[string]interface{}) info := resp2[1].(map[string]interface{})
@ -389,14 +408,12 @@ func (wac *Conn) Restore() error {
select { select {
case r := <-initChan: case r := <-initChan:
var resp map[string]interface{} resp := StatusResponse{RequestType: "init"}
if err = json.Unmarshal([]byte(r), &resp); err != nil { if err = json.Unmarshal([]byte(r), &resp); err != nil {
return fmt.Errorf("error decoding login connResp: %v\n", err) return fmt.Errorf("error decoding login connResp: %v\n", err)
} } else if resp.Status != 200 {
if int(resp["status"].(float64)) != 200 {
wac.timeTag = "" wac.timeTag = ""
return fmt.Errorf("init responded with %d", resp["status"]) return resp
} }
case <-time.After(wac.msgTimeout): case <-time.After(wac.msgTimeout):
wac.timeTag = "" wac.timeTag = ""
@ -416,12 +433,11 @@ func (wac *Conn) Restore() error {
//check for an error message //check for an error message
select { select {
case r := <-loginChan: case r := <-loginChan:
var resp map[string]interface{} resp := StatusResponse{RequestType: "admin login"}
if err = json.Unmarshal([]byte(r), &resp); err != nil { if err = json.Unmarshal([]byte(r), &resp); err != nil {
return fmt.Errorf("error decoding login connResp: %v\n", err) return fmt.Errorf("error decoding login connResp: %v\n", err)
} } else if resp.Status != 200 {
if int(resp["status"].(float64)) != 200 { return fmt.Errorf("admin login errored: %w", wac.getAdminLoginResponseError(resp))
return fmt.Errorf("admin login responded with %d", int(resp["status"].(float64)))
} }
default: default:
// not even an error message assume timeout // not even an error message assume timeout
@ -456,15 +472,13 @@ func (wac *Conn) Restore() error {
//check for login 200 --> login success //check for login 200 --> login success
select { select {
case r := <-loginChan: case r := <-loginChan:
var resp map[string]interface{} resp := StatusResponse{RequestType: "admin login"}
if err = json.Unmarshal([]byte(r), &resp); err != nil { if err = json.Unmarshal([]byte(r), &resp); err != nil {
wac.timeTag = "" wac.timeTag = ""
return fmt.Errorf("error decoding login connResp: %v\n", err) return fmt.Errorf("error decoding login connResp: %v\n", err)
} } else if resp.Status != 200 {
if int(resp["status"].(float64)) != 200 {
wac.timeTag = "" 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): case <-time.After(wac.msgTimeout):
wac.timeTag = "" wac.timeTag = ""
@ -484,6 +498,22 @@ func (wac *Conn) Restore() error {
return nil 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 { func (wac *Conn) resolveChallenge(challenge string) error {
decoded, err := base64.StdEncoding.DecodeString(challenge) decoded, err := base64.StdEncoding.DecodeString(challenge)
if err != nil { if err != nil {
@ -501,12 +531,11 @@ func (wac *Conn) resolveChallenge(challenge string) error {
select { select {
case r := <-challengeChan: case r := <-challengeChan:
var resp map[string]interface{} resp := StatusResponse{RequestType: "login challenge"}
if err := json.Unmarshal([]byte(r), &resp); err != nil { if err := json.Unmarshal([]byte(r), &resp); err != nil {
return fmt.Errorf("error decoding login resp: %v\n", err) return fmt.Errorf("error decoding login resp: %v\n", err)
} } else if resp.Status != 200 {
if int(resp["status"].(float64)) != 200 { return resp
return fmt.Errorf("challenge responded with %d\n", resp["status"])
} }
case <-time.After(wac.msgTimeout): case <-time.After(wac.msgTimeout):
return fmt.Errorf("connection timed out") return fmt.Errorf("connection timed out")

View File

@ -6,13 +6,12 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"strconv" "strconv"
"time" "time"
"github.com/gorilla/websocket"
"github.com/Rhymen/go-whatsapp/binary" "github.com/Rhymen/go-whatsapp/binary"
"github.com/Rhymen/go-whatsapp/crypto/cbc" "github.com/Rhymen/go-whatsapp/crypto/cbc"
"github.com/gorilla/websocket"
"github.com/pkg/errors"
) )
//writeJson enqueues a json message into the writeChan //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) data, err := wac.encryptBinaryMessage(node)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "encryptBinaryMessage(node) failed") return nil, fmt.Errorf("encryptBinaryMessage(node) failed: %w", err)
} }
bytes := []byte(messageTag + ",") 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) ch, err := wac.write(websocket.BinaryMessage, messageTag, bytes)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "failed to write message") return nil, fmt.Errorf("failed to write message: %w", err)
} }
wac.msgCount++ wac.msgCount++
@ -74,14 +73,14 @@ func (wac *Conn) sendKeepAlive() error {
bytes := []byte("?,,") bytes := []byte("?,,")
respChan, err := wac.write(websocket.TextMessage, "!", bytes) respChan, err := wac.write(websocket.TextMessage, "!", bytes)
if err != nil { if err != nil {
return errors.Wrap(err, "error sending keepAlive") return fmt.Errorf("error sending keepAlive: %w", err)
} }
select { select {
case resp := <-respChan: case resp := <-respChan:
msecs, err := strconv.ParseInt(resp, 10, 64) msecs, err := strconv.ParseInt(resp, 10, 64)
if err != nil { 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)) 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. 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. Tested with Airplane mode and no connection at all.
*/ */
func (wac *Conn) sendAdminTest() (bool, error) { func (wac *Conn) sendAdminTest() error {
data := []interface{}{"admin", "test"} data := []interface{}{"admin", "test"}
r, err := wac.writeJson(data) r, err := wac.writeJson(data)
if err != nil { 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 response []interface{}
var resp string
select { select {
case resp := <-r: case resp = <-r:
if err := json.Unmarshal([]byte(resp), &response); err != nil { 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): case <-time.After(wac.msgTimeout):
return false, ErrConnectionTimeout return ErrConnectionTimeout
} }
if len(response) == 2 && response[0].(string) == "Pong" && response[1].(bool) == true { if len(response) == 2 && response[0].(string) == "Pong" && response[1].(bool) == true {
return true, nil return nil
} else { } 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) delete(wac.listener.m, answerMessageTag)
wac.listener.Unlock() 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 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) { func (wac *Conn) encryptBinaryMessage(node binary.Node) (data []byte, err error) {
b, err := binary.Marshal(node) b, err := binary.Marshal(node)
if err != nil { 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) cipher, err := cbc.Encrypt(wac.session.EncKey, nil, b)
if err != nil { 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) 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" import qrcode "github.com/skip2/go-qrcode"
- **Create a PNG image:** - **Create a 256x256 PNG image:**
var png []byte var png []byte
png, err := qrcode.Encode("https://example.org", qrcode.Medium, 256) 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") 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") 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 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.
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.
## Documentation ## Documentation
@ -56,10 +50,13 @@ qrcode -- QR Code encoder in Go
https://github.com/skip2/go-qrcode https://github.com/skip2/go-qrcode
Flags: Flags:
-d disable QR Code border
-i invert black and white
-o string -o string
out PNG file prefix, empty for stdout out PNG file prefix, empty for stdout
-s int -s int
image size (pixel) (default 256) image size (pixel) (default 256)
-t print as text-art on stdout
Usage: Usage:
1. Arguments except for flags are joined by " " and used to generate QR code. 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: 2. Save to file if "display" not available:
qrcode "homepage: https://github.com/skip2/go-qrcode" > out.png 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 ## Links

View File

@ -172,7 +172,7 @@ func (d *dataEncoder) encode(data []byte) (*bitset.Bitset, error) {
} }
// Classify data into unoptimised segments. // Classify data into unoptimised segments.
d.classifyDataModes() highestRequiredMode := d.classifyDataModes()
// Optimise segments. // Optimise segments.
err := d.optimiseDataModes() err := d.optimiseDataModes()
@ -180,6 +180,25 @@ func (d *dataEncoder) encode(data []byte) (*bitset.Bitset, error) {
return nil, err 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. // Encode data.
encoded := bitset.New() encoded := bitset.New()
for _, s := range d.optimised { 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. // classifyDataModes classifies the raw data into unoptimised segments.
// e.g. "123ZZ#!#!" => // e.g. "123ZZ#!#!" =>
// [numeric, 3, "123"] [alphanumeric, 2, "ZZ"] [byte, 4, "#!#!"]. // [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 var start int
mode := dataModeNone mode := dataModeNone
highestRequiredMode := mode
for i, v := range d.data { for i, v := range d.data {
newMode := dataModeNone newMode := dataModeNone
@ -217,9 +242,15 @@ func (d *dataEncoder) classifyDataModes() {
mode = newMode mode = newMode
} }
if newMode > highestRequiredMode {
highestRequiredMode = newMode
}
} }
d.actual = append(d.actual, segment{dataMode: mode, data: d.data[start:len(d.data)]}) 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 // 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 ( import (
"bytes" "bytes"
"errors" "errors"
"fmt"
"image" "image"
"image/color" "image/color"
"image/png" "image/png"
@ -135,6 +136,9 @@ type QRCode struct {
ForegroundColor color.Color ForegroundColor color.Color
BackgroundColor color.Color BackgroundColor color.Color
// Disable the QR Code border.
DisableBorder bool
encoder *dataEncoder encoder *dataEncoder
version qrCodeVersion version qrCodeVersion
@ -193,12 +197,16 @@ func New(content string, level RecoveryLevel) (*QRCode, error) {
version: *chosenVersion, version: *chosenVersion,
} }
q.encode(chosenVersion.numTerminatorBitsRequired(encoded.Len()))
return q, nil 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 var encoder *dataEncoder
switch { switch {
@ -209,7 +217,7 @@ func newWithForcedVersion(content string, version int, level RecoveryLevel) (*QR
case version >= 27 && version <= 40: case version >= 27 && version <= 40:
encoder = newDataEncoder(dataEncoderType27To40) encoder = newDataEncoder(dataEncoderType27To40)
default: 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 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") 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{ q := &QRCode{
Content: content, Content: content,
@ -239,8 +254,6 @@ func newWithForcedVersion(content string, version int, level RecoveryLevel) (*QR
version: *chosenVersion, version: *chosenVersion,
} }
q.encode(chosenVersion.numTerminatorBitsRequired(encoded.Len()))
return q, nil 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 // The bitmap includes the required "quiet zone" around the QR Code to aid
// decoding. // decoding.
func (q *QRCode) Bitmap() [][]bool { func (q *QRCode) Bitmap() [][]bool {
// Build QR code.
q.encode()
return q.symbol.bitmap() 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 // 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. // each module (QR Code "pixel") to be 5px in size.
func (q *QRCode) Image(size int) image.Image { func (q *QRCode) Image(size int) image.Image {
// Build QR code.
q.encode()
// Minimum pixels (both width and height) required. // Minimum pixels (both width and height) required.
realSize := q.symbol.size realSize := q.symbol.size
@ -282,12 +301,7 @@ func (q *QRCode) Image(size int) image.Image {
size = realSize size = realSize
} }
// Size of each module drawn. // Output image.
pixelsPerModule := size / realSize
// Center the symbol within the image.
offset := (size - realSize*pixelsPerModule) / 2
rect := image.Rectangle{Min: image.Point{0, 0}, Max: image.Point{size, size}} rect := image.Rectangle{Min: image.Point{0, 0}, Max: image.Point{size, size}}
// Saves a few bytes to have them in this order // Saves a few bytes to have them in this order
@ -295,21 +309,24 @@ func (q *QRCode) Image(size int) image.Image {
img := image.NewPaletted(rect, p) img := image.NewPaletted(rect, p)
fgClr := uint8(img.Palette.Index(q.ForegroundColor)) fgClr := uint8(img.Palette.Index(q.ForegroundColor))
// QR code bitmap.
bitmap := q.symbol.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 { if v {
startX := x*pixelsPerModule + offset pos := img.PixOffset(x, y)
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 img.Pix[pos] = fgClr
} }
} }
} }
}
}
return img return img
} }
@ -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 // encode completes the steps required to encode the QR Code. These include
// adding the terminator bits and padding, splitting the data into blocks and // adding the terminator bits and padding, splitting the data into blocks and
// applying the error correction, and selecting the best data mask. // 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.addTerminatorBits(numTerminatorBits)
q.addPadding() q.addPadding()
@ -384,7 +403,7 @@ func (q *QRCode) encode(numTerminatorBits int) {
var s *symbol var s *symbol
var err error var err error
s, err = buildRegularSymbol(q.version, mask, encoded) s, err = buildRegularSymbol(q.version, mask, encoded, !q.DisableBorder)
if err != nil { if err != nil {
log.Panic(err.Error()) log.Panic(err.Error())

View File

@ -105,13 +105,19 @@ var (
) )
func buildRegularSymbol(version qrCodeVersion, mask int, 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{ m := &regularSymbol{
version: version, version: version,
mask: mask, mask: mask,
data: data, data: data,
symbol: newSymbol(version.symbolSize(), version.quietZoneSize()), symbol: newSymbol(version.symbolSize(), quietZoneSize),
size: version.symbolSize(), 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/rwu
github.com/Philipp15b/go-steam/socialcache github.com/Philipp15b/go-steam/socialcache
github.com/Philipp15b/go-steam/steamid 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 ## explicit
github.com/Rhymen/go-whatsapp github.com/Rhymen/go-whatsapp
github.com/Rhymen/go-whatsapp/binary github.com/Rhymen/go-whatsapp/binary
@ -231,7 +231,8 @@ github.com/shazow/ssh-chat/sshd/terminal
# github.com/sirupsen/logrus v1.7.0 # github.com/sirupsen/logrus v1.7.0
## explicit ## explicit
github.com/sirupsen/logrus 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
github.com/skip2/go-qrcode/bitset github.com/skip2/go-qrcode/bitset
github.com/skip2/go-qrcode/reedsolomon github.com/skip2/go-qrcode/reedsolomon
@ -431,3 +432,4 @@ layeh.com/gumble/gumble
layeh.com/gumble/gumble/MumbleProto layeh.com/gumble/gumble/MumbleProto
layeh.com/gumble/gumble/varint layeh.com/gumble/gumble/varint
layeh.com/gumble/gumbleutil layeh.com/gumble/gumbleutil
# github.com/Rhymen/go-whatsapp => github.com/tulir/go-whatsapp v0.3.16