diff --git a/CHANGELOG.md b/CHANGELOG.md index f648c08d0c0..85d9df26812 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog ## master / unreleased +* [FEATURE] AlertManager: Add support for Webex, Discord and Telegram Receiver. #5493 * [FEATURE] Ingester: added `-admin-limit-message` to customize the message contained in limit errors.#5460 * [CHANGE] AlertManager: include reason label in cortex_alertmanager_notifications_failed_total.#5409 * [CHANGE] Query: Set CORS Origin headers for Query API #5388 diff --git a/docs/configuration/config-file-reference.md b/docs/configuration/config-file-reference.md index e84d16a611f..4b910812d51 100644 --- a/docs/configuration/config-file-reference.md +++ b/docs/configuration/config-file-reference.md @@ -2999,7 +2999,7 @@ The `limits_config` configures default and per-tenant limits imposed by Cortex s # is given in JSON format. Rate limit has the same meaning as # -alertmanager.notification-rate-limit, but only applies for specific # integration. Allowed integration names: webhook, email, pagerduty, opsgenie, -# wechat, slack, victorops, pushover, sns. +# wechat, slack, victorops, pushover, sns, telegram, discord, webex. # CLI flag: -alertmanager.notification-rate-limit-per-integration [alertmanager_notification_rate_limit_per_integration: | default = {}] diff --git a/go.mod b/go.mod index 9633c104119..8f122f4b103 100644 --- a/go.mod +++ b/go.mod @@ -227,6 +227,7 @@ require ( google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e // indirect gopkg.in/alecthomas/kingpin.v2 v2.2.6 // indirect gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/telebot.v3 v3.1.3 // indirect ) // Override since git.apache.org is down. The docs say to fetch from github. diff --git a/go.sum b/go.sum index 0ecf0571630..964408fcb26 100644 --- a/go.sum +++ b/go.sum @@ -183,6 +183,7 @@ cloud.google.com/go/eventarc v1.7.0/go.mod h1:6ctpF3zTnaQCxUjHUdcfgcA1A2T309+omH cloud.google.com/go/eventarc v1.8.0/go.mod h1:imbzxkyAU4ubfsaKYdQg04WS1NvncblHEup4kvF+4gw= cloud.google.com/go/filestore v1.3.0/go.mod h1:+qbvHGvXU1HaKX2nD0WEPo92TP/8AQuCVEBXNY9z0+w= cloud.google.com/go/filestore v1.4.0/go.mod h1:PaG5oDfo9r224f8OYXURtAsY+Fbyq/bLYoINEK8XQAI= +cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY= cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE= cloud.google.com/go/functions v1.6.0/go.mod h1:3H1UA3qiIPRWD7PeZKLvHZ9SaQhR26XIJcC0A5GbvAk= cloud.google.com/go/functions v1.7.0/go.mod h1:+d+QBcWM+RsrgZfV9xo6KfA1GlzJfxcfZcRPEhDDfzg= @@ -445,6 +446,7 @@ github.com/aliyun/aliyun-oss-go-sdk v2.2.2+incompatible h1:9gWa46nstkJ9miBReJcN8 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= @@ -535,6 +537,7 @@ github.com/cncf/xds/go v0.0.0-20230310173818-32f1caf87195 h1:58f1tJ1ra+zFINPlwLW github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.4.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= @@ -556,6 +559,7 @@ github.com/docker/docker v23.0.4+incompatible h1:Kd3Bh9V/rO+XpTP/BLqM+gx8z7+Yb0A github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ= @@ -585,6 +589,7 @@ github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb h1:IT4JYU7k4ikYg1S github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb/go.mod h1:bH6Xx7IW64qjjJq8M2u4dxNaBiDfKK+z/3eGDpXEQhc= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= @@ -594,6 +599,7 @@ github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSw github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= @@ -653,6 +659,10 @@ github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogB github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-openapi/validate v0.22.1 h1:G+c2ub6q47kfX1sOBLwIQwzBVt8qmOAARyo/9Fqs9NU= github.com/go-openapi/validate v0.22.1/go.mod h1:rjnrwK57VJ7A8xqfpAOEKRH8yQSGUriMu5/zuPSQ1hg= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY= @@ -682,6 +692,7 @@ github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWe github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= +github.com/goccy/go-yaml v1.9.5/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= @@ -830,22 +841,28 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDa github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.0-rc.2.0.20201207153454-9f6bf00c00a7 h1:guQyUpELu4I0wKgdsRBZDA5blfGiUleuppRSVy9Qbi0= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.0-rc.2.0.20201207153454-9f6bf00c00a7/go.mod h1:GhphxcdlaRyAuBSvo6rV71BvQcvB/vuX8ugCyybuS2k= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2 h1:gDLXvp5S9izjldquuoAhDzccbskOL6tDC5jMSyx3zxE= github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2/go.mod h1:7pdNwVWBBHGiCxa9lAszqCJMbfTISJ7oMftp8+UGV08= github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= +github.com/hashicorp/consul/api v1.12.0/go.mod h1:6pVBMo0ebnYdt2S3H87XhekM/HHrUoTD2XXb/VrZVy0= github.com/hashicorp/consul/api v1.20.0 h1:9IHTjNVSZ7MIwjlW3N3a7iGiykCMDpxZu8jsxFJh0yc= github.com/hashicorp/consul/api v1.20.0/go.mod h1:nR64eD44KQ59Of/ECwt2vUmIK2DKsDzAwTmwmLl8Wpo= +github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= github.com/hashicorp/consul/sdk v0.13.1 h1:EygWVWWMczTzXGpO93awkHFzfUka6hLYJ0qhETd+6lY= github.com/hashicorp/cronexpr v1.1.1 h1:NJZDd87hGXjoZBdvyCF9mX4DCq5Wy7+A/w+A7q0wn6c= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v1.4.0 h1:ctuWFGrhFha8BnnzxqeRGidlEcQkDyL5u8J8t5eA11I= github.com/hashicorp/go-hclog v1.4.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= @@ -872,13 +889,17 @@ github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2I github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru v0.6.0 h1:uL2shRDx7RTrOrTCUZEGP/wJUFiUI8QT6E7z5o8jga4= github.com/hashicorp/golang-lru v0.6.0/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru/v2 v2.0.2 h1:Dwmkdr5Nc/oBiXgJS3CDHNhJtIHkuZ3DZF5twqnfBdU= github.com/hashicorp/golang-lru/v2 v2.0.2/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= github.com/hashicorp/nomad/api v0.0.0-20230418003350-3067191c5197 h1:I5xhKLePXpXgM6pZ4xZNTiurLLS3sGuZrZFFzAbM67A= +github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= +github.com/hashicorp/serf v0.9.7/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY= github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= github.com/hetznercloud/hcloud-go v1.45.1 h1:nl0OOklFfQT5J6AaNIOhl5Ruh3fhmGmhvZEqHbibVuk= @@ -933,7 +954,9 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxv github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -946,11 +969,13 @@ github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtB github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/linode/linodego v1.16.1 h1:5otq57M4PdHycPERRfSFZ0s1yz1ETVWGjCp3hh7+F9w= github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= +github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= @@ -961,6 +986,7 @@ github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kN github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -968,6 +994,7 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk 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.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= @@ -992,12 +1019,15 @@ github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceT github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= @@ -1042,6 +1072,8 @@ github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FI github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -1062,6 +1094,7 @@ github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5Fsn github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ= github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI= @@ -1103,6 +1136,7 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rs/cors v1.9.0 h1:l9HGsTsHJcvW14Nk7J9KFz8bzeAWXn3CG6bgt7LsrAE= github.com/rs/cors v1.9.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= @@ -1110,6 +1144,7 @@ github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sagikazarmark/crypt v0.6.0/go.mod h1:U8+INwJo3nBv1m6A/8OBXAq7Jnpspk5AxSgDyEQcea8= github.com/scaleway/scaleway-sdk-go v1.0.0-beta.15 h1:Y7xOFbD+3jaPw+VN7lkakNJ/pa+ZSQVFp1ONtJaBxns= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= @@ -1134,12 +1169,17 @@ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasO github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= +github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.13.0/go.mod h1:Icm2xNL3/8uyh/wFuB1jI7TiTNKp8632Nwegu+zgdYw= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= @@ -1153,10 +1193,12 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/tencentyun/cos-go-sdk-v5 v0.7.40 h1:W6vDGKCHe4wBACI1d2UgE6+50sJFhRWU4O8IB2ozzxM= github.com/thanos-community/galaxycache v0.0.0-20211122094458-3a32041a1f1e h1:f1Zsv7OAU9iQhZwigp50Yl38W10g/vd5NC8Rdk1Jzng= github.com/thanos-community/galaxycache v0.0.0-20211122094458-3a32041a1f1e/go.mod h1:jXcofnrSln/cLI6/dhlBxPQZEEQHVPCcFaH75M+nSzM= @@ -1196,10 +1238,14 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/gopher-lua v1.1.0 h1:BojcDhfyDWgU2f2TOzYK/g5p2gxMrku8oupLDqlnSqE= github.com/yuin/gopher-lua v1.1.0/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= +go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A= go.etcd.io/etcd/api/v3 v3.5.8 h1:Zf44zJszoU7zRV0X/nStPenegNXoFDWcB/MwrJbA+L4= go.etcd.io/etcd/api/v3 v3.5.8/go.mod h1:uyAal843mC8uUVSLWz6eHa/d971iDGnCRpmKd2Z+X8k= +go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/pkg/v3 v3.5.9 h1:oidDC4+YEuSIQbsR94rY9gur91UPL6DnxDCIYd2IGsE= go.etcd.io/etcd/client/pkg/v3 v3.5.9/go.mod h1:y+CzeSmkMpWN2Jyu1npecjB9BBnABxGM4pN8cGuJeL4= +go.etcd.io/etcd/client/v2 v2.305.4/go.mod h1:Ud+VUwIi9/uQHOMA+4ekToJ12lTxlv0zB/+DHwTGEbU= +go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY= go.etcd.io/etcd/client/v3 v3.5.7 h1:u/OhpiuCgYY8awOHlhIhmGIGpxfBU/GZBUP3m/3/Iz4= go.etcd.io/etcd/client/v3 v3.5.7/go.mod h1:sOWmj9DZUMyAngS7QQwCyAXXAL6WhgTOPLNS/NabQgw= go.mongodb.org/mongo-driver v1.7.3/go.mod h1:NqaYOwnXWr5Pm7AOpO5QFxKJ503nbMse/R79oO62zWg= @@ -1262,6 +1308,7 @@ go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9i go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= @@ -1283,6 +1330,7 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20221012134737-56aed061732a/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= @@ -1383,6 +1431,7 @@ golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= @@ -1410,6 +1459,7 @@ golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= @@ -1435,6 +1485,7 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1462,6 +1513,7 @@ golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1469,6 +1521,7 @@ golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1496,6 +1549,7 @@ golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1680,6 +1734,7 @@ google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6 google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= +google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU= google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= @@ -1690,6 +1745,7 @@ google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69 google.golang.org/api v0.77.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg= +google.golang.org/api v0.81.0/go.mod h1:FA6Mb/bZxj706H2j+j2d6mHEEaHBmbbWnkfvmorOCko= google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o= google.golang.org/api v0.85.0/go.mod h1:AqZf8Ep9uZ2pyTvgL+x0D3Zt0eoT9b5E8fmzfu6FO2g= google.golang.org/api v0.90.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= @@ -1773,6 +1829,8 @@ google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEc google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211028162531-8db9c33dc351/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= @@ -1793,6 +1851,7 @@ google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= @@ -1902,6 +1961,8 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/telebot.v3 v3.1.3 h1:T+CTyOWpZMqp3ALHSweNgp1awQ9nMXdRAMpe/r6x9/s= +gopkg.in/telebot.v3 v3.1.3/go.mod h1:GJKwwWqp9nSkIVN51eRKU78aB5f5OnQuWdwiIZfPbko= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -1938,5 +1999,6 @@ rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/pkg/alertmanager/alertmanager.go b/pkg/alertmanager/alertmanager.go index d5a2aa616b9..d0dc08fb5b0 100644 --- a/pkg/alertmanager/alertmanager.go +++ b/pkg/alertmanager/alertmanager.go @@ -24,13 +24,16 @@ import ( "github.com/prometheus/alertmanager/inhibit" "github.com/prometheus/alertmanager/nflog" "github.com/prometheus/alertmanager/notify" + "github.com/prometheus/alertmanager/notify/discord" "github.com/prometheus/alertmanager/notify/email" "github.com/prometheus/alertmanager/notify/opsgenie" "github.com/prometheus/alertmanager/notify/pagerduty" "github.com/prometheus/alertmanager/notify/pushover" "github.com/prometheus/alertmanager/notify/slack" "github.com/prometheus/alertmanager/notify/sns" + "github.com/prometheus/alertmanager/notify/telegram" "github.com/prometheus/alertmanager/notify/victorops" + "github.com/prometheus/alertmanager/notify/webex" "github.com/prometheus/alertmanager/notify/webhook" "github.com/prometheus/alertmanager/notify/wechat" "github.com/prometheus/alertmanager/provider/mem" @@ -479,7 +482,7 @@ func buildIntegrationsMap(nc []config.Receiver, tmpl *template.Template, firewal // buildReceiverIntegrations builds a list of integration notifiers off of a // receiver config. -// Taken from https://github.com/prometheus/alertmanager/blob/94d875f1227b29abece661db1a68c001122d1da5/cmd/alertmanager/main.go#L112-L159. +// Taken from https://github.com/prometheus/alertmanager/blob/263ca5c9438e/cmd/alertmanager/main.go#L134-L189. func buildReceiverIntegrations(nc config.Receiver, tmpl *template.Template, firewallDialer *util_net.FirewallDialer, logger log.Logger, wrapper func(string, notify.Notifier) notify.Notifier) ([]notify.Integration, error) { var ( errs types.MultiError @@ -527,6 +530,15 @@ func buildReceiverIntegrations(nc config.Receiver, tmpl *template.Template, fire for i, c := range nc.SNSConfigs { add("sns", i, c, func(l log.Logger) (notify.Notifier, error) { return sns.New(c, tmpl, l, httpOps...) }) } + for i, c := range nc.TelegramConfigs { + add("telegram", i, c, func(l log.Logger) (notify.Notifier, error) { return telegram.New(c, tmpl, l, httpOps...) }) + } + for i, c := range nc.DiscordConfigs { + add("discord", i, c, func(l log.Logger) (notify.Notifier, error) { return discord.New(c, tmpl, l, httpOps...) }) + } + for i, c := range nc.WebexConfigs { + add("webex", i, c, func(l log.Logger) (notify.Notifier, error) { return webex.New(c, tmpl, l, httpOps...) }) + } // If we add support for more integrations, we need to add them to validation as well. See validation.allowedIntegrationNames field. if errs.Len() > 0 { return nil, &errs diff --git a/pkg/util/validation/notifications_limit_flag.go b/pkg/util/validation/notifications_limit_flag.go index 97f2901d5b4..8c0788a8762 100644 --- a/pkg/util/validation/notifications_limit_flag.go +++ b/pkg/util/validation/notifications_limit_flag.go @@ -10,7 +10,7 @@ import ( ) var allowedIntegrationNames = []string{ - "webhook", "email", "pagerduty", "opsgenie", "wechat", "slack", "victorops", "pushover", "sns", + "webhook", "email", "pagerduty", "opsgenie", "wechat", "slack", "victorops", "pushover", "sns", "telegram", "discord", "webex", } type NotificationRateLimitMap map[string]float64 diff --git a/vendor/github.com/prometheus/alertmanager/notify/discord/discord.go b/vendor/github.com/prometheus/alertmanager/notify/discord/discord.go new file mode 100644 index 00000000000..31a0a7cfe77 --- /dev/null +++ b/vendor/github.com/prometheus/alertmanager/notify/discord/discord.go @@ -0,0 +1,133 @@ +// Copyright 2021 Prometheus Team +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package discord + +import ( + "bytes" + "context" + "encoding/json" + "net/http" + + "github.com/go-kit/log" + "github.com/go-kit/log/level" + commoncfg "github.com/prometheus/common/config" + "github.com/prometheus/common/model" + + "github.com/prometheus/alertmanager/config" + "github.com/prometheus/alertmanager/notify" + "github.com/prometheus/alertmanager/template" + "github.com/prometheus/alertmanager/types" +) + +const ( + colorRed = 0x992D22 + colorGreen = 0x2ECC71 + colorGrey = 0x95A5A6 +) + +// Notifier implements a Notifier for Discord notifications. +type Notifier struct { + conf *config.DiscordConfig + tmpl *template.Template + logger log.Logger + client *http.Client + retrier *notify.Retrier + webhookURL *config.SecretURL +} + +// New returns a new Discord notifier. +func New(c *config.DiscordConfig, t *template.Template, l log.Logger, httpOpts ...commoncfg.HTTPClientOption) (*Notifier, error) { + client, err := commoncfg.NewClientFromConfig(*c.HTTPConfig, "discord", httpOpts...) + if err != nil { + return nil, err + } + n := &Notifier{ + conf: c, + tmpl: t, + logger: l, + client: client, + retrier: ¬ify.Retrier{}, + webhookURL: c.WebhookURL, + } + return n, nil +} + +type webhook struct { + Content string `json:"content"` + Embeds []webhookEmbed `json:"embeds"` +} + +type webhookEmbed struct { + Title string `json:"title"` + Description string `json:"description"` + Color int `json:"color"` +} + +// Notify implements the Notifier interface. +func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) { + key, err := notify.ExtractGroupKey(ctx) + if err != nil { + return false, err + } + + level.Debug(n.logger).Log("incident", key) + + alerts := types.Alerts(as...) + data := notify.GetTemplateData(ctx, n.tmpl, as, n.logger) + tmpl := notify.TmplText(n.tmpl, data, &err) + if err != nil { + return false, err + } + + title := tmpl(n.conf.Title) + if err != nil { + return false, err + } + description := tmpl(n.conf.Message) + if err != nil { + return false, err + } + + color := colorGrey + if alerts.Status() == model.AlertFiring { + color = colorRed + } + if alerts.Status() == model.AlertResolved { + color = colorGreen + } + + w := webhook{ + Embeds: []webhookEmbed{{ + Title: title, + Description: description, + Color: color, + }}, + } + + var payload bytes.Buffer + if err = json.NewEncoder(&payload).Encode(w); err != nil { + return false, err + } + + resp, err := notify.PostJSON(ctx, n.client, n.webhookURL.String(), &payload) + if err != nil { + return true, notify.RedactURL(err) + } + + shouldRetry, err := n.retrier.Check(resp.StatusCode, resp.Body) + if err != nil { + return shouldRetry, err + } + return false, nil +} diff --git a/vendor/github.com/prometheus/alertmanager/notify/telegram/telegram.go b/vendor/github.com/prometheus/alertmanager/notify/telegram/telegram.go new file mode 100644 index 00000000000..3e60ab76414 --- /dev/null +++ b/vendor/github.com/prometheus/alertmanager/notify/telegram/telegram.go @@ -0,0 +1,128 @@ +// Copyright 2022 Prometheus Team +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package telegram + +import ( + "context" + "fmt" + "net/http" + "os" + "strings" + + "github.com/go-kit/log" + "github.com/go-kit/log/level" + commoncfg "github.com/prometheus/common/config" + "gopkg.in/telebot.v3" + + "github.com/prometheus/alertmanager/config" + "github.com/prometheus/alertmanager/notify" + "github.com/prometheus/alertmanager/template" + "github.com/prometheus/alertmanager/types" +) + +// Telegram supports 4096 chars max - from https://limits.tginfo.me/en. +const maxMessageLenRunes = 4096 + +// Notifier implements a Notifier for telegram notifications. +type Notifier struct { + conf *config.TelegramConfig + tmpl *template.Template + logger log.Logger + client *telebot.Bot + retrier *notify.Retrier +} + +// New returns a new Telegram notification handler. +func New(conf *config.TelegramConfig, t *template.Template, l log.Logger, httpOpts ...commoncfg.HTTPClientOption) (*Notifier, error) { + httpclient, err := commoncfg.NewClientFromConfig(*conf.HTTPConfig, "telegram", httpOpts...) + if err != nil { + return nil, err + } + + client, err := createTelegramClient(conf.APIUrl.String(), conf.ParseMode, httpclient) + if err != nil { + return nil, err + } + + return &Notifier{ + conf: conf, + tmpl: t, + logger: l, + client: client, + retrier: ¬ify.Retrier{}, + }, nil +} + +func (n *Notifier) Notify(ctx context.Context, alert ...*types.Alert) (bool, error) { + var ( + err error + data = notify.GetTemplateData(ctx, n.tmpl, alert, n.logger) + tmpl = notify.TmplText(n.tmpl, data, &err) + ) + + if n.conf.ParseMode == "HTML" { + tmpl = notify.TmplHTML(n.tmpl, data, &err) + } + + key, ok := notify.GroupKey(ctx) + if !ok { + return false, fmt.Errorf("group key missing") + } + + messageText, truncated := notify.TruncateInRunes(tmpl(n.conf.Message), maxMessageLenRunes) + if truncated { + level.Warn(n.logger).Log("msg", "Truncated message", "alert", key, "max_runes", maxMessageLenRunes) + } + + n.client.Token, err = n.getBotToken() + if err != nil { + return true, err + } + + message, err := n.client.Send(telebot.ChatID(n.conf.ChatID), messageText, &telebot.SendOptions{ + DisableNotification: n.conf.DisableNotifications, + DisableWebPagePreview: true, + }) + if err != nil { + return true, err + } + level.Debug(n.logger).Log("msg", "Telegram message successfully published", "message_id", message.ID, "chat_id", message.Chat.ID) + + return false, nil +} + +func createTelegramClient(apiURL, parseMode string, httpClient *http.Client) (*telebot.Bot, error) { + bot, err := telebot.NewBot(telebot.Settings{ + URL: apiURL, + ParseMode: parseMode, + Client: httpClient, + Offline: true, + }) + if err != nil { + return nil, err + } + + return bot, nil +} + +func (n *Notifier) getBotToken() (string, error) { + if len(n.conf.BotTokenFile) > 0 { + content, err := os.ReadFile(n.conf.BotTokenFile) + if err != nil { + return "", fmt.Errorf("could not read %s: %w", n.conf.BotTokenFile, err) + } + return strings.TrimSpace(string(content)), nil + } + return string(n.conf.BotToken), nil +} diff --git a/vendor/github.com/prometheus/alertmanager/notify/webex/webex.go b/vendor/github.com/prometheus/alertmanager/notify/webex/webex.go new file mode 100644 index 00000000000..9e95e3ed9b1 --- /dev/null +++ b/vendor/github.com/prometheus/alertmanager/notify/webex/webex.go @@ -0,0 +1,114 @@ +// Copyright 2022 Prometheus Team +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package webex + +import ( + "bytes" + "context" + "encoding/json" + "net/http" + + "github.com/go-kit/log" + "github.com/go-kit/log/level" + commoncfg "github.com/prometheus/common/config" + + "github.com/prometheus/alertmanager/config" + "github.com/prometheus/alertmanager/notify" + "github.com/prometheus/alertmanager/template" + "github.com/prometheus/alertmanager/types" +) + +const ( + // maxMessageSize represents the maximum message length that Webex supports. + maxMessageSize = 7439 +) + +type Notifier struct { + conf *config.WebexConfig + tmpl *template.Template + logger log.Logger + client *http.Client + retrier *notify.Retrier +} + +// New returns a new Webex notifier. +func New(c *config.WebexConfig, t *template.Template, l log.Logger, httpOpts ...commoncfg.HTTPClientOption) (*Notifier, error) { + client, err := commoncfg.NewClientFromConfig(*c.HTTPConfig, "webex", httpOpts...) + if err != nil { + return nil, err + } + + n := &Notifier{ + conf: c, + tmpl: t, + logger: l, + client: client, + retrier: ¬ify.Retrier{}, + } + + return n, nil +} + +type webhook struct { + Markdown string `json:"markdown"` + RoomID string `json:"roomId,omitempty"` +} + +// Notify implements the Notifier interface. +func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) { + key, err := notify.ExtractGroupKey(ctx) + if err != nil { + return false, err + } + + level.Debug(n.logger).Log("incident", key) + + data := notify.GetTemplateData(ctx, n.tmpl, as, n.logger) + tmpl := notify.TmplText(n.tmpl, data, &err) + if err != nil { + return false, err + } + + message := tmpl(n.conf.Message) + if err != nil { + return false, err + } + + message, truncated := notify.TruncateInBytes(message, maxMessageSize) + if truncated { + level.Debug(n.logger).Log("msg", "message truncated due to exceeding maximum allowed length by webex", "truncated_message", message) + } + + w := webhook{ + Markdown: message, + RoomID: n.conf.RoomID, + } + + var payload bytes.Buffer + if err = json.NewEncoder(&payload).Encode(w); err != nil { + return false, err + } + + resp, err := notify.PostJSON(ctx, n.client, n.conf.APIURL.String(), &payload) + if err != nil { + return true, notify.RedactURL(err) + } + + shouldRetry, err := n.retrier.Check(resp.StatusCode, resp.Body) + if err != nil { + return shouldRetry, err + } + + return false, nil +} diff --git a/vendor/gopkg.in/telebot.v3/.gitignore b/vendor/gopkg.in/telebot.v3/.gitignore new file mode 100644 index 00000000000..c81da31dbeb --- /dev/null +++ b/vendor/gopkg.in/telebot.v3/.gitignore @@ -0,0 +1,34 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof + +.idea +.DS_Store +coverage.txt + +# Terraform artifacts +*.zip +.terraform* +terraform* +/examples/awslambdaechobot/awslambdaechobot diff --git a/vendor/gopkg.in/telebot.v3/LICENSE b/vendor/gopkg.in/telebot.v3/LICENSE new file mode 100644 index 00000000000..2965b8423b1 --- /dev/null +++ b/vendor/gopkg.in/telebot.v3/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 llya Kowalewski + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/gopkg.in/telebot.v3/README.md b/vendor/gopkg.in/telebot.v3/README.md new file mode 100644 index 00000000000..060a70d0d75 --- /dev/null +++ b/vendor/gopkg.in/telebot.v3/README.md @@ -0,0 +1,489 @@ +# Telebot +>"I never knew creating Telegram bots could be so _sexy_!" + +[![GoDoc](https://godoc.org/gopkg.in/telebot.v3?status.svg)](https://godoc.org/gopkg.in/telebot.v3) +[![GitHub Actions](https://github.com/tucnak/telebot/actions/workflows/go.yml/badge.svg)](https://github.com/tucnak/telebot/actions) +[![codecov.io](https://codecov.io/gh/tucnak/telebot/coverage.svg?branch=v3)](https://codecov.io/gh/tucnak/telebot) +[![Discuss on Telegram](https://img.shields.io/badge/telegram-discuss-0088cc.svg)](https://t.me/go_telebot) + +```bash +go get -u gopkg.in/telebot.v3 +``` + +* [Overview](#overview) +* [Getting Started](#getting-started) + - [Context](#context) + - [Middleware](#middleware) + - [Poller](#poller) + - [Commands](#commands) + - [Files](#files) + - [Sendable](#sendable) + - [Editable](#editable) + - [Keyboards](#keyboards) + - [Inline mode](#inline-mode) +* [Contributing](#contributing) +* [Donate](#donate) +* [License](#license) + +# Overview +Telebot is a bot framework for [Telegram Bot API](https://core.telegram.org/bots/api). +This package provides the best of its kind API for command routing, inline query requests and keyboards, as well +as callbacks. Actually, I went a couple steps further, so instead of making a 1:1 API wrapper I chose to focus on +the beauty of API and performance. Some strong sides of Telebot are: + +* Real concise API +* Command routing +* Middleware +* Transparent File API +* Effortless bot callbacks + +All the methods of Telebot API are _extremely_ easy to memorize and get used to. Also, consider Telebot a +highload-ready solution. I'll test and benchmark the most popular actions and if necessary, optimize +against them without sacrificing API quality. + +# Getting Started +Let's take a look at the minimal Telebot setup: + +```go +package main + +import ( + "log" + "os" + "time" + + tele "gopkg.in/telebot.v3" +) + +func main() { + pref := tele.Settings{ + Token: os.Getenv("TOKEN"), + Poller: &tele.LongPoller{Timeout: 10 * time.Second}, + } + + b, err := tele.NewBot(pref) + if err != nil { + log.Fatal(err) + return + } + + b.Handle("/hello", func(c tele.Context) error { + return c.Send("Hello!") + }) + + b.Start() +} + +``` + +Simple, innit? Telebot's routing system takes care of delivering updates +to their endpoints, so in order to get to handle any meaningful event, +all you got to do is just plug your function into one of the Telebot-provided +endpoints. You can find the full list +[here](https://godoc.org/gopkg.in/telebot.v3#pkg-constants). + +There are dozens of supported endpoints (see package consts). Let me know +if you'd like to see some endpoint or endpoint ideas implemented. This system +is completely extensible, so I can introduce them without breaking +backwards compatibility. + +## Context +Context is a special type that wraps a huge update structure and represents +the context of the current event. It provides several helpers, which allow +getting, for example, the chat that this update had been sent in, no matter +what kind of update this is. + +```go +b.Handle(tele.OnText, func(c tele.Context) error { + // All the text messages that weren't + // captured by existing handlers. + + var ( + user = c.Sender() + text = c.Text() + ) + + // Use full-fledged bot's functions + // only if you need a result: + msg, err := b.Send(user, text) + if err != nil { + return err + } + + // Instead, prefer a context short-hand: + return c.Send(text) +}) + +b.Handle(tele.OnChannelPost, func(c tele.Context) error { + // Channel posts only. + msg := c.Message() +}) + +b.Handle(tele.OnPhoto, func(c tele.Context) error { + // Photos only. + photo := c.Message().Photo +}) + +b.Handle(tele.OnQuery, func(c tele.Context) error { + // Incoming inline queries. + return c.Answer(...) +}) +``` + +## Middleware +Telebot has a simple and recognizable way to set up middleware — chained functions with access to `Context`, called before the handler execution. + +Import a `middleware` package to get some basic out-of-box middleware +implementations: +```go +import "gopkg.in/telebot.v3/middleware" +``` + +```go +// Global-scoped middleware: +b.Use(middleware.Logger()) +b.Use(middleware.AutoRespond()) + +// Group-scoped middleware: +adminOnly := b.Group() +adminOnly.Use(middleware.Whitelist(adminIDs...)) +adminOnly.Handle("/ban", onBan) +adminOnly.Handle("/kick", onKick) + +// Handler-scoped middleware: +b.Handle(tele.OnText, onText, middleware.IgnoreVia()) +``` + +Custom middleware example: +```go +// AutoResponder automatically responds to every callback update. +func AutoResponder(next tele.HandlerFunc) tele.HandlerFunc { + return func(c tele.Context) error { + if c.Callback() != nil { + defer c.Respond() + } + return next(c) // continue execution chain + } +} +``` + +## Poller +Telebot doesn't really care how you provide it with incoming updates, as long +as you set it up with a Poller, or call ProcessUpdate for each update: + +```go +// Poller is a provider of Updates. +// +// All pollers must implement Poll(), which accepts bot +// pointer and subscription channel and start polling +// synchronously straight away. +type Poller interface { + // Poll is supposed to take the bot object + // subscription channel and start polling + // for Updates immediately. + // + // Poller must listen for stop constantly and close + // it as soon as it's done polling. + Poll(b *Bot, updates chan Update, stop chan struct{}) +} +``` + +## Commands +When handling commands, Telebot supports both direct (`/command`) and group-like +syntax (`/command@botname`) and will never deliver messages addressed to some +other bot, even if [privacy mode](https://core.telegram.org/bots#privacy-mode) is off. + +For simplified deep-linking, Telebot also extracts payload: +```go +// Command: /start +b.Handle("/start", func(c tele.Context) error { + fmt.Println(c.Message().Payload) // +}) +``` + +For multiple arguments use: +```go +// Command: /tags <...> +b.Handle("/tags", func(c tele.Context) error { + tags := c.Args() // list of arguments splitted by a space + for _, tag := range tags { + // iterate through passed arguments + } +}) +``` + +## Files +>Telegram allows files up to 50 MB in size. + +Telebot allows to both upload (from disk or by URL) and download (from Telegram) +files in bot's scope. Also, sending any kind of media with a File created +from disk will upload the file to Telegram automatically: +```go +a := &tele.Audio{File: tele.FromDisk("file.ogg")} + +fmt.Println(a.OnDisk()) // true +fmt.Println(a.InCloud()) // false + +// Will upload the file from disk and send it to the recipient +b.Send(recipient, a) + +// Next time you'll be sending this very *Audio, Telebot won't +// re-upload the same file but rather utilize its Telegram FileID +b.Send(otherRecipient, a) + +fmt.Println(a.OnDisk()) // true +fmt.Println(a.InCloud()) // true +fmt.Println(a.FileID) // +``` + +You might want to save certain `File`s in order to avoid re-uploading. Feel free +to marshal them into whatever format, `File` only contain public fields, so no +data will ever be lost. + +## Sendable +Send is undoubtedly the most important method in Telebot. `Send()` accepts a +`Recipient` (could be user, group or a channel) and a `Sendable`. Other types other than +the Telebot-provided media types (`Photo`, `Audio`, `Video`, etc.) are `Sendable`. +If you create composite types of your own, and they satisfy the `Sendable` interface, +Telebot will be able to send them out. + +```go +// Sendable is any object that can send itself. +// +// This is pretty cool, since it lets bots implement +// custom Sendables for complex kinds of media or +// chat objects spanning across multiple messages. +type Sendable interface { + Send(*Bot, Recipient, *SendOptions) (*Message, error) +} +``` + +The only type at the time that doesn't fit `Send()` is `Album` and there is a reason +for that. Albums were added not so long ago, so they are slightly quirky for backwards +compatibilities sake. In fact, an `Album` can be sent, but never received. Instead, +Telegram returns a `[]Message`, one for each media object in the album: +```go +p := &tele.Photo{File: tele.FromDisk("chicken.jpg")} +v := &tele.Video{File: tele.FromURL("http://video.mp4")} + +msgs, err := b.SendAlbum(user, tele.Album{p, v}) +``` + +### Send options +Send options are objects and flags you can pass to `Send()`, `Edit()` and friends +as optional arguments (following the recipient and the text/media). The most +important one is called `SendOptions`, it lets you control _all_ the properties of +the message supported by Telegram. The only drawback is that it's rather +inconvenient to use at times, so `Send()` supports multiple shorthands: +```go +// regular send options +b.Send(user, "text", &tele.SendOptions{ + // ... +}) + +// ReplyMarkup is a part of SendOptions, +// but often it's the only option you need +b.Send(user, "text", &tele.ReplyMarkup{ + // ... +}) + +// flags: no notification && no web link preview +b.Send(user, "text", tele.Silent, tele.NoPreview) +``` + +Full list of supported option-flags you can find +[here](https://pkg.go.dev/gopkg.in/telebot.v3#Option). + +## Editable +If you want to edit some existing message, you don't really need to store the +original `*Message` object. In fact, upon edit, Telegram only requires `chat_id` +and `message_id`. So you don't really need the Message as a whole. Also, you +might want to store references to certain messages in the database, so I thought +it made sense for *any* Go struct to be editable as a Telegram message, to implement +`Editable`: +```go +// Editable is an interface for all objects that +// provide "message signature", a pair of 32-bit +// message ID and 64-bit chat ID, both required +// for edit operations. +// +// Use case: DB model struct for messages to-be +// edited with, say two columns: msg_id,chat_id +// could easily implement MessageSig() making +// instances of stored messages editable. +type Editable interface { + // MessageSig is a "message signature". + // + // For inline messages, return chatID = 0. + MessageSig() (messageID int, chatID int64) +} +``` + +For example, `Message` type is Editable. Here is the implementation of `StoredMessage` +type, provided by Telebot: +```go +// StoredMessage is an example struct suitable for being +// stored in the database as-is or being embedded into +// a larger struct, which is often the case (you might +// want to store some metadata alongside, or might not.) +type StoredMessage struct { + MessageID int `sql:"message_id" json:"message_id"` + ChatID int64 `sql:"chat_id" json:"chat_id"` +} + +func (x StoredMessage) MessageSig() (int, int64) { + return x.MessageID, x.ChatID +} +``` + +Why bother at all? Well, it allows you to do things like this: +```go +// just two integer columns in the database +var msgs []tele.StoredMessage +db.Find(&msgs) // gorm syntax + +for _, msg := range msgs { + bot.Edit(&msg, "Updated text") + // or + bot.Delete(&msg) +} +``` + +I find it incredibly neat. Worth noting, at this point of time there exists +another method in the Edit family, `EditCaption()` which is of a pretty +rare use, so I didn't bother including it to `Edit()`, just like I did with +`SendAlbum()` as it would inevitably lead to unnecessary complications. +```go +var m *Message + +// change caption of a photo, audio, etc. +bot.EditCaption(m, "new caption") +``` + +## Keyboards +Telebot supports both kinds of keyboards Telegram provides: reply and inline +keyboards. Any button can also act as endpoints for `Handle()`. + +```go +var ( + // Universal markup builders. + menu = &tele.ReplyMarkup{ResizeKeyboard: true} + selector = &tele.ReplyMarkup{} + + // Reply buttons. + btnHelp = menu.Text("ℹ Help") + btnSettings = menu.Text("⚙ Settings") + + // Inline buttons. + // + // Pressing it will cause the client to + // send the bot a callback. + // + // Make sure Unique stays unique as per button kind + // since it's required for callback routing to work. + // + btnPrev = selector.Data("⬅", "prev", ...) + btnNext = selector.Data("➡", "next", ...) +) + +menu.Reply( + menu.Row(btnHelp), + menu.Row(btnSettings), +) +selector.Inline( + selector.Row(btnPrev, btnNext), +) + +b.Handle("/start", func(c tele.Context) error { + return c.Send("Hello!", menu) +}) + +// On reply button pressed (message) +b.Handle(&btnHelp, func(c tele.Context) error { + return c.Edit("Here is some help: ...") +}) + +// On inline button pressed (callback) +b.Handle(&btnPrev, func(c tele.Context) error { + return c.Respond() +}) +``` + +You can use markup constructor for every type of possible button: +```go +r := b.NewMarkup() + +// Reply buttons: +r.Text("Hello!") +r.Contact("Send phone number") +r.Location("Send location") +r.Poll(tele.PollQuiz) + +// Inline buttons: +r.Data("Show help", "help") // data is optional +r.Data("Delete item", "delete", item.ID) +r.URL("Visit", "https://google.com") +r.Query("Search", query) +r.QueryChat("Share", query) +r.Login("Login", &tele.Login{...}) +``` + +## Inline mode +So if you want to handle incoming inline queries you better plug the `tele.OnQuery` +endpoint and then use the `Answer()` method to send a list of inline queries +back. I think at the time of writing, Telebot supports all of the provided result +types (but not the cached ones). This is what it looks like: + +```go +b.Handle(tele.OnQuery, func(c tele.Context) error { + urls := []string{ + "http://photo.jpg", + "http://photo2.jpg", + } + + results := make(tele.Results, len(urls)) // []tele.Result + for i, url := range urls { + result := &tele.PhotoResult{ + URL: url, + ThumbURL: url, // required for photos + } + + results[i] = result + // needed to set a unique string ID for each result + results[i].SetResultID(strconv.Itoa(i)) + } + + return c.Answer(&tele.QueryResponse{ + Results: results, + CacheTime: 60, // a minute + }) +}) +``` + +There's not much to talk about really. It also supports some form of authentication +through deep-linking. For that, use fields `SwitchPMText` and `SwitchPMParameter` +of `QueryResponse`. + +# Contributing + +1. Fork it +2. Clone v3: `git clone -b v3 https://github.com/tucnak/telebot` +3. Create your feature branch: `git checkout -b v3-feature` +4. Make changes and add them: `git add .` +5. Commit: `git commit -m "add some feature"` +6. Push: `git push origin v3-feature` +7. Pull request + +# Donate + +I do coding for fun, but I also try to search for interesting solutions and +optimize them as much as possible. +If you feel like it's a good piece of software, I wouldn't mind a tip! + +Litecoin: `ltc1qskt5ltrtyg7esfjm0ftx6jnacwffhpzpqmerus` + +Ethereum: `0xB78A2Ac1D83a0aD0b993046F9fDEfC5e619efCAB` + +# License + +Telebot is distributed under MIT. diff --git a/vendor/gopkg.in/telebot.v3/admin.go b/vendor/gopkg.in/telebot.v3/admin.go new file mode 100644 index 00000000000..c4c828d19ea --- /dev/null +++ b/vendor/gopkg.in/telebot.v3/admin.go @@ -0,0 +1,287 @@ +package telebot + +import ( + "encoding/json" + "strconv" + "time" +) + +// Rights is a list of privileges available to chat members. +type Rights struct { + // Anonymous is true, if the user's presence in the chat is hidden. + Anonymous bool `json:"is_anonymous"` + + CanBeEdited bool `json:"can_be_edited"` + CanChangeInfo bool `json:"can_change_info"` + CanPostMessages bool `json:"can_post_messages"` + CanEditMessages bool `json:"can_edit_messages"` + CanDeleteMessages bool `json:"can_delete_messages"` + CanPinMessages bool `json:"can_pin_messages"` + CanInviteUsers bool `json:"can_invite_users"` + CanRestrictMembers bool `json:"can_restrict_members"` + CanPromoteMembers bool `json:"can_promote_members"` + CanSendMessages bool `json:"can_send_messages"` + CanSendMedia bool `json:"can_send_media_messages"` + CanSendPolls bool `json:"can_send_polls"` + CanSendOther bool `json:"can_send_other_messages"` + CanAddPreviews bool `json:"can_add_web_page_previews"` + CanManageVideoChats bool `json:"can_manage_video_chats"` + CanManageChat bool `json:"can_manage_chat"` +} + +// NoRights is the default Rights{}. +func NoRights() Rights { return Rights{} } + +// NoRestrictions should be used when un-restricting or +// un-promoting user. +// +// member.Rights = tele.NoRestrictions() +// b.Restrict(chat, member) +// +func NoRestrictions() Rights { + return Rights{ + CanBeEdited: true, + CanChangeInfo: false, + CanPostMessages: false, + CanEditMessages: false, + CanDeleteMessages: false, + CanInviteUsers: false, + CanRestrictMembers: false, + CanPinMessages: false, + CanPromoteMembers: false, + CanSendMessages: true, + CanSendMedia: true, + CanSendPolls: true, + CanSendOther: true, + CanAddPreviews: true, + CanManageVideoChats: false, + CanManageChat: false, + } +} + +// AdminRights could be used to promote user to admin. +func AdminRights() Rights { + return Rights{ + CanBeEdited: true, + CanChangeInfo: true, + CanPostMessages: true, + CanEditMessages: true, + CanDeleteMessages: true, + CanInviteUsers: true, + CanRestrictMembers: true, + CanPinMessages: true, + CanPromoteMembers: true, + CanSendMessages: true, + CanSendMedia: true, + CanSendPolls: true, + CanSendOther: true, + CanAddPreviews: true, + CanManageVideoChats: true, + CanManageChat: true, + } +} + +// Forever is a ExpireUnixtime of "forever" banning. +func Forever() int64 { + return time.Now().Add(367 * 24 * time.Hour).Unix() +} + +// Ban will ban user from chat until `member.RestrictedUntil`. +func (b *Bot) Ban(chat *Chat, member *ChatMember, revokeMessages ...bool) error { + params := map[string]string{ + "chat_id": chat.Recipient(), + "user_id": member.User.Recipient(), + "until_date": strconv.FormatInt(member.RestrictedUntil, 10), + } + if len(revokeMessages) > 0 { + params["revoke_messages"] = strconv.FormatBool(revokeMessages[0]) + } + + _, err := b.Raw("kickChatMember", params) + return err +} + +// Unban will unban user from chat, who would have thought eh? +// forBanned does nothing if the user is not banned. +func (b *Bot) Unban(chat *Chat, user *User, forBanned ...bool) error { + params := map[string]string{ + "chat_id": chat.Recipient(), + "user_id": user.Recipient(), + } + + if len(forBanned) > 0 { + params["only_if_banned"] = strconv.FormatBool(forBanned[0]) + } + + _, err := b.Raw("unbanChatMember", params) + return err +} + +// Restrict lets you restrict a subset of member's rights until +// member.RestrictedUntil, such as: +// +// * can send messages +// * can send media +// * can send other +// * can add web page previews +// +func (b *Bot) Restrict(chat *Chat, member *ChatMember) error { + prv, until := member.Rights, member.RestrictedUntil + + params := map[string]interface{}{ + "chat_id": chat.Recipient(), + "user_id": member.User.Recipient(), + "until_date": strconv.FormatInt(until, 10), + } + embedRights(params, prv) + + _, err := b.Raw("restrictChatMember", params) + return err +} + +// Promote lets you update member's admin rights, such as: +// +// * can change info +// * can post messages +// * can edit messages +// * can delete messages +// * can invite users +// * can restrict members +// * can pin messages +// * can promote members +// +func (b *Bot) Promote(chat *Chat, member *ChatMember) error { + prv := member.Rights + + params := map[string]interface{}{ + "chat_id": chat.Recipient(), + "user_id": member.User.Recipient(), + "is_anonymous": member.Anonymous, + } + embedRights(params, prv) + + _, err := b.Raw("promoteChatMember", params) + return err +} + +// AdminsOf returns a member list of chat admins. +// +// On success, returns an Array of ChatMember objects that +// contains information about all chat administrators except other bots. +// +// If the chat is a group or a supergroup and +// no administrators were appointed, only the creator will be returned. +// +func (b *Bot) AdminsOf(chat *Chat) ([]ChatMember, error) { + params := map[string]string{ + "chat_id": chat.Recipient(), + } + + data, err := b.Raw("getChatAdministrators", params) + if err != nil { + return nil, err + } + + var resp struct { + Result []ChatMember + } + if err := json.Unmarshal(data, &resp); err != nil { + return nil, wrapError(err) + } + return resp.Result, nil +} + +// Len returns the number of members in a chat. +func (b *Bot) Len(chat *Chat) (int, error) { + params := map[string]string{ + "chat_id": chat.Recipient(), + } + + data, err := b.Raw("getChatMembersCount", params) + if err != nil { + return 0, err + } + + var resp struct { + Result int + } + if err := json.Unmarshal(data, &resp); err != nil { + return 0, wrapError(err) + } + return resp.Result, nil +} + +// SetAdminTitle sets a custom title for an administrator. +// A title should be 0-16 characters length, emoji are not allowed. +func (b *Bot) SetAdminTitle(chat *Chat, user *User, title string) error { + params := map[string]string{ + "chat_id": chat.Recipient(), + "user_id": user.Recipient(), + "custom_title": title, + } + + _, err := b.Raw("setChatAdministratorCustomTitle", params) + return err +} + +// BanSenderChat will use this method to ban a channel chat in a supergroup or a channel. +// Until the chat is unbanned, the owner of the banned chat won't be able +// to send messages on behalf of any of their channels. +func (b *Bot) BanSenderChat(chat *Chat, sender Recipient) error { + params := map[string]string{ + "chat_id": chat.Recipient(), + "sender_chat_id": sender.Recipient(), + } + + _, err := b.Raw("banChatSenderChat", params) + return err +} + +// UnbanSenderChat will use this method to unban a previously banned channel chat in a supergroup or channel. +// The bot must be an administrator for this to work and must have the appropriate administrator rights. +func (b *Bot) UnbanSenderChat(chat *Chat, sender Recipient) error { + params := map[string]string{ + "chat_id": chat.Recipient(), + "sender_chat_id": sender.Recipient(), + } + + _, err := b.Raw("unbanChatSenderChat", params) + return err +} + +// DefaultRights returns the current default administrator rights of the bot. +func (b *Bot) DefaultRights(forChannels bool) (*Rights, error) { + params := map[string]bool{ + "for_channels": forChannels, + } + + data, err := b.Raw("getMyDefaultAdministratorRights", params) + if err != nil { + return nil, err + } + + var resp struct { + Result *Rights + } + if err := json.Unmarshal(data, &resp); err != nil { + return nil, wrapError(err) + } + return resp.Result, nil +} + +// SetDefaultRights changes the default administrator rights requested by the bot +// when it's added as an administrator to groups or channels. +func (b *Bot) SetDefaultRights(rights Rights, forChannels bool) error { + params := map[string]interface{}{ + "rights": rights, + "for_channels": forChannels, + } + + _, err := b.Raw("setMyDefaultAdministratorRights", params) + return err +} + +func embedRights(p map[string]interface{}, rights Rights) { + data, _ := json.Marshal(rights) + _ = json.Unmarshal(data, &p) +} diff --git a/vendor/gopkg.in/telebot.v3/api.go b/vendor/gopkg.in/telebot.v3/api.go new file mode 100644 index 00000000000..6ed4eb146fe --- /dev/null +++ b/vendor/gopkg.in/telebot.v3/api.go @@ -0,0 +1,330 @@ +package telebot + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "log" + "mime/multipart" + "net/http" + "os" + "strconv" + "strings" + "time" +) + +// Raw lets you call any method of Bot API manually. +// It also handles API errors, so you only need to unwrap +// result field from json data. +func (b *Bot) Raw(method string, payload interface{}) ([]byte, error) { + url := b.URL + "/bot" + b.Token + "/" + method + + var buf bytes.Buffer + if err := json.NewEncoder(&buf).Encode(payload); err != nil { + return nil, err + } + + // Cancel the request immediately without waiting for the timeout when bot is about to stop. + // This may become important if doing long polling with long timeout. + exit := make(chan struct{}) + defer close(exit) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + go func() { + select { + case <-b.stopClient: + cancel() + case <-exit: + } + }() + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, &buf) + if err != nil { + return nil, wrapError(err) + } + req.Header.Set("Content-Type", "application/json") + + resp, err := b.client.Do(req) + if err != nil { + return nil, wrapError(err) + } + resp.Close = true + defer resp.Body.Close() + + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, wrapError(err) + } + + if b.verbose { + verbose(method, payload, data) + } + + // returning data as well + return data, extractOk(data) +} + +func (b *Bot) sendFiles(method string, files map[string]File, params map[string]string) ([]byte, error) { + rawFiles := make(map[string]interface{}) + for name, f := range files { + switch { + case f.InCloud(): + params[name] = f.FileID + case f.FileURL != "": + params[name] = f.FileURL + case f.OnDisk(): + rawFiles[name] = f.FileLocal + case f.FileReader != nil: + rawFiles[name] = f.FileReader + default: + return nil, fmt.Errorf("telebot: file for field %s doesn't exist", name) + } + } + + if len(rawFiles) == 0 { + return b.Raw(method, params) + } + + pipeReader, pipeWriter := io.Pipe() + writer := multipart.NewWriter(pipeWriter) + + go func() { + defer pipeWriter.Close() + + for field, file := range rawFiles { + if err := addFileToWriter(writer, files[field].fileName, field, file); err != nil { + pipeWriter.CloseWithError(err) + return + } + } + for field, value := range params { + if err := writer.WriteField(field, value); err != nil { + pipeWriter.CloseWithError(err) + return + } + } + if err := writer.Close(); err != nil { + pipeWriter.CloseWithError(err) + return + } + }() + + url := b.URL + "/bot" + b.Token + "/" + method + + resp, err := b.client.Post(url, writer.FormDataContentType(), pipeReader) + if err != nil { + err = wrapError(err) + pipeReader.CloseWithError(err) + return nil, err + } + resp.Close = true + defer resp.Body.Close() + + if resp.StatusCode == http.StatusInternalServerError { + return nil, ErrInternal + } + + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, wrapError(err) + } + + return data, extractOk(data) +} + +func addFileToWriter(writer *multipart.Writer, filename, field string, file interface{}) error { + var reader io.Reader + if r, ok := file.(io.Reader); ok { + reader = r + } else if path, ok := file.(string); ok { + f, err := os.Open(path) + if err != nil { + return err + } + defer f.Close() + reader = f + } else { + return fmt.Errorf("telebot: file for field %v should be io.ReadCloser or string", field) + } + + part, err := writer.CreateFormFile(field, filename) + if err != nil { + return err + } + + _, err = io.Copy(part, reader) + return err +} + +func (b *Bot) sendText(to Recipient, text string, opt *SendOptions) (*Message, error) { + params := map[string]string{ + "chat_id": to.Recipient(), + "text": text, + } + b.embedSendOptions(params, opt) + + data, err := b.Raw("sendMessage", params) + if err != nil { + return nil, err + } + + return extractMessage(data) +} + +func (b *Bot) sendMedia(media Media, params map[string]string, files map[string]File) (*Message, error) { + kind := media.MediaType() + what := "send" + strings.Title(kind) + + if kind == "videoNote" { + kind = "video_note" + } + + sendFiles := map[string]File{kind: *media.MediaFile()} + for k, v := range files { + sendFiles[k] = v + } + + data, err := b.sendFiles(what, sendFiles, params) + if err != nil { + return nil, err + } + + return extractMessage(data) +} + +func (b *Bot) getMe() (*User, error) { + data, err := b.Raw("getMe", nil) + if err != nil { + return nil, err + } + + var resp struct { + Result *User + } + if err := json.Unmarshal(data, &resp); err != nil { + return nil, wrapError(err) + } + return resp.Result, nil +} + +func (b *Bot) getUpdates(offset, limit int, timeout time.Duration, allowed []string) ([]Update, error) { + params := map[string]string{ + "offset": strconv.Itoa(offset), + "timeout": strconv.Itoa(int(timeout / time.Second)), + } + + data, _ := json.Marshal(allowed) + params["allowed_updates"] = string(data) + + if limit != 0 { + params["limit"] = strconv.Itoa(limit) + } + + data, err := b.Raw("getUpdates", params) + if err != nil { + return nil, err + } + + var resp struct { + Result []Update + } + if err := json.Unmarshal(data, &resp); err != nil { + return nil, wrapError(err) + } + return resp.Result, nil +} + +// extractOk checks given result for error. If result is ok returns nil. +// In other cases it extracts API error. If error is not presented +// in errors.go, it will be prefixed with `unknown` keyword. +func extractOk(data []byte) error { + var e struct { + Ok bool `json:"ok"` + Code int `json:"error_code"` + Description string `json:"description"` + Parameters map[string]interface{} `json:"parameters"` + } + if json.NewDecoder(bytes.NewReader(data)).Decode(&e) != nil { + return nil // FIXME + } + if e.Ok { + return nil + } + + err := Err(e.Description) + switch err { + case nil: + case ErrGroupMigrated: + migratedTo, ok := e.Parameters["migrate_to_chat_id"] + if !ok { + return NewError(e.Code, e.Description) + } + + return GroupError{ + err: err.(*Error), + MigratedTo: int64(migratedTo.(float64)), + } + default: + return err + } + + switch e.Code { + case http.StatusTooManyRequests: + retryAfter, ok := e.Parameters["retry_after"] + if !ok { + return NewError(e.Code, e.Description) + } + + err = FloodError{ + err: NewError(e.Code, e.Description), + RetryAfter: int(retryAfter.(float64)), + } + default: + err = fmt.Errorf("telegram: %s (%d)", e.Description, e.Code) + } + + return err +} + +// extractMessage extracts common Message result from given data. +// Should be called after extractOk or b.Raw() to handle possible errors. +func extractMessage(data []byte) (*Message, error) { + var resp struct { + Result *Message + } + if err := json.Unmarshal(data, &resp); err != nil { + var resp struct { + Result bool + } + if err := json.Unmarshal(data, &resp); err != nil { + return nil, wrapError(err) + } + if resp.Result { + return nil, ErrTrueResult + } + return nil, wrapError(err) + } + return resp.Result, nil +} + +func verbose(method string, payload interface{}, data []byte) { + body, _ := json.Marshal(payload) + body = bytes.ReplaceAll(body, []byte(`\"`), []byte(`"`)) + body = bytes.ReplaceAll(body, []byte(`"{`), []byte(`{`)) + body = bytes.ReplaceAll(body, []byte(`}"`), []byte(`}`)) + + indent := func(b []byte) string { + var buf bytes.Buffer + json.Indent(&buf, b, "", " ") + return buf.String() + } + + log.Printf( + "[verbose] telebot: sent request\nMethod: %v\nParams: %v\nResponse: %v", + method, indent(body), indent(data), + ) +} diff --git a/vendor/gopkg.in/telebot.v3/bot.go b/vendor/gopkg.in/telebot.v3/bot.go new file mode 100644 index 00000000000..52d6bcc0f99 --- /dev/null +++ b/vendor/gopkg.in/telebot.v3/bot.go @@ -0,0 +1,1163 @@ +package telebot + +import ( + "encoding/json" + "fmt" + "io" + "log" + "net/http" + "os" + "regexp" + "strconv" + "strings" + "time" +) + +// NewBot does try to build a Bot with token `token`, which +// is a secret API key assigned to particular bot. +func NewBot(pref Settings) (*Bot, error) { + if pref.Updates == 0 { + pref.Updates = 100 + } + + client := pref.Client + if client == nil { + client = &http.Client{Timeout: time.Minute} + } + + if pref.URL == "" { + pref.URL = DefaultApiURL + } + if pref.Poller == nil { + pref.Poller = &LongPoller{} + } + if pref.OnError == nil { + pref.OnError = defaultOnError + } + + bot := &Bot{ + Token: pref.Token, + URL: pref.URL, + Poller: pref.Poller, + onError: pref.OnError, + + Updates: make(chan Update, pref.Updates), + handlers: make(map[string]HandlerFunc), + stop: make(chan chan struct{}), + + synchronous: pref.Synchronous, + verbose: pref.Verbose, + parseMode: pref.ParseMode, + client: client, + } + + if pref.Offline { + bot.Me = &User{} + } else { + user, err := bot.getMe() + if err != nil { + return nil, err + } + bot.Me = user + } + + bot.group = bot.Group() + return bot, nil +} + +// Bot represents a separate Telegram bot instance. +type Bot struct { + Me *User + Token string + URL string + Updates chan Update + Poller Poller + onError func(error, Context) + + group *Group + handlers map[string]HandlerFunc + synchronous bool + verbose bool + parseMode ParseMode + stop chan chan struct{} + client *http.Client + stopClient chan struct{} +} + +// Settings represents a utility struct for passing certain +// properties of a bot around and is required to make bots. +type Settings struct { + URL string + Token string + + // Updates channel capacity, defaulted to 100. + Updates int + + // Poller is the provider of Updates. + Poller Poller + + // Synchronous prevents handlers from running in parallel. + // It makes ProcessUpdate return after the handler is finished. + Synchronous bool + + // Verbose forces bot to log all upcoming requests. + // Use for debugging purposes only. + Verbose bool + + // ParseMode used to set default parse mode of all sent messages. + // It attaches to every send, edit or whatever method. You also + // will be able to override the default mode by passing a new one. + ParseMode ParseMode + + // OnError is a callback function that will get called on errors + // resulted from the handler. It is used as post-middleware function. + // Notice that context can be nil. + OnError func(error, Context) + + // HTTP Client used to make requests to telegram api + Client *http.Client + + // Offline allows to create a bot without network for testing purposes. + Offline bool +} + +var defaultOnError = func(err error, c Context) { + if c != nil { + log.Println(c.Update().ID, err) + } else { + log.Println(err) + } +} + +func (b *Bot) OnError(err error, c Context) { + b.onError(err, c) +} + +func (b *Bot) debug(err error) { + if b.verbose { + b.OnError(err, nil) + } +} + +// Group returns a new group. +func (b *Bot) Group() *Group { + return &Group{b: b} +} + +// Use adds middleware to the global bot chain. +func (b *Bot) Use(middleware ...MiddlewareFunc) { + b.group.Use(middleware...) +} + +var ( + cmdRx = regexp.MustCompile(`^(/\w+)(@(\w+))?(\s|$)(.+)?`) + cbackRx = regexp.MustCompile(`^\f([-\w]+)(\|(.+))?$`) +) + +// Handle lets you set the handler for some command name or +// one of the supported endpoints. It also applies middleware +// if such passed to the function. +// +// Example: +// +// b.Handle("/start", func (c tele.Context) error { +// return c.Reply("Hello!") +// }) +// +// b.Handle(&inlineButton, func (c tele.Context) error { +// return c.Respond(&tele.CallbackResponse{Text: "Hello!"}) +// }) +// +// Middleware usage: +// +// b.Handle("/ban", onBan, middleware.Whitelist(ids...)) +// +func (b *Bot) Handle(endpoint interface{}, h HandlerFunc, m ...MiddlewareFunc) { + if len(b.group.middleware) > 0 { + m = append(b.group.middleware, m...) + } + + handler := func(c Context) error { + return applyMiddleware(h, m...)(c) + } + + switch end := endpoint.(type) { + case string: + b.handlers[end] = handler + case CallbackEndpoint: + b.handlers[end.CallbackUnique()] = handler + default: + panic("telebot: unsupported endpoint") + } +} + +// Start brings bot into motion by consuming incoming +// updates (see Bot.Updates channel). +func (b *Bot) Start() { + if b.Poller == nil { + panic("telebot: can't start without a poller") + } + + // do nothing if called twice + if b.stopClient != nil { + return + } + b.stopClient = make(chan struct{}) + + stop := make(chan struct{}) + stopConfirm := make(chan struct{}) + + go func() { + b.Poller.Poll(b, b.Updates, stop) + close(stopConfirm) + }() + + for { + select { + // handle incoming updates + case upd := <-b.Updates: + b.ProcessUpdate(upd) + // call to stop polling + case confirm := <-b.stop: + close(stop) + <-stopConfirm + close(confirm) + b.stopClient = nil + return + } + } +} + +// Stop gracefully shuts the poller down. +func (b *Bot) Stop() { + if b.stopClient != nil { + close(b.stopClient) + } + confirm := make(chan struct{}) + b.stop <- confirm + <-confirm +} + +// NewMarkup simply returns newly created markup instance. +func (b *Bot) NewMarkup() *ReplyMarkup { + return &ReplyMarkup{} +} + +// NewContext returns a new native context object, +// field by the passed update. +func (b *Bot) NewContext(u Update) Context { + return &nativeContext{ + b: b, + u: u, + } +} + +// Send accepts 2+ arguments, starting with destination chat, followed by +// some Sendable (or string!) and optional send options. +// +// NOTE: +// Since most arguments are of type interface{}, but have pointer +// method receivers, make sure to pass them by-pointer, NOT by-value. +// +// What is a send option exactly? It can be one of the following types: +// +// - *SendOptions (the actual object accepted by Telegram API) +// - *ReplyMarkup (a component of SendOptions) +// - Option (a shortcut flag for popular options) +// - ParseMode (HTML, Markdown, etc) +// +func (b *Bot) Send(to Recipient, what interface{}, opts ...interface{}) (*Message, error) { + if to == nil { + return nil, ErrBadRecipient + } + + sendOpts := extractOptions(opts) + + switch object := what.(type) { + case string: + return b.sendText(to, object, sendOpts) + case Sendable: + return object.Send(b, to, sendOpts) + default: + return nil, ErrUnsupportedWhat + } +} + +// SendAlbum sends multiple instances of media as a single message. +// From all existing options, it only supports tele.Silent. +func (b *Bot) SendAlbum(to Recipient, a Album, opts ...interface{}) ([]Message, error) { + if to == nil { + return nil, ErrBadRecipient + } + + sendOpts := extractOptions(opts) + media := make([]string, len(a)) + files := make(map[string]File) + + for i, x := range a { + var ( + repr string + data []byte + file = x.MediaFile() + ) + + switch { + case file.InCloud(): + repr = file.FileID + case file.FileURL != "": + repr = file.FileURL + case file.OnDisk() || file.FileReader != nil: + repr = "attach://" + strconv.Itoa(i) + files[strconv.Itoa(i)] = *file + default: + return nil, fmt.Errorf("telebot: album entry #%d does not exist", i) + } + + im := x.InputMedia() + im.Media = repr + + if len(sendOpts.Entities) > 0 { + im.Entities = sendOpts.Entities + } else { + im.ParseMode = sendOpts.ParseMode + } + + data, _ = json.Marshal(im) + media[i] = string(data) + } + + params := map[string]string{ + "chat_id": to.Recipient(), + "media": "[" + strings.Join(media, ",") + "]", + } + b.embedSendOptions(params, sendOpts) + + data, err := b.sendFiles("sendMediaGroup", files, params) + if err != nil { + return nil, err + } + + var resp struct { + Result []Message + } + if err := json.Unmarshal(data, &resp); err != nil { + return nil, wrapError(err) + } + + for attachName := range files { + i, _ := strconv.Atoi(attachName) + r := resp.Result[i] + + var newID string + switch { + case r.Photo != nil: + newID = r.Photo.FileID + case r.Video != nil: + newID = r.Video.FileID + case r.Audio != nil: + newID = r.Audio.FileID + case r.Document != nil: + newID = r.Document.FileID + } + + a[i].MediaFile().FileID = newID + } + + return resp.Result, nil +} + +// Reply behaves just like Send() with an exception of "reply-to" indicator. +// This function will panic upon nil Message. +func (b *Bot) Reply(to *Message, what interface{}, opts ...interface{}) (*Message, error) { + sendOpts := extractOptions(opts) + if sendOpts == nil { + sendOpts = &SendOptions{} + } + + sendOpts.ReplyTo = to + return b.Send(to.Chat, what, sendOpts) +} + +// Forward behaves just like Send() but of all options it only supports Silent (see Bots API). +// This function will panic upon nil Editable. +func (b *Bot) Forward(to Recipient, msg Editable, opts ...interface{}) (*Message, error) { + if to == nil { + return nil, ErrBadRecipient + } + msgID, chatID := msg.MessageSig() + + params := map[string]string{ + "chat_id": to.Recipient(), + "from_chat_id": strconv.FormatInt(chatID, 10), + "message_id": msgID, + } + + sendOpts := extractOptions(opts) + b.embedSendOptions(params, sendOpts) + + data, err := b.Raw("forwardMessage", params) + if err != nil { + return nil, err + } + + return extractMessage(data) +} + +// Copy behaves just like Forward() but the copied message doesn't have a link to the original message (see Bots API). +// +// This function will panic upon nil Editable. +func (b *Bot) Copy(to Recipient, msg Editable, options ...interface{}) (*Message, error) { + if to == nil { + return nil, ErrBadRecipient + } + msgID, chatID := msg.MessageSig() + + params := map[string]string{ + "chat_id": to.Recipient(), + "from_chat_id": strconv.FormatInt(chatID, 10), + "message_id": msgID, + } + + sendOpts := extractOptions(options) + b.embedSendOptions(params, sendOpts) + + data, err := b.Raw("copyMessage", params) + if err != nil { + return nil, err + } + + return extractMessage(data) +} + +// Edit is magic, it lets you change already sent message. +// This function will panic upon nil Editable. +// +// If edited message is sent by the bot, returns it, +// otherwise returns nil and ErrTrueResult. +// +// Use cases: +// +// b.Edit(m, m.Text, newMarkup) +// b.Edit(m, "new text", tele.ModeHTML) +// b.Edit(m, &tele.ReplyMarkup{...}) +// b.Edit(m, &tele.Photo{File: ...}) +// b.Edit(m, tele.Location{42.1337, 69.4242}) +// b.Edit(c, "edit inline message from the callback") +// b.Edit(r, "edit message from chosen inline result") +// +func (b *Bot) Edit(msg Editable, what interface{}, opts ...interface{}) (*Message, error) { + var ( + method string + params = make(map[string]string) + ) + + switch v := what.(type) { + case *ReplyMarkup: + return b.EditReplyMarkup(msg, v) + case Inputtable: + return b.EditMedia(msg, v, opts...) + case string: + method = "editMessageText" + params["text"] = v + case Location: + method = "editMessageLiveLocation" + params["latitude"] = fmt.Sprintf("%f", v.Lat) + params["longitude"] = fmt.Sprintf("%f", v.Lng) + + if v.HorizontalAccuracy != nil { + params["horizontal_accuracy"] = fmt.Sprintf("%f", *v.HorizontalAccuracy) + } + if v.Heading != 0 { + params["heading"] = strconv.Itoa(v.Heading) + } + if v.AlertRadius != 0 { + params["proximity_alert_radius"] = strconv.Itoa(v.AlertRadius) + } + default: + return nil, ErrUnsupportedWhat + } + + msgID, chatID := msg.MessageSig() + + if chatID == 0 { // if inline message + params["inline_message_id"] = msgID + } else { + params["chat_id"] = strconv.FormatInt(chatID, 10) + params["message_id"] = msgID + } + + sendOpts := extractOptions(opts) + b.embedSendOptions(params, sendOpts) + + data, err := b.Raw(method, params) + if err != nil { + return nil, err + } + + return extractMessage(data) +} + +// EditReplyMarkup edits reply markup of already sent message. +// This function will panic upon nil Editable. +// Pass nil or empty ReplyMarkup to delete it from the message. +// +// If edited message is sent by the bot, returns it, +// otherwise returns nil and ErrTrueResult. +// +func (b *Bot) EditReplyMarkup(msg Editable, markup *ReplyMarkup) (*Message, error) { + msgID, chatID := msg.MessageSig() + params := make(map[string]string) + + if chatID == 0 { // if inline message + params["inline_message_id"] = msgID + } else { + params["chat_id"] = strconv.FormatInt(chatID, 10) + params["message_id"] = msgID + } + + if markup == nil { + // will delete reply markup + markup = &ReplyMarkup{} + } + + processButtons(markup.InlineKeyboard) + data, _ := json.Marshal(markup) + params["reply_markup"] = string(data) + + data, err := b.Raw("editMessageReplyMarkup", params) + if err != nil { + return nil, err + } + + return extractMessage(data) +} + +// EditCaption edits already sent photo caption with known recipient and message id. +// This function will panic upon nil Editable. +// +// If edited message is sent by the bot, returns it, +// otherwise returns nil and ErrTrueResult. +// +func (b *Bot) EditCaption(msg Editable, caption string, opts ...interface{}) (*Message, error) { + msgID, chatID := msg.MessageSig() + + params := map[string]string{ + "caption": caption, + } + + if chatID == 0 { // if inline message + params["inline_message_id"] = msgID + } else { + params["chat_id"] = strconv.FormatInt(chatID, 10) + params["message_id"] = msgID + } + + sendOpts := extractOptions(opts) + b.embedSendOptions(params, sendOpts) + + data, err := b.Raw("editMessageCaption", params) + if err != nil { + return nil, err + } + + return extractMessage(data) +} + +// EditMedia edits already sent media with known recipient and message id. +// This function will panic upon nil Editable. +// +// If edited message is sent by the bot, returns it, +// otherwise returns nil and ErrTrueResult. +// +// Use cases: +// +// b.EditMedia(m, &tele.Photo{File: tele.FromDisk("chicken.jpg")}) +// b.EditMedia(m, &tele.Video{File: tele.FromURL("http://video.mp4")}) +// +func (b *Bot) EditMedia(msg Editable, media Inputtable, opts ...interface{}) (*Message, error) { + var ( + repr string + file = media.MediaFile() + files = make(map[string]File) + + thumb *Photo + thumbName = "thumb" + ) + + switch { + case file.InCloud(): + repr = file.FileID + case file.FileURL != "": + repr = file.FileURL + case file.OnDisk() || file.FileReader != nil: + s := file.FileLocal + if file.FileReader != nil { + s = "0" + } else if s == thumbName { + thumbName = "thumb2" + } + + repr = "attach://" + s + files[s] = *file + default: + return nil, fmt.Errorf("telebot: cannot edit media, it does not exist") + } + + switch m := media.(type) { + case *Video: + thumb = m.Thumbnail + case *Audio: + thumb = m.Thumbnail + case *Document: + thumb = m.Thumbnail + case *Animation: + thumb = m.Thumbnail + } + + msgID, chatID := msg.MessageSig() + params := make(map[string]string) + + sendOpts := extractOptions(opts) + b.embedSendOptions(params, sendOpts) + + im := media.InputMedia() + im.Media = repr + + if len(sendOpts.Entities) > 0 { + im.Entities = sendOpts.Entities + } else { + im.ParseMode = sendOpts.ParseMode + } + + if thumb != nil { + im.Thumbnail = "attach://" + thumbName + files[thumbName] = *thumb.MediaFile() + } + + data, _ := json.Marshal(im) + params["media"] = string(data) + + if chatID == 0 { // if inline message + params["inline_message_id"] = msgID + } else { + params["chat_id"] = strconv.FormatInt(chatID, 10) + params["message_id"] = msgID + } + + data, err := b.sendFiles("editMessageMedia", files, params) + if err != nil { + return nil, err + } + + return extractMessage(data) +} + +// Delete removes the message, including service messages. +// This function will panic upon nil Editable. +// +// - A message can only be deleted if it was sent less than 48 hours ago. +// - A dice message in a private chat can only be deleted if it was sent more than 24 hours ago. +// - Bots can delete outgoing messages in private chats, groups, and supergroups. +// - Bots can delete incoming messages in private chats. +// - Bots granted can_post_messages permissions can delete outgoing messages in channels. +// - If the bot is an administrator of a group, it can delete any message there. +// - If the bot has can_delete_messages permission in a supergroup or a +// channel, it can delete any message there. +// +func (b *Bot) Delete(msg Editable) error { + msgID, chatID := msg.MessageSig() + + params := map[string]string{ + "chat_id": strconv.FormatInt(chatID, 10), + "message_id": msgID, + } + + _, err := b.Raw("deleteMessage", params) + return err +} + +// Notify updates the chat action for recipient. +// +// Chat action is a status message that recipient would see where +// you typically see "Harry is typing" status message. The only +// difference is that bots' chat actions live only for 5 seconds +// and die just once the client receives a message from the bot. +// +// Currently, Telegram supports only a narrow range of possible +// actions, these are aligned as constants of this package. +// +func (b *Bot) Notify(to Recipient, action ChatAction) error { + if to == nil { + return ErrBadRecipient + } + + params := map[string]string{ + "chat_id": to.Recipient(), + "action": string(action), + } + + _, err := b.Raw("sendChatAction", params) + return err +} + +// Ship replies to the shipping query, if you sent an invoice +// requesting an address and the parameter is_flexible was specified. +// +// Example: +// +// b.Ship(query) // OK +// b.Ship(query, opts...) // OK with options +// b.Ship(query, "Oops!") // Error message +// +func (b *Bot) Ship(query *ShippingQuery, what ...interface{}) error { + params := map[string]string{ + "shipping_query_id": query.ID, + } + + if len(what) == 0 { + params["ok"] = "true" + } else if s, ok := what[0].(string); ok { + params["ok"] = "false" + params["error_message"] = s + } else { + var opts []ShippingOption + for _, v := range what { + opt, ok := v.(ShippingOption) + if !ok { + return ErrUnsupportedWhat + } + opts = append(opts, opt) + } + + params["ok"] = "true" + data, _ := json.Marshal(opts) + params["shipping_options"] = string(data) + } + + _, err := b.Raw("answerShippingQuery", params) + return err +} + +// Accept finalizes the deal. +func (b *Bot) Accept(query *PreCheckoutQuery, errorMessage ...string) error { + params := map[string]string{ + "pre_checkout_query_id": query.ID, + } + + if len(errorMessage) == 0 { + params["ok"] = "true" + } else { + params["ok"] = "False" + params["error_message"] = errorMessage[0] + } + + _, err := b.Raw("answerPreCheckoutQuery", params) + return err +} + +// Respond sends a response for a given callback query. A callback can +// only be responded to once, subsequent attempts to respond to the same callback +// will result in an error. +// +// Example: +// +// b.Respond(c) +// b.Respond(c, response) +// +func (b *Bot) Respond(c *Callback, resp ...*CallbackResponse) error { + var r *CallbackResponse + if resp == nil { + r = &CallbackResponse{} + } else { + r = resp[0] + } + + r.CallbackID = c.ID + _, err := b.Raw("answerCallbackQuery", r) + return err +} + +// Answer sends a response for a given inline query. A query can only +// be responded to once, subsequent attempts to respond to the same query +// will result in an error. +func (b *Bot) Answer(query *Query, resp *QueryResponse) error { + resp.QueryID = query.ID + + for _, result := range resp.Results { + result.Process(b) + } + + _, err := b.Raw("answerInlineQuery", resp) + return err +} + +// AnswerWebApp sends a response for a query from Web App and returns +// information about an inline message sent by a Web App on behalf of a user +func (b *Bot) AnswerWebApp(query *Query, r Result) (*WebAppMessage, error) { + r.Process(b) + + params := map[string]interface{}{ + "web_app_query_id": query.ID, + "result": r, + } + + data, err := b.Raw("answerWebAppQuery", params) + if err != nil { + return nil, err + } + + var resp struct { + Result *WebAppMessage + } + if err := json.Unmarshal(data, &resp); err != nil { + return nil, wrapError(err) + } + + return resp.Result, err +} + +// FileByID returns full file object including File.FilePath, allowing you to +// download the file from the server. +// +// Usually, Telegram-provided File objects miss FilePath so you might need to +// perform an additional request to fetch them. +// +func (b *Bot) FileByID(fileID string) (File, error) { + params := map[string]string{ + "file_id": fileID, + } + + data, err := b.Raw("getFile", params) + if err != nil { + return File{}, err + } + + var resp struct { + Result File + } + if err := json.Unmarshal(data, &resp); err != nil { + return File{}, wrapError(err) + } + return resp.Result, nil +} + +// Download saves the file from Telegram servers locally. +// Maximum file size to download is 20 MB. +func (b *Bot) Download(file *File, localFilename string) error { + reader, err := b.File(file) + if err != nil { + return err + } + defer reader.Close() + + out, err := os.Create(localFilename) + if err != nil { + return wrapError(err) + } + defer out.Close() + + _, err = io.Copy(out, reader) + if err != nil { + return wrapError(err) + } + + file.FileLocal = localFilename + return nil +} + +// File gets a file from Telegram servers. +func (b *Bot) File(file *File) (io.ReadCloser, error) { + f, err := b.FileByID(file.FileID) + if err != nil { + return nil, err + } + + url := b.URL + "/file/bot" + b.Token + "/" + f.FilePath + file.FilePath = f.FilePath // saving file path + + req, err := http.NewRequest(http.MethodGet, url, nil) + if err != nil { + return nil, wrapError(err) + } + + resp, err := b.client.Do(req) + if err != nil { + return nil, wrapError(err) + } + + if resp.StatusCode != http.StatusOK { + resp.Body.Close() + return nil, fmt.Errorf("telebot: expected status 200 but got %s", resp.Status) + } + + return resp.Body, nil +} + +// StopLiveLocation stops broadcasting live message location +// before Location.LivePeriod expires. +// +// It supports ReplyMarkup. +// This function will panic upon nil Editable. +// +// If the message is sent by the bot, returns it, +// otherwise returns nil and ErrTrueResult. +// +func (b *Bot) StopLiveLocation(msg Editable, opts ...interface{}) (*Message, error) { + msgID, chatID := msg.MessageSig() + + params := map[string]string{ + "chat_id": strconv.FormatInt(chatID, 10), + "message_id": msgID, + } + + sendOpts := extractOptions(opts) + b.embedSendOptions(params, sendOpts) + + data, err := b.Raw("stopMessageLiveLocation", params) + if err != nil { + return nil, err + } + + return extractMessage(data) +} + +// StopPoll stops a poll which was sent by the bot and returns +// the stopped Poll object with the final results. +// +// It supports ReplyMarkup. +// This function will panic upon nil Editable. +// +func (b *Bot) StopPoll(msg Editable, opts ...interface{}) (*Poll, error) { + msgID, chatID := msg.MessageSig() + + params := map[string]string{ + "chat_id": strconv.FormatInt(chatID, 10), + "message_id": msgID, + } + + sendOpts := extractOptions(opts) + b.embedSendOptions(params, sendOpts) + + data, err := b.Raw("stopPoll", params) + if err != nil { + return nil, err + } + + var resp struct { + Result *Poll + } + if err := json.Unmarshal(data, &resp); err != nil { + return nil, wrapError(err) + } + return resp.Result, nil +} + +// Leave makes bot leave a group, supergroup or channel. +func (b *Bot) Leave(chat *Chat) error { + params := map[string]string{ + "chat_id": chat.Recipient(), + } + + _, err := b.Raw("leaveChat", params) + return err +} + +// Pin pins a message in a supergroup or a channel. +// +// It supports Silent option. +// This function will panic upon nil Editable. +// +func (b *Bot) Pin(msg Editable, opts ...interface{}) error { + msgID, chatID := msg.MessageSig() + + params := map[string]string{ + "chat_id": strconv.FormatInt(chatID, 10), + "message_id": msgID, + } + + sendOpts := extractOptions(opts) + b.embedSendOptions(params, sendOpts) + + _, err := b.Raw("pinChatMessage", params) + return err +} + +// Unpin unpins a message in a supergroup or a channel. +// It supports tb.Silent option. +func (b *Bot) Unpin(chat *Chat, messageID ...int) error { + params := map[string]string{ + "chat_id": chat.Recipient(), + } + if len(messageID) > 0 { + params["message_id"] = strconv.Itoa(messageID[0]) + } + + _, err := b.Raw("unpinChatMessage", params) + return err +} + +// UnpinAll unpins all messages in a supergroup or a channel. +// It supports tb.Silent option. +func (b *Bot) UnpinAll(chat *Chat) error { + params := map[string]string{ + "chat_id": chat.Recipient(), + } + + _, err := b.Raw("unpinAllChatMessages", params) + return err +} + +// ChatByID fetches chat info of its ID. +// +// Including current name of the user for one-on-one conversations, +// current username of a user, group or channel, etc. +// +func (b *Bot) ChatByID(id int64) (*Chat, error) { + return b.ChatByUsername(strconv.FormatInt(id, 10)) +} + +// ChatByUsername fetches chat info by its username. +func (b *Bot) ChatByUsername(name string) (*Chat, error) { + params := map[string]string{ + "chat_id": name, + } + + data, err := b.Raw("getChat", params) + if err != nil { + return nil, err + } + + var resp struct { + Result *Chat + } + if err := json.Unmarshal(data, &resp); err != nil { + return nil, wrapError(err) + } + if resp.Result.Type == ChatChannel && resp.Result.Username == "" { + resp.Result.Type = ChatChannelPrivate + } + return resp.Result, nil +} + +// ProfilePhotosOf returns list of profile pictures for a user. +func (b *Bot) ProfilePhotosOf(user *User) ([]Photo, error) { + params := map[string]string{ + "user_id": user.Recipient(), + } + + data, err := b.Raw("getUserProfilePhotos", params) + if err != nil { + return nil, err + } + + var resp struct { + Result struct { + Count int `json:"total_count"` + Photos []Photo `json:"photos"` + } + } + if err := json.Unmarshal(data, &resp); err != nil { + return nil, wrapError(err) + } + return resp.Result.Photos, nil +} + +// ChatMemberOf returns information about a member of a chat. +func (b *Bot) ChatMemberOf(chat, user Recipient) (*ChatMember, error) { + params := map[string]string{ + "chat_id": chat.Recipient(), + "user_id": user.Recipient(), + } + + data, err := b.Raw("getChatMember", params) + if err != nil { + return nil, err + } + + var resp struct { + Result *ChatMember + } + if err := json.Unmarshal(data, &resp); err != nil { + return nil, wrapError(err) + } + return resp.Result, nil +} + +// MenuButton returns the current value of the bot's menu button in a private chat, +// or the default menu button. +func (b *Bot) MenuButton(chat *User) (*MenuButton, error) { + params := map[string]string{ + "chat_id": chat.Recipient(), + } + + data, err := b.Raw("getChatMenuButton", params) + if err != nil { + return nil, err + } + + var resp struct { + Result *MenuButton + } + if err := json.Unmarshal(data, &resp); err != nil { + return nil, wrapError(err) + } + return resp.Result, nil +} + +// SetMenuButton changes the bot's menu button in a private chat, +// or the default menu button. +// +// It accepts two kinds of menu button arguments: +// +// - MenuButtonType for simple menu buttons (default, commands) +// - MenuButton complete structure for web_app menu button type +// +func (b *Bot) SetMenuButton(chat *User, mb interface{}) error { + params := map[string]interface{}{ + "chat_id": chat.Recipient(), + } + + switch v := mb.(type) { + case MenuButtonType: + params["menu_button"] = MenuButton{Type: v} + case *MenuButton: + params["menu_button"] = v + } + + _, err := b.Raw("setChatMenuButton", params) + return err +} + +// Logout logs out from the cloud Bot API server before launching the bot locally. +func (b *Bot) Logout() (bool, error) { + data, err := b.Raw("logOut", nil) + if err != nil { + return false, err + } + + var resp struct { + Result bool `json:"result"` + } + if err := json.Unmarshal(data, &resp); err != nil { + return false, wrapError(err) + } + + return resp.Result, nil +} + +// Close closes the bot instance before moving it from one local server to another. +func (b *Bot) Close() (bool, error) { + data, err := b.Raw("close", nil) + if err != nil { + return false, err + } + + var resp struct { + Result bool `json:"result"` + } + if err := json.Unmarshal(data, &resp); err != nil { + return false, wrapError(err) + } + + return resp.Result, nil +} diff --git a/vendor/gopkg.in/telebot.v3/callback.go b/vendor/gopkg.in/telebot.v3/callback.go new file mode 100644 index 00000000000..4bce60a1e46 --- /dev/null +++ b/vendor/gopkg.in/telebot.v3/callback.go @@ -0,0 +1,89 @@ +package telebot + +// CallbackEndpoint is an interface any element capable +// of responding to a callback `\f`. +type CallbackEndpoint interface { + CallbackUnique() string +} + +// Callback object represents a query from a callback button in an +// inline keyboard. +type Callback struct { + ID string `json:"id"` + + // For message sent to channels, Sender may be empty + Sender *User `json:"from"` + + // Message will be set if the button that originated the query + // was attached to a message sent by a bot. + Message *Message `json:"message"` + + // MessageID will be set if the button was attached to a message + // sent via the bot in inline mode. + MessageID string `json:"inline_message_id"` + + // Data associated with the callback button. Be aware that + // a bad client can send arbitrary data in this field. + Data string `json:"data"` + + // Unique displays an unique of the button from which the + // callback was fired. Sets immediately before the handling, + // while the Data field stores only with payload. + Unique string `json:"-"` +} + +// MessageSig satisfies Editable interface. +func (c *Callback) MessageSig() (string, int64) { + if c.IsInline() { + return c.MessageID, 0 + } + return c.Message.MessageSig() +} + +// IsInline says whether message is an inline message. +func (c *Callback) IsInline() bool { + return c.MessageID != "" +} + +// CallbackResponse builds a response to a Callback query. +type CallbackResponse struct { + // The ID of the callback to which this is a response. + // + // Note: Telebot sets this field automatically! + CallbackID string `json:"callback_query_id"` + + // Text of the notification. If not specified, nothing will be + // shown to the user. + Text string `json:"text,omitempty"` + + // (Optional) If true, an alert will be shown by the client instead + // of a notification at the top of the chat screen. Defaults to false. + ShowAlert bool `json:"show_alert,omitempty"` + + // (Optional) URL that will be opened by the user's client. + // If you have created a Game and accepted the conditions via + // @BotFather, specify the URL that opens your game. + // + // Note: this will only work if the query comes from a game + // callback button. Otherwise, you may use deep-linking: + // https://telegram.me/your_bot?start=XXXX + URL string `json:"url,omitempty"` +} + +// CallbackUnique returns ReplyButton.Text. +func (t *ReplyButton) CallbackUnique() string { + return t.Text +} + +// CallbackUnique returns InlineButton.Unique. +func (t *InlineButton) CallbackUnique() string { + return "\f" + t.Unique +} + +// CallbackUnique implements CallbackEndpoint. +func (t *Btn) CallbackUnique() string { + if t.Unique != "" { + return "\f" + t.Unique + } + return t.Text +} diff --git a/vendor/gopkg.in/telebot.v3/chat.go b/vendor/gopkg.in/telebot.v3/chat.go new file mode 100644 index 00000000000..ab767463ca8 --- /dev/null +++ b/vendor/gopkg.in/telebot.v3/chat.go @@ -0,0 +1,453 @@ +package telebot + +import ( + "encoding/json" + "strconv" + "time" +) + +// User object represents a Telegram user, bot. +type User struct { + ID int64 `json:"id"` + + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + Username string `json:"username"` + LanguageCode string `json:"language_code"` + IsBot bool `json:"is_bot"` + IsPremium bool `json:"is_premium"` + AddedToMenu bool `json:"added_to_attachment_menu"` + + // Returns only in getMe + CanJoinGroups bool `json:"can_join_groups"` + CanReadMessages bool `json:"can_read_all_group_messages"` + SupportsInline bool `json:"supports_inline_queries"` +} + +// Recipient returns user ID (see Recipient interface). +func (u *User) Recipient() string { + return strconv.FormatInt(u.ID, 10) +} + +// Chat object represents a Telegram user, bot, group or a channel. +type Chat struct { + ID int64 `json:"id"` + + // See ChatType and consts. + Type ChatType `json:"type"` + + // Won't be there for ChatPrivate. + Title string `json:"title"` + + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + Username string `json:"username"` + + // Returns only in getChat + Bio string `json:"bio,omitempty"` + Photo *ChatPhoto `json:"photo,omitempty"` + Description string `json:"description,omitempty"` + InviteLink string `json:"invite_link,omitempty"` + PinnedMessage *Message `json:"pinned_message,omitempty"` + Permissions *Rights `json:"permissions,omitempty"` + SlowMode int `json:"slow_mode_delay,omitempty"` + StickerSet string `json:"sticker_set_name,omitempty"` + CanSetStickerSet bool `json:"can_set_sticker_set,omitempty"` + LinkedChatID int64 `json:"linked_chat_id,omitempty"` + ChatLocation *ChatLocation `json:"location,omitempty"` + Private bool `json:"has_private_forwards,omitempty"` + Protected bool `json:"has_protected_content,omitempty"` + NoVoiceAndVideo bool `json:"has_restricted_voice_and_video_messages"` +} + +// Recipient returns chat ID (see Recipient interface). +func (c *Chat) Recipient() string { + return strconv.FormatInt(c.ID, 10) +} + +// ChatType represents one of the possible chat types. +type ChatType string + +const ( + ChatPrivate ChatType = "private" + ChatGroup ChatType = "group" + ChatSuperGroup ChatType = "supergroup" + ChatChannel ChatType = "channel" + ChatChannelPrivate ChatType = "privatechannel" +) + +// ChatLocation represents a location to which a chat is connected. +type ChatLocation struct { + Location Location `json:"location,omitempty"` + Address string `json:"address,omitempty"` +} + +// ChatPhoto object represents a chat photo. +type ChatPhoto struct { + // File identifiers of small (160x160) chat photo + SmallFileID string `json:"small_file_id"` + SmallUniqueID string `json:"small_file_unique_id"` + + // File identifiers of big (640x640) chat photo + BigFileID string `json:"big_file_id"` + BigUniqueID string `json:"big_file_unique_id"` +} + +// ChatMember object represents information about a single chat member. +type ChatMember struct { + Rights + + User *User `json:"user"` + Role MemberStatus `json:"status"` + Title string `json:"custom_title"` + Anonymous bool `json:"is_anonymous"` + + // Date when restrictions will be lifted for the user, unix time. + // + // If user is restricted for more than 366 days or less than + // 30 seconds from the current time, they are considered to be + // restricted forever. + // + // Use tele.Forever(). + // + RestrictedUntil int64 `json:"until_date,omitempty"` + + JoinToSend string `json:"join_to_send_messages"` + JoinByRequest string `json:"join_by_request"` +} + +// MemberStatus is one's chat status. +type MemberStatus string + +const ( + Creator MemberStatus = "creator" + Administrator MemberStatus = "administrator" + Member MemberStatus = "member" + Restricted MemberStatus = "restricted" + Left MemberStatus = "left" + Kicked MemberStatus = "kicked" +) + +// ChatMemberUpdate object represents changes in the status of a chat member. +type ChatMemberUpdate struct { + // Chat where the user belongs to. + Chat *Chat `json:"chat"` + + // Sender which user the action was triggered. + Sender *User `json:"from"` + + // Unixtime, use Date() to get time.Time. + Unixtime int64 `json:"date"` + + // Previous information about the chat member. + OldChatMember *ChatMember `json:"old_chat_member"` + + // New information about the chat member. + NewChatMember *ChatMember `json:"new_chat_member"` + + // (Optional) InviteLink which was used by the user to + // join the chat; for joining by invite link events only. + InviteLink *ChatInviteLink `json:"invite_link"` +} + +// Time returns the moment of the change in local time. +func (c *ChatMemberUpdate) Time() time.Time { + return time.Unix(c.Unixtime, 0) +} + +// ChatID represents a chat or an user integer ID, which can be used +// as recipient in bot methods. It is very useful in cases where +// you have special group IDs, for example in your config, and don't +// want to wrap it into *tele.Chat every time you send messages. +// +// Example: +// +// group := tele.ChatID(-100756389456) +// b.Send(group, "Hello!") +// +// type Config struct { +// AdminGroup tele.ChatID `json:"admin_group"` +// } +// b.Send(conf.AdminGroup, "Hello!") +// +type ChatID int64 + +// Recipient returns chat ID (see Recipient interface). +func (i ChatID) Recipient() string { + return strconv.FormatInt(int64(i), 10) +} + +// ChatJoinRequest represents a join request sent to a chat. +type ChatJoinRequest struct { + // Chat to which the request was sent. + Chat *Chat `json:"chat"` + + // Sender is the user that sent the join request. + Sender *User `json:"from"` + + // Unixtime, use ChatJoinRequest.Time() to get time.Time. + Unixtime int64 `json:"date"` + + // Bio of the user, optional. + Bio string `json:"bio"` + + // InviteLink is the chat invite link that was used by + //the user to send the join request, optional. + InviteLink *ChatInviteLink `json:"invite_link"` +} + +// ChatInviteLink object represents an invite for a chat. +type ChatInviteLink struct { + // The invite link. + InviteLink string `json:"invite_link"` + + // Invite link name. + Name string `json:"name"` + + // The creator of the link. + Creator *User `json:"creator"` + + // If the link is primary. + IsPrimary bool `json:"is_primary"` + + // If the link is revoked. + IsRevoked bool `json:"is_revoked"` + + // (Optional) Point in time when the link will expire, + // use ExpireDate() to get time.Time. + ExpireUnixtime int64 `json:"expire_date,omitempty"` + + // (Optional) Maximum number of users that can be members of + // the chat simultaneously. + MemberLimit int `json:"member_limit,omitempty"` + + // (Optional) True, if users joining the chat via the link need to + // be approved by chat administrators. If True, member_limit can't be specified. + JoinRequest bool `json:"creates_join_request"` + + // (Optional) Number of pending join requests created using this link. + PendingCount int `json:"pending_join_request_count"` +} + +// ExpireDate returns the moment of the link expiration in local time. +func (c *ChatInviteLink) ExpireDate() time.Time { + return time.Unix(c.ExpireUnixtime, 0) +} + +// Time returns the moment of chat join request sending in local time. +func (r ChatJoinRequest) Time() time.Time { + return time.Unix(r.Unixtime, 0) +} + +// InviteLink should be used to export chat's invite link. +func (b *Bot) InviteLink(chat *Chat) (string, error) { + params := map[string]string{ + "chat_id": chat.Recipient(), + } + + data, err := b.Raw("exportChatInviteLink", params) + if err != nil { + return "", err + } + + var resp struct { + Result string + } + if err := json.Unmarshal(data, &resp); err != nil { + return "", wrapError(err) + } + return resp.Result, nil +} + +// CreateInviteLink creates an additional invite link for a chat. +func (b *Bot) CreateInviteLink(chat Recipient, link *ChatInviteLink) (*ChatInviteLink, error) { + params := map[string]string{ + "chat_id": chat.Recipient(), + } + if link != nil { + params["name"] = link.Name + + if link.ExpireUnixtime != 0 { + params["expire_date"] = strconv.FormatInt(link.ExpireUnixtime, 10) + } + if link.MemberLimit > 0 { + params["member_limit"] = strconv.Itoa(link.MemberLimit) + } else if link.JoinRequest { + params["creates_join_request"] = "true" + } + } + + data, err := b.Raw("createChatInviteLink", params) + if err != nil { + return nil, err + } + + var resp struct { + Result ChatInviteLink `json:"result"` + } + if err := json.Unmarshal(data, &resp); err != nil { + return nil, wrapError(err) + } + + return &resp.Result, nil +} + +// EditInviteLink edits a non-primary invite link created by the bot. +func (b *Bot) EditInviteLink(chat Recipient, link *ChatInviteLink) (*ChatInviteLink, error) { + params := map[string]string{ + "chat_id": chat.Recipient(), + } + if link != nil { + params["invite_link"] = link.InviteLink + params["name"] = link.Name + + if link.ExpireUnixtime != 0 { + params["expire_date"] = strconv.FormatInt(link.ExpireUnixtime, 10) + } + if link.MemberLimit > 0 { + params["member_limit"] = strconv.Itoa(link.MemberLimit) + } else if link.JoinRequest { + params["creates_join_request"] = "true" + } + } + + data, err := b.Raw("editChatInviteLink", params) + if err != nil { + return nil, err + } + + var resp struct { + Result ChatInviteLink `json:"result"` + } + if err := json.Unmarshal(data, &resp); err != nil { + return nil, wrapError(err) + } + + return &resp.Result, nil +} + +// RevokeInviteLink revokes an invite link created by the bot. +func (b *Bot) RevokeInviteLink(chat Recipient, link string) (*ChatInviteLink, error) { + params := map[string]string{ + "chat_id": chat.Recipient(), + "invite_link": link, + } + + data, err := b.Raw("revokeChatInviteLink", params) + if err != nil { + return nil, err + } + + var resp struct { + Result ChatInviteLink `json:"result"` + } + if err := json.Unmarshal(data, &resp); err != nil { + return nil, wrapError(err) + } + + return &resp.Result, nil +} + +// ApproveJoinRequest approves a chat join request. +func (b *Bot) ApproveJoinRequest(chat Recipient, user *User) error { + params := map[string]string{ + "chat_id": chat.Recipient(), + "user_id": user.Recipient(), + } + + data, err := b.Raw("approveChatJoinRequest", params) + if err != nil { + return err + } + + return extractOk(data) +} + +// DeclineJoinRequest declines a chat join request. +func (b *Bot) DeclineJoinRequest(chat Recipient, user *User) error { + params := map[string]string{ + "chat_id": chat.Recipient(), + "user_id": user.Recipient(), + } + + data, err := b.Raw("declineChatJoinRequest", params) + if err != nil { + return err + } + + return extractOk(data) +} + +// SetGroupTitle should be used to update group title. +func (b *Bot) SetGroupTitle(chat *Chat, title string) error { + params := map[string]string{ + "chat_id": chat.Recipient(), + "title": title, + } + + _, err := b.Raw("setChatTitle", params) + return err +} + +// SetGroupDescription should be used to update group description. +func (b *Bot) SetGroupDescription(chat *Chat, description string) error { + params := map[string]string{ + "chat_id": chat.Recipient(), + "description": description, + } + + _, err := b.Raw("setChatDescription", params) + return err +} + +// SetGroupPhoto should be used to update group photo. +func (b *Bot) SetGroupPhoto(chat *Chat, p *Photo) error { + params := map[string]string{ + "chat_id": chat.Recipient(), + } + + _, err := b.sendFiles("setChatPhoto", map[string]File{"photo": p.File}, params) + return err +} + +// SetGroupStickerSet should be used to update group's group sticker set. +func (b *Bot) SetGroupStickerSet(chat *Chat, setName string) error { + params := map[string]string{ + "chat_id": chat.Recipient(), + "sticker_set_name": setName, + } + + _, err := b.Raw("setChatStickerSet", params) + return err +} + +// SetGroupPermissions sets default chat permissions for all members. +func (b *Bot) SetGroupPermissions(chat *Chat, perms Rights) error { + params := map[string]interface{}{ + "chat_id": chat.Recipient(), + "permissions": perms, + } + + _, err := b.Raw("setChatPermissions", params) + return err +} + +// DeleteGroupPhoto should be used to just remove group photo. +func (b *Bot) DeleteGroupPhoto(chat *Chat) error { + params := map[string]string{ + "chat_id": chat.Recipient(), + } + + _, err := b.Raw("deleteChatPhoto", params) + return err +} + +// DeleteGroupStickerSet should be used to just remove group sticker set. +func (b *Bot) DeleteGroupStickerSet(chat *Chat) error { + params := map[string]string{ + "chat_id": chat.Recipient(), + } + + _, err := b.Raw("deleteChatStickerSet", params) + return err +} diff --git a/vendor/gopkg.in/telebot.v3/commands.go b/vendor/gopkg.in/telebot.v3/commands.go new file mode 100644 index 00000000000..36a515afdb2 --- /dev/null +++ b/vendor/gopkg.in/telebot.v3/commands.go @@ -0,0 +1,85 @@ +package telebot + +import "encoding/json" + +// Command represents a bot command. +type Command struct { + // Text is a text of the command, 1-32 characters. + // Can contain only lowercase English letters, digits and underscores. + Text string `json:"command"` + + // Description of the command, 3-256 characters. + Description string `json:"description"` +} + +// CommandParams controls parameters for commands-related methods (setMyCommands, deleteMyCommands and getMyCommands). +type CommandParams struct { + Commands []Command `json:"commands,omitempty"` + Scope *CommandScope `json:"scope,omitempty"` + LanguageCode string `json:"language_code,omitempty"` +} + +type CommandScopeType = string + +const ( + CommandScopeDefault CommandScopeType = "default" + CommandScopeAllPrivateChats CommandScopeType = "all_private_chats" + CommandScopeAllGroupChats CommandScopeType = "all_group_chats" + CommandScopeAllChatAdmin CommandScopeType = "all_chat_administrators" + CommandScopeChat CommandScopeType = "chat" + CommandScopeChatAdmin CommandScopeType = "chat_administrators" + CommandScopeChatMember CommandScopeType = "chat_member" +) + +// CommandScope object represents a scope to which bot commands are applied. +type CommandScope struct { + Type CommandScopeType `json:"type"` + ChatID int64 `json:"chat_id,omitempty"` + UserID int64 `json:"user_id,omitempty"` +} + +// Commands returns the current list of the bot's commands for the given scope and user language. +func (b *Bot) Commands(opts ...interface{}) ([]Command, error) { + params := extractCommandsParams(opts...) + data, err := b.Raw("getMyCommands", params) + if err != nil { + return nil, err + } + + var resp struct { + Result []Command + } + if err := json.Unmarshal(data, &resp); err != nil { + return nil, wrapError(err) + } + return resp.Result, nil +} + +// SetCommands changes the list of the bot's commands. +func (b *Bot) SetCommands(opts ...interface{}) error { + params := extractCommandsParams(opts...) + _, err := b.Raw("setMyCommands", params) + return err +} + +// DeleteCommands deletes the list of the bot's commands for the given scope and user language. +func (b *Bot) DeleteCommands(opts ...interface{}) error { + params := extractCommandsParams(opts...) + _, err := b.Raw("deleteMyCommands", params) + return err +} + +// extractCommandsParams extracts parameters for commands-related methods from the given options. +func extractCommandsParams(opts ...interface{}) (params CommandParams) { + for _, opt := range opts { + switch value := opt.(type) { + case []Command: + params.Commands = value + case string: + params.LanguageCode = value + case CommandScope: + params.Scope = &value + } + } + return +} diff --git a/vendor/gopkg.in/telebot.v3/context.go b/vendor/gopkg.in/telebot.v3/context.go new file mode 100644 index 00000000000..9654aaeb596 --- /dev/null +++ b/vendor/gopkg.in/telebot.v3/context.go @@ -0,0 +1,486 @@ +package telebot + +import ( + "errors" + "strings" + "sync" + "time" +) + +// HandlerFunc represents a handler function, which is +// used to handle actual endpoints. +type HandlerFunc func(Context) error + +// Context wraps an update and represents the context of current event. +type Context interface { + // Bot returns the bot instance. + Bot() *Bot + + // Update returns the original update. + Update() Update + + // Message returns stored message if such presented. + Message() *Message + + // Callback returns stored callback if such presented. + Callback() *Callback + + // Query returns stored query if such presented. + Query() *Query + + // InlineResult returns stored inline result if such presented. + InlineResult() *InlineResult + + // ShippingQuery returns stored shipping query if such presented. + ShippingQuery() *ShippingQuery + + // PreCheckoutQuery returns stored pre checkout query if such presented. + PreCheckoutQuery() *PreCheckoutQuery + + // Poll returns stored poll if such presented. + Poll() *Poll + + // PollAnswer returns stored poll answer if such presented. + PollAnswer() *PollAnswer + + // ChatMember returns chat member changes. + ChatMember() *ChatMemberUpdate + + // ChatJoinRequest returns cha + ChatJoinRequest() *ChatJoinRequest + + // Migration returns both migration from and to chat IDs. + Migration() (int64, int64) + + // Sender returns the current recipient, depending on the context type. + // Returns nil if user is not presented. + Sender() *User + + // Chat returns the current chat, depending on the context type. + // Returns nil if chat is not presented. + Chat() *Chat + + // Recipient combines both Sender and Chat functions. If there is no user + // the chat will be returned. The native context cannot be without sender, + // but it is useful in the case when the context created intentionally + // by the NewContext constructor and have only Chat field inside. + Recipient() Recipient + + // Text returns the message text, depending on the context type. + // In the case when no related data presented, returns an empty string. + Text() string + + // Entities returns the message entities, whether it's media caption's or the text's. + // In the case when no entities presented, returns a nil. + Entities() Entities + + // Data returns the current data, depending on the context type. + // If the context contains command, returns its arguments string. + // If the context contains payment, returns its payload. + // In the case when no related data presented, returns an empty string. + Data() string + + // Args returns a raw slice of command or callback arguments as strings. + // The message arguments split by space, while the callback's ones by a "|" symbol. + Args() []string + + // Send sends a message to the current recipient. + // See Send from bot.go. + Send(what interface{}, opts ...interface{}) error + + // SendAlbum sends an album to the current recipient. + // See SendAlbum from bot.go. + SendAlbum(a Album, opts ...interface{}) error + + // Reply replies to the current message. + // See Reply from bot.go. + Reply(what interface{}, opts ...interface{}) error + + // Forward forwards the given message to the current recipient. + // See Forward from bot.go. + Forward(msg Editable, opts ...interface{}) error + + // ForwardTo forwards the current message to the given recipient. + // See Forward from bot.go + ForwardTo(to Recipient, opts ...interface{}) error + + // Edit edits the current message. + // See Edit from bot.go. + Edit(what interface{}, opts ...interface{}) error + + // EditCaption edits the caption of the current message. + // See EditCaption from bot.go. + EditCaption(caption string, opts ...interface{}) error + + // EditOrSend edits the current message if the update is callback, + // otherwise the content is sent to the chat as a separate message. + EditOrSend(what interface{}, opts ...interface{}) error + + // EditOrReply edits the current message if the update is callback, + // otherwise the content is replied as a separate message. + EditOrReply(what interface{}, opts ...interface{}) error + + // Delete removes the current message. + // See Delete from bot.go. + Delete() error + + // DeleteAfter waits for the duration to elapse and then removes the + // message. It handles an error automatically using b.OnError callback. + // It returns a Timer that can be used to cancel the call using its Stop method. + DeleteAfter(d time.Duration) *time.Timer + + // Notify updates the chat action for the current recipient. + // See Notify from bot.go. + Notify(action ChatAction) error + + // Ship replies to the current shipping query. + // See Ship from bot.go. + Ship(what ...interface{}) error + + // Accept finalizes the current deal. + // See Accept from bot.go. + Accept(errorMessage ...string) error + + // Answer sends a response to the current inline query. + // See Answer from bot.go. + Answer(resp *QueryResponse) error + + // Respond sends a response for the current callback query. + // See Respond from bot.go. + Respond(resp ...*CallbackResponse) error + + // Get retrieves data from the context. + Get(key string) interface{} + + // Set saves data in the context. + Set(key string, val interface{}) +} + +// nativeContext is a native implementation of the Context interface. +// "context" is taken by context package, maybe there is a better name. +type nativeContext struct { + b *Bot + u Update + lock sync.RWMutex + store map[string]interface{} +} + +func (c *nativeContext) Bot() *Bot { + return c.b +} + +func (c *nativeContext) Update() Update { + return c.u +} + +func (c *nativeContext) Message() *Message { + switch { + case c.u.Message != nil: + return c.u.Message + case c.u.Callback != nil: + return c.u.Callback.Message + case c.u.EditedMessage != nil: + return c.u.EditedMessage + case c.u.ChannelPost != nil: + if c.u.ChannelPost.PinnedMessage != nil { + return c.u.ChannelPost.PinnedMessage + } + return c.u.ChannelPost + case c.u.EditedChannelPost != nil: + return c.u.EditedChannelPost + default: + return nil + } +} + +func (c *nativeContext) Callback() *Callback { + return c.u.Callback +} + +func (c *nativeContext) Query() *Query { + return c.u.Query +} + +func (c *nativeContext) InlineResult() *InlineResult { + return c.u.InlineResult +} + +func (c *nativeContext) ShippingQuery() *ShippingQuery { + return c.u.ShippingQuery +} + +func (c *nativeContext) PreCheckoutQuery() *PreCheckoutQuery { + return c.u.PreCheckoutQuery +} + +func (c *nativeContext) ChatMember() *ChatMemberUpdate { + switch { + case c.u.ChatMember != nil: + return c.u.ChatMember + case c.u.MyChatMember != nil: + return c.u.MyChatMember + default: + return nil + } +} + +func (c *nativeContext) ChatJoinRequest() *ChatJoinRequest { + return c.u.ChatJoinRequest +} + +func (c *nativeContext) Poll() *Poll { + return c.u.Poll +} + +func (c *nativeContext) PollAnswer() *PollAnswer { + return c.u.PollAnswer +} + +func (c *nativeContext) Migration() (int64, int64) { + return c.u.Message.MigrateFrom, c.u.Message.MigrateTo +} + +func (c *nativeContext) Sender() *User { + switch { + case c.u.Callback != nil: + return c.u.Callback.Sender + case c.Message() != nil: + return c.Message().Sender + case c.u.Query != nil: + return c.u.Query.Sender + case c.u.InlineResult != nil: + return c.u.InlineResult.Sender + case c.u.ShippingQuery != nil: + return c.u.ShippingQuery.Sender + case c.u.PreCheckoutQuery != nil: + return c.u.PreCheckoutQuery.Sender + case c.u.PollAnswer != nil: + return c.u.PollAnswer.Sender + case c.u.MyChatMember != nil: + return c.u.MyChatMember.Sender + case c.u.ChatMember != nil: + return c.u.ChatMember.Sender + case c.u.ChatJoinRequest != nil: + return c.u.ChatJoinRequest.Sender + default: + return nil + } +} + +func (c *nativeContext) Chat() *Chat { + switch { + case c.Message() != nil: + return c.Message().Chat + case c.u.MyChatMember != nil: + return c.u.MyChatMember.Chat + case c.u.ChatMember != nil: + return c.u.ChatMember.Chat + case c.u.ChatJoinRequest != nil: + return c.u.ChatJoinRequest.Chat + default: + return nil + } +} + +func (c *nativeContext) Recipient() Recipient { + chat := c.Chat() + if chat != nil { + return chat + } + return c.Sender() +} + +func (c *nativeContext) Text() string { + m := c.Message() + if m == nil { + return "" + } + if m.Caption != "" { + return m.Caption + } + return m.Text +} + +func (c *nativeContext) Entities() Entities { + m := c.Message() + if m == nil { + return nil + } + if len(m.CaptionEntities) > 0 { + return m.CaptionEntities + } + return m.Entities +} + +func (c *nativeContext) Data() string { + switch { + case c.u.Message != nil: + return c.u.Message.Payload + case c.u.Callback != nil: + return c.u.Callback.Data + case c.u.Query != nil: + return c.u.Query.Text + case c.u.InlineResult != nil: + return c.u.InlineResult.Query + case c.u.ShippingQuery != nil: + return c.u.ShippingQuery.Payload + case c.u.PreCheckoutQuery != nil: + return c.u.PreCheckoutQuery.Payload + default: + return "" + } +} + +func (c *nativeContext) Args() []string { + switch { + case c.u.Message != nil: + payload := strings.Trim(c.u.Message.Payload, " ") + if payload != "" { + return strings.Split(payload, " ") + } + case c.u.Callback != nil: + return strings.Split(c.u.Callback.Data, "|") + case c.u.Query != nil: + return strings.Split(c.u.Query.Text, " ") + case c.u.InlineResult != nil: + return strings.Split(c.u.InlineResult.Query, " ") + } + return nil +} + +func (c *nativeContext) Send(what interface{}, opts ...interface{}) error { + _, err := c.b.Send(c.Recipient(), what, opts...) + return err +} + +func (c *nativeContext) SendAlbum(a Album, opts ...interface{}) error { + _, err := c.b.SendAlbum(c.Recipient(), a, opts...) + return err +} + +func (c *nativeContext) Reply(what interface{}, opts ...interface{}) error { + msg := c.Message() + if msg == nil { + return ErrBadContext + } + _, err := c.b.Reply(msg, what, opts...) + return err +} + +func (c *nativeContext) Forward(msg Editable, opts ...interface{}) error { + _, err := c.b.Forward(c.Recipient(), msg, opts...) + return err +} + +func (c *nativeContext) ForwardTo(to Recipient, opts ...interface{}) error { + msg := c.Message() + if msg == nil { + return ErrBadContext + } + _, err := c.b.Forward(to, msg, opts...) + return err +} + +func (c *nativeContext) Edit(what interface{}, opts ...interface{}) error { + if c.u.InlineResult != nil { + _, err := c.b.Edit(c.u.InlineResult, what, opts...) + return err + } + if c.u.Callback != nil { + _, err := c.b.Edit(c.u.Callback, what, opts...) + return err + } + return ErrBadContext +} + +func (c *nativeContext) EditCaption(caption string, opts ...interface{}) error { + if c.u.InlineResult != nil { + _, err := c.b.EditCaption(c.u.InlineResult, caption, opts...) + return err + } + if c.u.Callback != nil { + _, err := c.b.EditCaption(c.u.Callback, caption, opts...) + return err + } + return ErrBadContext +} + +func (c *nativeContext) EditOrSend(what interface{}, opts ...interface{}) error { + err := c.Edit(what, opts...) + if err == ErrBadContext { + return c.Send(what, opts...) + } + return err +} + +func (c *nativeContext) EditOrReply(what interface{}, opts ...interface{}) error { + err := c.Edit(what, opts...) + if err == ErrBadContext { + return c.Reply(what, opts...) + } + return err +} + +func (c *nativeContext) Delete() error { + msg := c.Message() + if msg == nil { + return ErrBadContext + } + return c.b.Delete(msg) +} + +func (c *nativeContext) DeleteAfter(d time.Duration) *time.Timer { + return time.AfterFunc(d, func() { + if err := c.Delete(); err != nil { + c.b.OnError(err, c) + } + }) +} + +func (c *nativeContext) Notify(action ChatAction) error { + return c.b.Notify(c.Recipient(), action) +} + +func (c *nativeContext) Ship(what ...interface{}) error { + if c.u.ShippingQuery == nil { + return errors.New("telebot: context shipping query is nil") + } + return c.b.Ship(c.u.ShippingQuery, what...) +} + +func (c *nativeContext) Accept(errorMessage ...string) error { + if c.u.PreCheckoutQuery == nil { + return errors.New("telebot: context pre checkout query is nil") + } + return c.b.Accept(c.u.PreCheckoutQuery, errorMessage...) +} + +func (c *nativeContext) Respond(resp ...*CallbackResponse) error { + if c.u.Callback == nil { + return errors.New("telebot: context callback is nil") + } + return c.b.Respond(c.u.Callback, resp...) +} + +func (c *nativeContext) Answer(resp *QueryResponse) error { + if c.u.Query == nil { + return errors.New("telebot: context inline query is nil") + } + return c.b.Answer(c.u.Query, resp) +} + +func (c *nativeContext) Set(key string, value interface{}) { + c.lock.Lock() + defer c.lock.Unlock() + + if c.store == nil { + c.store = make(map[string]interface{}) + } + c.store[key] = value +} + +func (c *nativeContext) Get(key string) interface{} { + c.lock.RLock() + defer c.lock.RUnlock() + return c.store[key] +} diff --git a/vendor/gopkg.in/telebot.v3/editable.go b/vendor/gopkg.in/telebot.v3/editable.go new file mode 100644 index 00000000000..ec1fb5b93ea --- /dev/null +++ b/vendor/gopkg.in/telebot.v3/editable.go @@ -0,0 +1,30 @@ +package telebot + +// Editable is an interface for all objects that +// provide "message signature", a pair of 32-bit +// message ID and 64-bit chat ID, both required +// for edit operations. +// +// Use case: DB model struct for messages to-be +// edited with, say two columns: msg_id,chat_id +// could easily implement MessageSig() making +// instances of stored messages editable. +type Editable interface { + // MessageSig is a "message signature". + // + // For inline messages, return chatID = 0. + MessageSig() (messageID string, chatID int64) +} + +// StoredMessage is an example struct suitable for being +// stored in the database as-is or being embedded into +// a larger struct, which is often the case (you might +// want to store some metadata alongside, or might not.) +type StoredMessage struct { + MessageID string `sql:"message_id" json:"message_id"` + ChatID int64 `sql:"chat_id" json:"chat_id"` +} + +func (x StoredMessage) MessageSig() (string, int64) { + return x.MessageID, x.ChatID +} diff --git a/vendor/gopkg.in/telebot.v3/errors.go b/vendor/gopkg.in/telebot.v3/errors.go new file mode 100644 index 00000000000..73d6d74e1c9 --- /dev/null +++ b/vendor/gopkg.in/telebot.v3/errors.go @@ -0,0 +1,242 @@ +package telebot + +import ( + "fmt" + "strings" +) + +type ( + Error struct { + Code int + Description string + Message string + } + + FloodError struct { + err *Error + RetryAfter int + } + + GroupError struct { + err *Error + MigratedTo int64 + } +) + +// ʔ returns description of error. +// A tiny shortcut to make code clearer. +func (err *Error) ʔ() string { + return err.Description +} + +// Error implements error interface. +func (err *Error) Error() string { + msg := err.Message + if msg == "" { + split := strings.Split(err.Description, ": ") + if len(split) == 2 { + msg = split[1] + } else { + msg = err.Description + } + } + return fmt.Sprintf("telegram: %s (%d)", msg, err.Code) +} + +// Error implements error interface. +func (err FloodError) Error() string { + return err.err.Error() +} + +// Error implements error interface. +func (err GroupError) Error() string { + return err.err.Error() +} + +// NewError returns new Error instance with given description. +// First element of msgs is Description. The second is optional Message. +func NewError(code int, msgs ...string) *Error { + err := &Error{Code: code} + if len(msgs) >= 1 { + err.Description = msgs[0] + } + if len(msgs) >= 2 { + err.Message = msgs[1] + } + return err +} + +// General errors +var ( + ErrTooLarge = NewError(400, "Request Entity Too Large") + ErrUnauthorized = NewError(401, "Unauthorized") + ErrNotFound = NewError(404, "Not Found") + ErrInternal = NewError(500, "Internal Server Error") +) + +// Bad request errors +var ( + ErrBadButtonData = NewError(400, "Bad Request: BUTTON_DATA_INVALID") + ErrBadPollOptions = NewError(400, "Bad Request: expected an Array of String as options") + ErrBadURLContent = NewError(400, "Bad Request: failed to get HTTP URL content") + ErrCantEditMessage = NewError(400, "Bad Request: message can't be edited") + ErrCantRemoveOwner = NewError(400, "Bad Request: can't remove chat owner") + ErrCantUploadFile = NewError(400, "Bad Request: can't upload file by URL") + ErrCantUseMediaInAlbum = NewError(400, "Bad Request: can't use the media of the specified type in the album") + ErrChatAboutNotModified = NewError(400, "Bad Request: chat description is not modified") + ErrChatNotFound = NewError(400, "Bad Request: chat not found") + ErrEmptyChatID = NewError(400, "Bad Request: chat_id is empty") + ErrEmptyMessage = NewError(400, "Bad Request: message must be non-empty") + ErrEmptyText = NewError(400, "Bad Request: text is empty") + ErrFailedImageProcess = NewError(400, "Bad Request: IMAGE_PROCESS_FAILED", "Image process failed") + ErrGroupMigrated = NewError(400, "Bad Request: group chat was upgraded to a supergroup chat") + ErrMessageNotModified = NewError(400, "Bad Request: message is not modified") + ErrNoRightsToDelete = NewError(400, "Bad Request: message can't be deleted") + ErrNoRightsToRestrict = NewError(400, "Bad Request: not enough rights to restrict/unrestrict chat member") + ErrNoRightsToSend = NewError(400, "Bad Request: have no rights to send a message") + ErrNoRightsToSendGifs = NewError(400, "Bad Request: CHAT_SEND_GIFS_FORBIDDEN", "sending GIFS is not allowed in this chat") + ErrNoRightsToSendPhoto = NewError(400, "Bad Request: not enough rights to send photos to the chat") + ErrNoRightsToSendStickers = NewError(400, "Bad Request: not enough rights to send stickers to the chat") + ErrNotFoundToDelete = NewError(400, "Bad Request: message to delete not found") + ErrNotFoundToForward = NewError(400, "Bad Request: message to forward not found") + ErrNotFoundToReply = NewError(400, "Bad Request: reply message not found") + ErrQueryTooOld = NewError(400, "Bad Request: query is too old and response timeout expired or query ID is invalid") + ErrSameMessageContent = NewError(400, "Bad Request: message is not modified: specified new message content and reply markup are exactly the same as a current content and reply markup of the message") + ErrStickerEmojisInvalid = NewError(400, "Bad Request: invalid sticker emojis") + ErrStickerSetInvalid = NewError(400, "Bad Request: STICKERSET_INVALID", "Stickerset is invalid") + ErrStickerSetInvalidName = NewError(400, "Bad Request: invalid sticker set name is specified") + ErrStickerSetNameOccupied = NewError(400, "Bad Request: sticker set name is already occupied") + ErrTooLongMarkup = NewError(400, "Bad Request: reply markup is too long") + ErrTooLongMessage = NewError(400, "Bad Request: message is too long") + ErrUserIsAdmin = NewError(400, "Bad Request: user is an administrator of the chat") + ErrWrongFileID = NewError(400, "Bad Request: wrong file identifier/HTTP URL specified") + ErrWrongFileIDCharacter = NewError(400, "Bad Request: wrong remote file id specified: Wrong character in the string") + ErrWrongFileIDLength = NewError(400, "Bad Request: wrong remote file id specified: Wrong string length") + ErrWrongFileIDPadding = NewError(400, "Bad Request: wrong remote file id specified: Wrong padding in the string") + ErrWrongFileIDSymbol = NewError(400, "Bad Request: wrong remote file id specified: can't unserialize it. Wrong last symbol") + ErrWrongTypeOfContent = NewError(400, "Bad Request: wrong type of the web page content") + ErrWrongURL = NewError(400, "Bad Request: wrong HTTP URL specified") + ErrForwardMessage = NewError(400, "Bad Request: administrators of the chat restricted message forwarding") +) + +// Forbidden errors +var ( + ErrBlockedByUser = NewError(403, "Forbidden: bot was blocked by the user") + ErrKickedFromGroup = NewError(403, "Forbidden: bot was kicked from the group chat") + ErrKickedFromSuperGroup = NewError(403, "Forbidden: bot was kicked from the supergroup chat") + ErrNotStartedByUser = NewError(403, "Forbidden: bot can't initiate conversation with a user") + ErrUserIsDeactivated = NewError(403, "Forbidden: user is deactivated") +) + +// Err returns Error instance by given description. +func Err(s string) error { + switch s { + case ErrTooLarge.ʔ(): + return ErrTooLarge + case ErrUnauthorized.ʔ(): + return ErrUnauthorized + case ErrNotFound.ʔ(): + return ErrNotFound + case ErrInternal.ʔ(): + return ErrInternal + case ErrBadButtonData.ʔ(): + return ErrBadButtonData + case ErrBadPollOptions.ʔ(): + return ErrBadPollOptions + case ErrBadURLContent.ʔ(): + return ErrBadURLContent + case ErrCantEditMessage.ʔ(): + return ErrCantEditMessage + case ErrCantRemoveOwner.ʔ(): + return ErrCantRemoveOwner + case ErrCantUploadFile.ʔ(): + return ErrCantUploadFile + case ErrCantUseMediaInAlbum.ʔ(): + return ErrCantUseMediaInAlbum + case ErrChatAboutNotModified.ʔ(): + return ErrChatAboutNotModified + case ErrChatNotFound.ʔ(): + return ErrChatNotFound + case ErrEmptyChatID.ʔ(): + return ErrEmptyChatID + case ErrEmptyMessage.ʔ(): + return ErrEmptyMessage + case ErrEmptyText.ʔ(): + return ErrEmptyText + case ErrFailedImageProcess.ʔ(): + return ErrFailedImageProcess + case ErrGroupMigrated.ʔ(): + return ErrGroupMigrated + case ErrMessageNotModified.ʔ(): + return ErrMessageNotModified + case ErrNoRightsToDelete.ʔ(): + return ErrNoRightsToDelete + case ErrNoRightsToRestrict.ʔ(): + return ErrNoRightsToRestrict + case ErrNoRightsToSend.ʔ(): + return ErrNoRightsToSend + case ErrNoRightsToSendGifs.ʔ(): + return ErrNoRightsToSendGifs + case ErrNoRightsToSendPhoto.ʔ(): + return ErrNoRightsToSendPhoto + case ErrNoRightsToSendStickers.ʔ(): + return ErrNoRightsToSendStickers + case ErrNotFoundToDelete.ʔ(): + return ErrNotFoundToDelete + case ErrNotFoundToForward.ʔ(): + return ErrNotFoundToForward + case ErrNotFoundToReply.ʔ(): + return ErrNotFoundToReply + case ErrQueryTooOld.ʔ(): + return ErrQueryTooOld + case ErrSameMessageContent.ʔ(): + return ErrSameMessageContent + case ErrStickerEmojisInvalid.ʔ(): + return ErrStickerEmojisInvalid + case ErrStickerSetInvalid.ʔ(): + return ErrStickerSetInvalid + case ErrStickerSetInvalidName.ʔ(): + return ErrStickerSetInvalidName + case ErrStickerSetNameOccupied.ʔ(): + return ErrStickerSetNameOccupied + case ErrTooLongMarkup.ʔ(): + return ErrTooLongMarkup + case ErrTooLongMessage.ʔ(): + return ErrTooLongMessage + case ErrUserIsAdmin.ʔ(): + return ErrUserIsAdmin + case ErrWrongFileID.ʔ(): + return ErrWrongFileID + case ErrWrongFileIDCharacter.ʔ(): + return ErrWrongFileIDCharacter + case ErrWrongFileIDLength.ʔ(): + return ErrWrongFileIDLength + case ErrWrongFileIDPadding.ʔ(): + return ErrWrongFileIDPadding + case ErrWrongFileIDSymbol.ʔ(): + return ErrWrongFileIDSymbol + case ErrWrongTypeOfContent.ʔ(): + return ErrWrongTypeOfContent + case ErrWrongURL.ʔ(): + return ErrWrongURL + case ErrBlockedByUser.ʔ(): + return ErrBlockedByUser + case ErrKickedFromGroup.ʔ(): + return ErrKickedFromGroup + case ErrKickedFromSuperGroup.ʔ(): + return ErrKickedFromSuperGroup + case ErrNotStartedByUser.ʔ(): + return ErrNotStartedByUser + case ErrUserIsDeactivated.ʔ(): + return ErrUserIsDeactivated + case ErrForwardMessage.ʔ(): + return ErrForwardMessage + default: + return nil + } +} + +// wrapError returns new wrapped telebot-related error. +func wrapError(err error) error { + return fmt.Errorf("telebot: %w", err) +} diff --git a/vendor/gopkg.in/telebot.v3/file.go b/vendor/gopkg.in/telebot.v3/file.go new file mode 100644 index 00000000000..14c40f97fd0 --- /dev/null +++ b/vendor/gopkg.in/telebot.v3/file.go @@ -0,0 +1,87 @@ +package telebot + +import ( + "io" + "os" +) + +// File object represents any sort of file. +type File struct { + FileID string `json:"file_id"` + UniqueID string `json:"file_unique_id"` + FileSize int64 `json:"file_size"` + + // FilePath is used for files on Telegram server. + FilePath string `json:"file_path"` + + // FileLocal is used for files on local file system. + FileLocal string `json:"file_local"` + + // FileURL is used for file on the internet. + FileURL string `json:"file_url"` + + // FileReader is used for file backed with io.Reader. + FileReader io.Reader `json:"-"` + + fileName string +} + +// FromDisk constructs a new local (on-disk) file object. +// +// Note, it returns File, not *File for a very good reason: +// in telebot, File is pretty much an embeddable struct, +// so upon uploading media you'll need to set embedded File +// with something. NewFile() returning File makes it a one-liner. +// +// photo := &tele.Photo{File: tele.FromDisk("chicken.jpg")} +// +func FromDisk(filename string) File { + return File{FileLocal: filename} +} + +// FromURL constructs a new file on provided HTTP URL. +// +// Note, it returns File, not *File for a very good reason: +// in telebot, File is pretty much an embeddable struct, +// so upon uploading media you'll need to set embedded File +// with something. NewFile() returning File makes it a one-liner. +// +// photo := &tele.Photo{File: tele.FromURL("https://site.com/picture.jpg")} +// +func FromURL(url string) File { + return File{FileURL: url} +} + +// FromReader constructs a new file from io.Reader. +// +// Note, it returns File, not *File for a very good reason: +// in telebot, File is pretty much an embeddable struct, +// so upon uploading media you'll need to set embedded File +// with something. NewFile() returning File makes it a one-liner. +// +// photo := &tele.Photo{File: tele.FromReader(bytes.NewReader(...))} +// +func FromReader(reader io.Reader) File { + return File{FileReader: reader} +} + +func (f *File) stealRef(g *File) { + if g.OnDisk() { + f.FileLocal = g.FileLocal + } + + if g.FileURL != "" { + f.FileURL = g.FileURL + } +} + +// InCloud tells whether the file is present on Telegram servers. +func (f *File) InCloud() bool { + return f.FileID != "" +} + +// OnDisk will return true if file is present on disk. +func (f *File) OnDisk() bool { + _, err := os.Stat(f.FileLocal) + return err == nil +} diff --git a/vendor/gopkg.in/telebot.v3/game.go b/vendor/gopkg.in/telebot.v3/game.go new file mode 100644 index 00000000000..0a1276d5551 --- /dev/null +++ b/vendor/gopkg.in/telebot.v3/game.go @@ -0,0 +1,99 @@ +package telebot + +import ( + "encoding/json" + "strconv" +) + +// Game object represents a game. +// Their short names acts as unique identifiers. +type Game struct { + Name string `json:"game_short_name"` + + Title string `json:"title"` + Description string `json:"description"` + Photo *Photo `json:"photo"` + + // (Optional) + Text string `json:"text"` + Entities []MessageEntity `json:"text_entities"` + Animation *Animation `json:"animation"` +} + +// GameHighScore object represents one row +// of the high scores table for a game. +type GameHighScore struct { + User *User `json:"user"` + Position int `json:"position"` + + Score int `json:"score"` + Force bool `json:"force"` + NoEdit bool `json:"disable_edit_message"` +} + +// GameScores returns the score of the specified user +// and several of their neighbors in a game. +// +// This function will panic upon nil Editable. +// +// Currently, it returns scores for the target user, +// plus two of their closest neighbors on each side. +// Will also return the top three users +// if the user and his neighbors are not among them. +// +func (b *Bot) GameScores(user Recipient, msg Editable) ([]GameHighScore, error) { + msgID, chatID := msg.MessageSig() + + params := map[string]string{ + "user_id": user.Recipient(), + } + + if chatID == 0 { // if inline message + params["inline_message_id"] = msgID + } else { + params["chat_id"] = strconv.FormatInt(chatID, 10) + params["message_id"] = msgID + } + + data, err := b.Raw("getGameHighScores", params) + if err != nil { + return nil, err + } + + var resp struct { + Result []GameHighScore + } + if err := json.Unmarshal(data, &resp); err != nil { + return nil, err + } + return resp.Result, nil +} + +// SetGameScore sets the score of the specified user in a game. +// +// If the message was sent by the bot, returns the edited Message, +// otherwise returns nil and ErrTrueResult. +// +func (b *Bot) SetGameScore(user Recipient, msg Editable, score GameHighScore) (*Message, error) { + msgID, chatID := msg.MessageSig() + + params := map[string]string{ + "user_id": user.Recipient(), + "score": strconv.Itoa(score.Score), + "force": strconv.FormatBool(score.Force), + "disable_edit_message": strconv.FormatBool(score.NoEdit), + } + + if chatID == 0 { // if inline message + params["inline_message_id"] = msgID + } else { + params["chat_id"] = strconv.FormatInt(chatID, 10) + params["message_id"] = msgID + } + + data, err := b.Raw("setGameScore", params) + if err != nil { + return nil, err + } + return extractMessage(data) +} diff --git a/vendor/gopkg.in/telebot.v3/inline.go b/vendor/gopkg.in/telebot.v3/inline.go new file mode 100644 index 00000000000..b3691393cb7 --- /dev/null +++ b/vendor/gopkg.in/telebot.v3/inline.go @@ -0,0 +1,139 @@ +package telebot + +import ( + "encoding/json" + "fmt" +) + +// Query is an incoming inline query. When the user sends +// an empty query, your bot could return some default or +// trending results. +type Query struct { + // Unique identifier for this query. 1-64 bytes. + ID string `json:"id"` + + // Sender. + Sender *User `json:"from"` + + // Sender location, only for bots that request user location. + Location *Location `json:"location"` + + // Text of the query (up to 512 characters). + Text string `json:"query"` + + // Offset of the results to be returned, can be controlled by the bot. + Offset string `json:"offset"` + + // ChatType of the type of the chat, from which the inline query was sent. + ChatType string `json:"chat_type"` +} + +// QueryResponse builds a response to an inline Query. +type QueryResponse struct { + // The ID of the query to which this is a response. + // + // Note: Telebot sets this field automatically! + QueryID string `json:"inline_query_id"` + + // The results for the inline query. + Results Results `json:"results"` + + // (Optional) The maximum amount of time in seconds that the result + // of the inline query may be cached on the server. + CacheTime int `json:"cache_time,omitempty"` + + // (Optional) Pass True, if results may be cached on the server side + // only for the user that sent the query. By default, results may + // be returned to any user who sends the same query. + IsPersonal bool `json:"is_personal"` + + // (Optional) Pass the offset that a client should send in the next + // query with the same text to receive more results. Pass an empty + // string if there are no more results or if you don‘t support + // pagination. Offset length can’t exceed 64 bytes. + NextOffset string `json:"next_offset"` + + // (Optional) If passed, clients will display a button with specified + // text that switches the user to a private chat with the bot and sends + // the bot a start message with the parameter switch_pm_parameter. + SwitchPMText string `json:"switch_pm_text,omitempty"` + + // (Optional) Parameter for the start message sent to the bot when user + // presses the switch button. + SwitchPMParameter string `json:"switch_pm_parameter,omitempty"` +} + +// InlineResult represents a result of an inline query that was chosen +// by the user and sent to their chat partner. +type InlineResult struct { + Sender *User `json:"from"` + Location *Location `json:"location,omitempty"` + ResultID string `json:"result_id"` + Query string `json:"query"` + MessageID string `json:"inline_message_id"` // inline messages only! +} + +// MessageSig satisfies Editable interface. +func (ir *InlineResult) MessageSig() (string, int64) { + return ir.MessageID, 0 +} + +// Result represents one result of an inline query. +type Result interface { + ResultID() string + SetResultID(string) + SetParseMode(ParseMode) + SetContent(InputMessageContent) + SetReplyMarkup(*ReplyMarkup) + Process(*Bot) +} + +// Results is a slice wrapper for convenient marshalling. +type Results []Result + +// MarshalJSON makes sure IQRs have proper IDs and Type variables set. +func (results Results) MarshalJSON() ([]byte, error) { + for _, result := range results { + if result.ResultID() == "" { + result.SetResultID(fmt.Sprintf("%d", &result)) + } + if err := inferIQR(result); err != nil { + return nil, err + } + } + + return json.Marshal([]Result(results)) +} + +func inferIQR(result Result) error { + switch r := result.(type) { + case *ArticleResult: + r.Type = "article" + case *AudioResult: + r.Type = "audio" + case *ContactResult: + r.Type = "contact" + case *DocumentResult: + r.Type = "document" + case *GifResult: + r.Type = "gif" + case *LocationResult: + r.Type = "location" + case *Mpeg4GifResult: + r.Type = "mpeg4_gif" + case *PhotoResult: + r.Type = "photo" + case *VenueResult: + r.Type = "venue" + case *VideoResult: + r.Type = "video" + case *VoiceResult: + r.Type = "voice" + case *StickerResult: + r.Type = "sticker" + default: + return fmt.Errorf("telebot: result %v is not supported", result) + } + + return nil +} diff --git a/vendor/gopkg.in/telebot.v3/inline_types.go b/vendor/gopkg.in/telebot.v3/inline_types.go new file mode 100644 index 00000000000..d93cffc2099 --- /dev/null +++ b/vendor/gopkg.in/telebot.v3/inline_types.go @@ -0,0 +1,373 @@ +package telebot + +// ResultBase must be embedded into all IQRs. +type ResultBase struct { + // Unique identifier for this result, 1-64 Bytes. + // If left unspecified, a 64-bit FNV-1 hash will be calculated + ID string `json:"id"` + + // Ignore. This field gets set automatically. + Type string `json:"type"` + + // Optional. Send Markdown or HTML, if you want Telegram apps to show + // bold, italic, fixed-width text or inline URLs in the media caption. + ParseMode ParseMode `json:"parse_mode,omitempty"` + + // Optional. Content of the message to be sent. + Content InputMessageContent `json:"input_message_content,omitempty"` + + // Optional. Inline keyboard attached to the message. + ReplyMarkup *ReplyMarkup `json:"reply_markup,omitempty"` +} + +// ResultID returns ResultBase.ID. +func (r *ResultBase) ResultID() string { + return r.ID +} + +// SetResultID sets ResultBase.ID. +func (r *ResultBase) SetResultID(id string) { + r.ID = id +} + +// SetParseMode sets ResultBase.ParseMode. +func (r *ResultBase) SetParseMode(mode ParseMode) { + r.ParseMode = mode +} + +// SetContent sets ResultBase.Content. +func (r *ResultBase) SetContent(content InputMessageContent) { + r.Content = content +} + +// SetReplyMarkup sets ResultBase.ReplyMarkup. +func (r *ResultBase) SetReplyMarkup(markup *ReplyMarkup) { + r.ReplyMarkup = markup +} + +func (r *ResultBase) Process(b *Bot) { + if r.ParseMode == ModeDefault { + r.ParseMode = b.parseMode + } + if r.Content != nil { + c, ok := r.Content.(*InputTextMessageContent) + if ok && c.ParseMode == ModeDefault { + c.ParseMode = r.ParseMode + } + } + if r.ReplyMarkup != nil { + processButtons(r.ReplyMarkup.InlineKeyboard) + } +} + +// ArticleResult represents a link to an article or web page. +type ArticleResult struct { + ResultBase + + // Title of the result. + Title string `json:"title"` + + // Message text. Shortcut (and mutually exclusive to) specifying + // InputMessageContent. + Text string `json:"message_text,omitempty"` + + // Optional. URL of the result. + URL string `json:"url,omitempty"` + + // Optional. Pass True, if you don't want the URL to be shown in the message. + HideURL bool `json:"hide_url,omitempty"` + + // Optional. Short description of the result. + Description string `json:"description,omitempty"` + + // Optional. URL of the thumbnail for the result. + ThumbURL string `json:"thumb_url,omitempty"` + + // Optional. Width of the thumbnail for the result. + ThumbWidth int `json:"thumb_width,omitempty"` + + // Optional. Height of the thumbnail for the result. + ThumbHeight int `json:"thumb_height,omitempty"` +} + +// AudioResult represents a link to an mp3 audio file. +type AudioResult struct { + ResultBase + + // Title. + Title string `json:"title"` + + // A valid URL for the audio file. + URL string `json:"audio_url"` + + // Optional. Performer. + Performer string `json:"performer,omitempty"` + + // Optional. Audio duration in seconds. + Duration int `json:"audio_duration,omitempty"` + + // Optional. Caption, 0-1024 characters. + Caption string `json:"caption,omitempty"` + + // If Cache != "", it'll be used instead + Cache string `json:"audio_file_id,omitempty"` +} + +// ContactResult represents a contact with a phone number. +type ContactResult struct { + ResultBase + + // Contact's phone number. + PhoneNumber string `json:"phone_number"` + + // Optional. Additional data about the contact in the form of a vCard, 0-2048 bytes. + VCard string `json:"vcard,omitempty"` + + // Contact's first name. + FirstName string `json:"first_name"` + + // Optional. Contact's last name. + LastName string `json:"last_name,omitempty"` + + // Optional. URL of the thumbnail for the result. + ThumbURL string `json:"thumb_url,omitempty"` + + // Optional. Width of the thumbnail for the result. + ThumbWidth int `json:"thumb_width,omitempty"` + + // Optional. Height of the thumbnail for the result. + ThumbHeight int `json:"thumb_height,omitempty"` +} + +// DocumentResult represents a link to a file. +type DocumentResult struct { + ResultBase + + // Title for the result. + Title string `json:"title"` + + // A valid URL for the file + URL string `json:"document_url"` + + // Mime type of the content of the file, either “application/pdf” or + // “application/zip”. + MIME string `json:"mime_type"` + + // Optional. Caption of the document to be sent, 0-200 characters. + Caption string `json:"caption,omitempty"` + + // Optional. Short description of the result. + Description string `json:"description,omitempty"` + + // Optional. URL of the thumbnail (jpeg only) for the file. + ThumbURL string `json:"thumb_url,omitempty"` + + // Optional. Width of the thumbnail for the result. + ThumbWidth int `json:"thumb_width,omitempty"` + + // Optional. Height of the thumbnail for the result. + ThumbHeight int `json:"thumb_height,omitempty"` + + // If Cache != "", it'll be used instead + Cache string `json:"document_file_id,omitempty"` +} + +// GifResult represents a link to an animated GIF file. +type GifResult struct { + ResultBase + + // A valid URL for the GIF file. File size must not exceed 1MB. + URL string `json:"gif_url"` + + // Optional. Width of the GIF. + Width int `json:"gif_width,omitempty"` + + // Optional. Height of the GIF. + Height int `json:"gif_height,omitempty"` + + // Optional. Duration of the GIF. + Duration int `json:"gif_duration,omitempty"` + + // URL of the static thumbnail for the result (jpeg or gif). + ThumbURL string `json:"thumb_url"` + + // Optional. MIME type of the thumbnail, must be one of + // “image/jpeg”, “image/gif”, or “video/mp4”. + ThumbMIME string `json:"thumb_mime_type,omitempty"` + + // Optional. Title for the result. + Title string `json:"title,omitempty"` + + // Optional. Caption of the GIF file to be sent, 0-200 characters. + Caption string `json:"caption,omitempty"` + + // If Cache != "", it'll be used instead + Cache string `json:"gif_file_id,omitempty"` +} + +// LocationResult represents a location on a map. +type LocationResult struct { + ResultBase + + Location + + // Location title. + Title string `json:"title"` + + // Optional. Url of the thumbnail for the result. + ThumbURL string `json:"thumb_url,omitempty"` +} + +// Mpeg4GifResult represents a link to a video animation +// (H.264/MPEG-4 AVC video without sound). +type Mpeg4GifResult struct { + ResultBase + + // A valid URL for the MP4 file. + URL string `json:"mpeg4_url"` + + // Optional. Video width. + Width int `json:"mpeg4_width,omitempty"` + + // Optional. Video height. + Height int `json:"mpeg4_height,omitempty"` + + // Optional. Video duration. + Duration int `json:"mpeg4_duration,omitempty"` + + // URL of the static thumbnail (jpeg or gif) for the result. + ThumbURL string `json:"thumb_url,omitempty"` + + // Optional. MIME type of the thumbnail, must be one of + // “image/jpeg”, “image/gif”, or “video/mp4”. + ThumbMIME string `json:"thumb_mime_type,omitempty"` + + // Optional. Title for the result. + Title string `json:"title,omitempty"` + + // Optional. Caption of the MPEG-4 file to be sent, 0-200 characters. + Caption string `json:"caption,omitempty"` + + // If Cache != "", it'll be used instead + Cache string `json:"mpeg4_file_id,omitempty"` +} + +// PhotoResult represents a link to a photo. +type PhotoResult struct { + ResultBase + + // A valid URL of the photo. Photo must be in jpeg format. + // Photo size must not exceed 5MB. + URL string `json:"photo_url"` + + // Optional. Width of the photo. + Width int `json:"photo_width,omitempty"` + + // Optional. Height of the photo. + Height int `json:"photo_height,omitempty"` + + // Optional. Title for the result. + Title string `json:"title,omitempty"` + + // Optional. Short description of the result. + Description string `json:"description,omitempty"` + + // Optional. Caption of the photo to be sent, 0-200 characters. + Caption string `json:"caption,omitempty"` + + // URL of the thumbnail for the photo. + ThumbURL string `json:"thumb_url"` + + // If Cache != "", it'll be used instead + Cache string `json:"photo_file_id,omitempty"` +} + +// VenueResult represents a venue. +type VenueResult struct { + ResultBase + + Location + + // Title of the venue. + Title string `json:"title"` + + // Address of the venue. + Address string `json:"address"` + + // Optional. Foursquare identifier of the venue if known. + FoursquareID string `json:"foursquare_id,omitempty"` + + // Optional. URL of the thumbnail for the result. + ThumbURL string `json:"thumb_url,omitempty"` + + // Optional. Width of the thumbnail for the result. + ThumbWidth int `json:"thumb_width,omitempty"` + + // Optional. Height of the thumbnail for the result. + ThumbHeight int `json:"thumb_height,omitempty"` +} + +// VideoResult represents a link to a page containing an embedded +// video player or a video file. +type VideoResult struct { + ResultBase + + // A valid URL for the embedded video player or video file. + URL string `json:"video_url"` + + // Mime type of the content of video url, “text/html” or “video/mp4”. + MIME string `json:"mime_type"` + + // URL of the thumbnail (jpeg only) for the video. + ThumbURL string `json:"thumb_url"` + + // Title for the result. + Title string `json:"title"` + + // Optional. Caption of the video to be sent, 0-200 characters. + Caption string `json:"caption,omitempty"` + + // Optional. Video width. + Width int `json:"video_width,omitempty"` + + // Optional. Video height. + Height int `json:"video_height,omitempty"` + + // Optional. Video duration in seconds. + Duration int `json:"video_duration,omitempty"` + + // Optional. Short description of the result. + Description string `json:"description,omitempty"` + + // If Cache != "", it'll be used instead + Cache string `json:"video_file_id,omitempty"` +} + +// VoiceResult represents a link to a voice recording in an .ogg +// container encoded with OPUS. +type VoiceResult struct { + ResultBase + + // A valid URL for the voice recording. + URL string `json:"voice_url"` + + // Recording title. + Title string `json:"title"` + + // Optional. Recording duration in seconds. + Duration int `json:"voice_duration"` + + // Optional. Caption, 0-1024 characters. + Caption string `json:"caption,omitempty"` + + // If Cache != "", it'll be used instead + Cache string `json:"voice_file_id,omitempty"` +} + +// StickerResult represents an inline cached sticker response. +type StickerResult struct { + ResultBase + + // If Cache != "", it'll be used instead + Cache string `json:"sticker_file_id,omitempty"` +} diff --git a/vendor/gopkg.in/telebot.v3/input_types.go b/vendor/gopkg.in/telebot.v3/input_types.go new file mode 100644 index 00000000000..8186c0727c9 --- /dev/null +++ b/vendor/gopkg.in/telebot.v3/input_types.go @@ -0,0 +1,73 @@ +package telebot + +// InputMessageContent objects represent the content of a message to be sent +// as a result of an inline query. +type InputMessageContent interface { + IsInputMessageContent() bool +} + +// InputTextMessageContent represents the content of a text message to be +// sent as the result of an inline query. +type InputTextMessageContent struct { + // Text of the message to be sent, 1-4096 characters. + Text string `json:"message_text"` + + // Optional. Send Markdown or HTML, if you want Telegram apps to show + // bold, italic, fixed-width text or inline URLs in your bot's message. + ParseMode string `json:"parse_mode,omitempty"` + + // Optional. Disables link previews for links in the sent message. + DisablePreview bool `json:"disable_web_page_preview"` +} + +func (input *InputTextMessageContent) IsInputMessageContent() bool { + return true +} + +// InputLocationMessageContent represents the content of a location message +// to be sent as the result of an inline query. +type InputLocationMessageContent struct { + Lat float32 `json:"latitude"` + Lng float32 `json:"longitude"` +} + +func (input *InputLocationMessageContent) IsInputMessageContent() bool { + return true +} + +// InputVenueMessageContent represents the content of a venue message to +// be sent as the result of an inline query. +type InputVenueMessageContent struct { + Lat float32 `json:"latitude"` + Lng float32 `json:"longitude"` + + // Name of the venue. + Title string `json:"title"` + + // Address of the venue. + Address string `json:"address"` + + // Optional. Foursquare identifier of the venue, if known. + FoursquareID string `json:"foursquare_id,omitempty"` +} + +func (input *InputVenueMessageContent) IsInputMessageContent() bool { + return true +} + +// InputContactMessageContent represents the content of a contact +// message to be sent as the result of an inline query. +type InputContactMessageContent struct { + // Contact's phone number. + PhoneNumber string `json:"phone_number"` + + // Contact's first name. + FirstName string `json:"first_name"` + + // Optional. Contact's last name. + LastName string `json:"last_name,omitempty"` +} + +func (input *InputContactMessageContent) IsInputMessageContent() bool { + return true +} diff --git a/vendor/gopkg.in/telebot.v3/markup.go b/vendor/gopkg.in/telebot.v3/markup.go new file mode 100644 index 00000000000..77bca6bf943 --- /dev/null +++ b/vendor/gopkg.in/telebot.v3/markup.go @@ -0,0 +1,322 @@ +package telebot + +import ( + "encoding/json" + "fmt" + "strings" +) + +// ReplyMarkup controls two convenient options for bot-user communications +// such as reply keyboard and inline "keyboard" (a grid of buttons as a part +// of the message). +type ReplyMarkup struct { + // InlineKeyboard is a grid of InlineButtons displayed in the message. + // + // Note: DO NOT confuse with ReplyKeyboard and other keyboard properties! + InlineKeyboard [][]InlineButton `json:"inline_keyboard,omitempty"` + + // ReplyKeyboard is a grid, consisting of keyboard buttons. + // + // Note: you don't need to set HideCustomKeyboard field to show custom keyboard. + ReplyKeyboard [][]ReplyButton `json:"keyboard,omitempty"` + + // ForceReply forces Telegram clients to display + // a reply interface to the user (act as if the user + // has selected the bot‘s message and tapped "Reply"). + ForceReply bool `json:"force_reply,omitempty"` + + // Requests clients to resize the keyboard vertically for optimal fit + // (e.g. make the keyboard smaller if there are just two rows of buttons). + // + // Defaults to false, in which case the custom keyboard is always of the + // same height as the app's standard keyboard. + ResizeKeyboard bool `json:"resize_keyboard,omitempty"` + + // Requests clients to hide the reply keyboard as soon as it's been used. + // + // Defaults to false. + OneTimeKeyboard bool `json:"one_time_keyboard,omitempty"` + + // Requests clients to remove the reply keyboard. + // + // Defaults to false. + RemoveKeyboard bool `json:"remove_keyboard,omitempty"` + + // Use this param if you want to force reply from + // specific users only. + // + // Targets: + // 1) Users that are @mentioned in the text of the Message object; + // 2) If the bot's message is a reply (has SendOptions.ReplyTo), + // sender of the original message. + Selective bool `json:"selective,omitempty"` + + // Placeholder will be shown in the input field when the reply is active. + Placeholder string `json:"input_field_placeholder,omitempty"` +} + +func (r *ReplyMarkup) copy() *ReplyMarkup { + cp := *r + + if len(r.ReplyKeyboard) > 0 { + cp.ReplyKeyboard = make([][]ReplyButton, len(r.ReplyKeyboard)) + for i, row := range r.ReplyKeyboard { + cp.ReplyKeyboard[i] = make([]ReplyButton, len(row)) + copy(cp.ReplyKeyboard[i], row) + } + } + + if len(r.InlineKeyboard) > 0 { + cp.InlineKeyboard = make([][]InlineButton, len(r.InlineKeyboard)) + for i, row := range r.InlineKeyboard { + cp.InlineKeyboard[i] = make([]InlineButton, len(row)) + copy(cp.InlineKeyboard[i], row) + } + } + + return &cp +} + +// Btn is a constructor button, which will later become either a reply, or an inline button. +type Btn struct { + Unique string `json:"unique,omitempty"` + Text string `json:"text,omitempty"` + URL string `json:"url,omitempty"` + Data string `json:"callback_data,omitempty"` + InlineQuery string `json:"switch_inline_query,omitempty"` + InlineQueryChat string `json:"switch_inline_query_current_chat,omitempty"` + Contact bool `json:"request_contact,omitempty"` + Location bool `json:"request_location,omitempty"` + Poll PollType `json:"request_poll,omitempty"` + Login *Login `json:"login_url,omitempty"` + WebApp *WebApp `json:"web_app,omitempty"` +} + +// Row represents an array of buttons, a row. +type Row []Btn + +// Row creates a row of buttons. +func (r *ReplyMarkup) Row(many ...Btn) Row { + return many +} + +// Split splits the keyboard into the rows with N maximum number of buttons. +// For example, if you pass six buttons and 3 as the max, you get two rows with +// three buttons in each. +// +// `Split(3, []Btn{six buttons...}) -> [[1, 2, 3], [4, 5, 6]]` +// `Split(2, []Btn{six buttons...}) -> [[1, 2],[3, 4],[5, 6]]` +// +func (r *ReplyMarkup) Split(max int, btns []Btn) []Row { + rows := make([]Row, (max-1+len(btns))/max) + for i, b := range btns { + i /= max + rows[i] = append(rows[i], b) + } + return rows +} + +func (r *ReplyMarkup) Inline(rows ...Row) { + inlineKeys := make([][]InlineButton, 0, len(rows)) + for i, row := range rows { + keys := make([]InlineButton, 0, len(row)) + for j, btn := range row { + btn := btn.Inline() + if btn == nil { + panic(fmt.Sprintf( + "telebot: button row %d column %d is not an inline button", + i, j)) + } + keys = append(keys, *btn) + } + inlineKeys = append(inlineKeys, keys) + } + + r.InlineKeyboard = inlineKeys +} + +func (r *ReplyMarkup) Reply(rows ...Row) { + replyKeys := make([][]ReplyButton, 0, len(rows)) + for i, row := range rows { + keys := make([]ReplyButton, 0, len(row)) + for j, btn := range row { + btn := btn.Reply() + if btn == nil { + panic(fmt.Sprintf( + "telebot: button row %d column %d is not a reply button", + i, j)) + } + keys = append(keys, *btn) + } + replyKeys = append(replyKeys, keys) + } + + r.ReplyKeyboard = replyKeys +} + +func (r *ReplyMarkup) Text(text string) Btn { + return Btn{Text: text} +} + +func (r *ReplyMarkup) Contact(text string) Btn { + return Btn{Contact: true, Text: text} +} + +func (r *ReplyMarkup) Location(text string) Btn { + return Btn{Location: true, Text: text} +} + +func (r *ReplyMarkup) Poll(text string, poll PollType) Btn { + return Btn{Poll: poll, Text: text} +} + +func (r *ReplyMarkup) Data(text, unique string, data ...string) Btn { + return Btn{ + Unique: unique, + Text: text, + Data: strings.Join(data, "|"), + } +} + +func (r *ReplyMarkup) URL(text, url string) Btn { + return Btn{Text: text, URL: url} +} + +func (r *ReplyMarkup) Query(text, query string) Btn { + return Btn{Text: text, InlineQuery: query} +} + +func (r *ReplyMarkup) QueryChat(text, query string) Btn { + return Btn{Text: text, InlineQueryChat: query} +} + +func (r *ReplyMarkup) Login(text string, login *Login) Btn { + return Btn{Login: login, Text: text} +} + +func (r *ReplyMarkup) WebApp(text string, app *WebApp) Btn { + return Btn{Text: text, WebApp: app} +} + +// ReplyButton represents a button displayed in reply-keyboard. +// +// Set either Contact or Location to true in order to request +// sensitive info, such as user's phone number or current location. +// +type ReplyButton struct { + Text string `json:"text"` + + Contact bool `json:"request_contact,omitempty"` + Location bool `json:"request_location,omitempty"` + Poll PollType `json:"request_poll,omitempty"` + WebApp *WebApp `json:"web_app,omitempty"` +} + +// MarshalJSON implements json.Marshaler. It allows passing PollType as a +// keyboard's poll type instead of KeyboardButtonPollType object. +func (pt PollType) MarshalJSON() ([]byte, error) { + return json.Marshal(&struct { + Type string `json:"type"` + }{ + Type: string(pt), + }) +} + +// InlineButton represents a button displayed in the message. +type InlineButton struct { + // Unique slagish name for this kind of button, + // try to be as specific as possible. + // + // It will be used as a callback endpoint. + Unique string `json:"unique,omitempty"` + + Text string `json:"text"` + URL string `json:"url,omitempty"` + Data string `json:"callback_data,omitempty"` + InlineQuery string `json:"switch_inline_query,omitempty"` + InlineQueryChat string `json:"switch_inline_query_current_chat"` + Login *Login `json:"login_url,omitempty"` + WebApp *WebApp `json:"web_app,omitempty"` +} + +// MarshalJSON implements json.Marshaler interface. +// It needed to avoid InlineQueryChat and Login or WebApp fields conflict. +// If you have Login or WebApp field in your button, InlineQueryChat must be skipped. +func (t *InlineButton) MarshalJSON() ([]byte, error) { + type IB InlineButton + + if t.Login != nil || t.WebApp != nil { + return json.Marshal(struct { + IB + InlineQueryChat string `json:"switch_inline_query_current_chat,omitempty"` + }{ + IB: IB(*t), + }) + } + return json.Marshal(IB(*t)) +} + +// With returns a copy of the button with data. +func (t *InlineButton) With(data string) *InlineButton { + return &InlineButton{ + Unique: t.Unique, + Text: t.Text, + URL: t.URL, + InlineQuery: t.InlineQuery, + InlineQueryChat: t.InlineQueryChat, + Login: t.Login, + Data: data, + } +} + +func (b Btn) Reply() *ReplyButton { + if b.Unique != "" { + return nil + } + + return &ReplyButton{ + Text: b.Text, + Contact: b.Contact, + Location: b.Location, + Poll: b.Poll, + WebApp: b.WebApp, + } +} + +func (b Btn) Inline() *InlineButton { + return &InlineButton{ + Unique: b.Unique, + Text: b.Text, + URL: b.URL, + Data: b.Data, + InlineQuery: b.InlineQuery, + InlineQueryChat: b.InlineQueryChat, + Login: b.Login, + WebApp: b.WebApp, + } +} + +// Login represents a parameter of the inline keyboard button +// used to automatically authorize a user. Serves as a great replacement +// for the Telegram Login Widget when the user is coming from Telegram. +type Login struct { + URL string `json:"url"` + Text string `json:"forward_text,omitempty"` + Username string `json:"bot_username,omitempty"` + WriteAccess bool `json:"request_write_access,omitempty"` +} + +// MenuButton describes the bot's menu button in a private chat. +type MenuButton struct { + Type MenuButtonType `json:"type"` + Text string `json:"text,omitempty"` + WebApp *WebApp `json:"web_app,omitempty"` +} + +type MenuButtonType = string + +const ( + MenuButtonDefault MenuButtonType = "default" + MenuButtonCommands MenuButtonType = "commands" + MenuButtonWebApp MenuButtonType = "web_app" +) diff --git a/vendor/gopkg.in/telebot.v3/media.go b/vendor/gopkg.in/telebot.v3/media.go new file mode 100644 index 00000000000..7dcabce9dc8 --- /dev/null +++ b/vendor/gopkg.in/telebot.v3/media.go @@ -0,0 +1,355 @@ +package telebot + +import ( + "encoding/json" +) + +// Media is a generic type for all kinds of media that includes File. +type Media interface { + // MediaType returns string-represented media type. + MediaType() string + + // MediaFile returns a pointer to the media file. + MediaFile() *File +} + +// InputMedia represents a composite InputMedia struct that is +// used by Telebot in sending and editing media methods. +type InputMedia struct { + Type string `json:"type"` + Media string `json:"media"` + Caption string `json:"caption"` + Thumbnail string `json:"thumb,omitempty"` + ParseMode string `json:"parse_mode,omitempty"` + Entities Entities `json:"caption_entities,omitempty"` + Width int `json:"width,omitempty"` + Height int `json:"height,omitempty"` + Duration int `json:"duration,omitempty"` + Title string `json:"title,omitempty"` + Performer string `json:"performer,omitempty"` + Streaming bool `json:"supports_streaming,omitempty"` + DisableTypeDetection bool `json:"disable_content_type_detection,omitempty"` +} + +// Inputtable is a generic type for all kinds of media you +// can put into an album. +type Inputtable interface { + Media + + // InputMedia returns already marshalled InputMedia type + // ready to be used in sending and editing media methods. + InputMedia() InputMedia +} + +// Album lets you group multiple media into a single message. +type Album []Inputtable + +// Photo object represents a single photo file. +type Photo struct { + File + + Width int `json:"width"` + Height int `json:"height"` + Caption string `json:"caption,omitempty"` +} + +type photoSize struct { + File + + Width int `json:"width"` + Height int `json:"height"` + Caption string `json:"caption,omitempty"` +} + +func (p *Photo) MediaType() string { + return "photo" +} + +func (p *Photo) MediaFile() *File { + return &p.File +} + +func (p *Photo) InputMedia() InputMedia { + return InputMedia{ + Type: p.MediaType(), + Caption: p.Caption, + } +} + +// UnmarshalJSON is custom unmarshaller required to abstract +// away the hassle of treating different thumbnail sizes. +// Instead, Telebot chooses the hi-res one and just sticks to it. +// +// I really do find it a beautiful solution. +func (p *Photo) UnmarshalJSON(data []byte) error { + var hq photoSize + + if data[0] == '{' { + if err := json.Unmarshal(data, &hq); err != nil { + return err + } + } else { + var sizes []photoSize + if err := json.Unmarshal(data, &sizes); err != nil { + return err + } + + hq = sizes[len(sizes)-1] + } + + p.File = hq.File + p.Width = hq.Width + p.Height = hq.Height + + return nil +} + +// Audio object represents an audio file. +type Audio struct { + File + + Duration int `json:"duration,omitempty"` + + // (Optional) + Caption string `json:"caption,omitempty"` + Thumbnail *Photo `json:"thumb,omitempty"` + Title string `json:"title,omitempty"` + Performer string `json:"performer,omitempty"` + MIME string `json:"mime_type,omitempty"` + FileName string `json:"file_name,omitempty"` +} + +func (a *Audio) MediaType() string { + return "audio" +} + +func (a *Audio) MediaFile() *File { + a.fileName = a.FileName + return &a.File +} + +func (a *Audio) InputMedia() InputMedia { + return InputMedia{ + Type: a.MediaType(), + Caption: a.Caption, + Duration: a.Duration, + Title: a.Title, + Performer: a.Performer, + } +} + +// Document object represents a general file (as opposed to Photo or Audio). +// Telegram users can send files of any type of up to 1.5 GB in size. +type Document struct { + File + + // (Optional) + Thumbnail *Photo `json:"thumb,omitempty"` + Caption string `json:"caption,omitempty"` + MIME string `json:"mime_type"` + FileName string `json:"file_name,omitempty"` + DisableTypeDetection bool `json:"disable_content_type_detection,omitempty"` +} + +func (d *Document) MediaType() string { + return "document" +} + +func (d *Document) MediaFile() *File { + d.fileName = d.FileName + return &d.File +} + +func (d *Document) InputMedia() InputMedia { + return InputMedia{ + Type: d.MediaType(), + Caption: d.Caption, + DisableTypeDetection: d.DisableTypeDetection, + } +} + +// Video object represents a video file. +type Video struct { + File + + Width int `json:"width"` + Height int `json:"height"` + Duration int `json:"duration,omitempty"` + + // (Optional) + Caption string `json:"caption,omitempty"` + Thumbnail *Photo `json:"thumb,omitempty"` + Streaming bool `json:"supports_streaming,omitempty"` + MIME string `json:"mime_type,omitempty"` + FileName string `json:"file_name,omitempty"` +} + +func (v *Video) MediaType() string { + return "video" +} + +func (v *Video) MediaFile() *File { + v.fileName = v.FileName + return &v.File +} + +func (v *Video) InputMedia() InputMedia { + return InputMedia{ + Type: v.MediaType(), + Caption: v.Caption, + Width: v.Width, + Height: v.Height, + Duration: v.Duration, + Streaming: v.Streaming, + } +} + +// Animation object represents a animation file. +type Animation struct { + File + + Width int `json:"width"` + Height int `json:"height"` + Duration int `json:"duration,omitempty"` + + // (Optional) + Caption string `json:"caption,omitempty"` + Thumbnail *Photo `json:"thumb,omitempty"` + MIME string `json:"mime_type,omitempty"` + FileName string `json:"file_name,omitempty"` +} + +func (a *Animation) MediaType() string { + return "animation" +} + +func (a *Animation) MediaFile() *File { + a.fileName = a.FileName + return &a.File +} + +func (a *Animation) InputMedia() InputMedia { + return InputMedia{ + Type: a.MediaType(), + Caption: a.Caption, + Width: a.Width, + Height: a.Height, + Duration: a.Duration, + } +} + +// Voice object represents a voice note. +type Voice struct { + File + + Duration int `json:"duration"` + + // (Optional) + Caption string `json:"caption,omitempty"` + MIME string `json:"mime_type,omitempty"` +} + +func (v *Voice) MediaType() string { + return "voice" +} + +func (v *Voice) MediaFile() *File { + return &v.File +} + +// VideoNote represents a video message. +type VideoNote struct { + File + + Duration int `json:"duration"` + + // (Optional) + Thumbnail *Photo `json:"thumb,omitempty"` + Length int `json:"length,omitempty"` +} + +func (v *VideoNote) MediaType() string { + return "videoNote" +} + +func (v *VideoNote) MediaFile() *File { + return &v.File +} + +// Sticker object represents a WebP image, so-called sticker. +type Sticker struct { + File + Width int `json:"width"` + Height int `json:"height"` + Animated bool `json:"is_animated"` + Video bool `json:"is_video"` + Thumbnail *Photo `json:"thumb"` + Emoji string `json:"emoji"` + SetName string `json:"set_name"` + MaskPosition *MaskPosition `json:"mask_position"` + PremiumAnimation *File `json:"premium_animation"` +} + +func (s *Sticker) MediaType() string { + return "sticker" +} + +func (s *Sticker) MediaFile() *File { + return &s.File +} + +// Contact object represents a contact to Telegram user. +type Contact struct { + PhoneNumber string `json:"phone_number"` + FirstName string `json:"first_name"` + + // (Optional) + LastName string `json:"last_name"` + UserID int64 `json:"user_id,omitempty"` +} + +// Location object represents geographic position. +type Location struct { + Lat float32 `json:"latitude"` + Lng float32 `json:"longitude"` + HorizontalAccuracy *float32 `json:"horizontal_accuracy,omitempty"` + Heading int `json:"heading,omitempty"` + AlertRadius int `json:"proximity_alert_radius,omitempty"` + + // Period in seconds for which the location will be updated + // (see Live Locations, should be between 60 and 86400.) + LivePeriod int `json:"live_period,omitempty"` +} + +// Venue object represents a venue location with name, address and +// optional foursquare ID. +type Venue struct { + Location Location `json:"location"` + Title string `json:"title"` + Address string `json:"address"` + + // (Optional) + FoursquareID string `json:"foursquare_id,omitempty"` + FoursquareType string `json:"foursquare_type,omitempty"` + GooglePlaceID string `json:"google_place_id,omitempty"` + GooglePlaceType string `json:"google_place_type,omitempty"` +} + +// Dice object represents a dice with a random value +// from 1 to 6 for currently supported base emoji. +type Dice struct { + Type DiceType `json:"emoji"` + Value int `json:"value"` +} + +// DiceType defines dice types. +type DiceType string + +var ( + Cube = &Dice{Type: "🎲"} + Dart = &Dice{Type: "🎯"} + Ball = &Dice{Type: "🏀"} + Goal = &Dice{Type: "⚽"} + Slot = &Dice{Type: "🎰"} + Bowl = &Dice{Type: "🎳"} +) diff --git a/vendor/gopkg.in/telebot.v3/message.go b/vendor/gopkg.in/telebot.v3/message.go new file mode 100644 index 00000000000..47c072194eb --- /dev/null +++ b/vendor/gopkg.in/telebot.v3/message.go @@ -0,0 +1,429 @@ +package telebot + +import ( + "strconv" + "time" + "unicode/utf16" +) + +// Message object represents a message. +type Message struct { + ID int `json:"message_id"` + + // For message sent to channels, Sender will be nil + Sender *User `json:"from"` + + // Unixtime, use Message.Time() to get time.Time + Unixtime int64 `json:"date"` + + // Conversation the message belongs to. + Chat *Chat `json:"chat"` + + // Sender of the message, sent on behalf of a chat. + SenderChat *Chat `json:"sender_chat"` + + // For forwarded messages, sender of the original message. + OriginalSender *User `json:"forward_from"` + + // For forwarded messages, chat of the original message when + // forwarded from a channel. + OriginalChat *Chat `json:"forward_from_chat"` + + // For forwarded messages, identifier of the original message + // when forwarded from a channel. + OriginalMessageID int `json:"forward_from_message_id"` + + // For forwarded messages, signature of the post author. + OriginalSignature string `json:"forward_signature"` + + // For forwarded messages, sender's name from users who + // disallow adding a link to their account. + OriginalSenderName string `json:"forward_sender_name"` + + // For forwarded messages, unixtime of the original message. + OriginalUnixtime int `json:"forward_date"` + + // Message is a channel post that was automatically forwarded to the connected discussion group. + AutomaticForward bool `json:"is_automatic_forward"` + + // For replies, ReplyTo represents the original message. + // + // Note that the Message object in this field will not + // contain further ReplyTo fields even if it + // itself is a reply. + ReplyTo *Message `json:"reply_to_message"` + + // Shows through which bot the message was sent. + Via *User `json:"via_bot"` + + // (Optional) Time of last edit in Unix. + LastEdit int64 `json:"edit_date"` + + // (Optional) Message can't be forwarded. + Protected bool `json:"has_protected_content,omitempty"` + + // AlbumID is the unique identifier of a media message group + // this message belongs to. + AlbumID string `json:"media_group_id"` + + // Author signature (in channels). + Signature string `json:"author_signature"` + + // For a text message, the actual UTF-8 text of the message. + Text string `json:"text"` + + // For registered commands, will contain the string payload: + // + // Ex: `/command ` or `/command@botname ` + Payload string `json:"-"` + + // For text messages, special entities like usernames, URLs, bot commands, + // etc. that appear in the text. + Entities Entities `json:"entities,omitempty"` + + // Some messages containing media, may as well have a caption. + Caption string `json:"caption,omitempty"` + + // For messages with a caption, special entities like usernames, URLs, + // bot commands, etc. that appear in the caption. + CaptionEntities Entities `json:"caption_entities,omitempty"` + + // For an audio recording, information about it. + Audio *Audio `json:"audio"` + + // For a general file, information about it. + Document *Document `json:"document"` + + // For a photo, all available sizes (thumbnails). + Photo *Photo `json:"photo"` + + // For a sticker, information about it. + Sticker *Sticker `json:"sticker"` + + // For a voice message, information about it. + Voice *Voice `json:"voice"` + + // For a video note, information about it. + VideoNote *VideoNote `json:"video_note"` + + // For a video, information about it. + Video *Video `json:"video"` + + // For a animation, information about it. + Animation *Animation `json:"animation"` + + // For a contact, contact information itself. + Contact *Contact `json:"contact"` + + // For a location, its longitude and latitude. + Location *Location `json:"location"` + + // For a venue, information about it. + Venue *Venue `json:"venue"` + + // For a poll, information the native poll. + Poll *Poll `json:"poll"` + + // For a game, information about it. + Game *Game `json:"game"` + + // For a dice, information about it. + Dice *Dice `json:"dice"` + + // For a service message, represents a user, + // that just got added to chat, this message came from. + // + // Sender leads to User, capable of invite. + // + // UserJoined might be the Bot itself. + UserJoined *User `json:"new_chat_member"` + + // For a service message, represents a user, + // that just left chat, this message came from. + // + // If user was kicked, Sender leads to a User, + // capable of this kick. + // + // UserLeft might be the Bot itself. + UserLeft *User `json:"left_chat_member"` + + // For a service message, represents a new title + // for chat this message came from. + // + // Sender would lead to a User, capable of change. + NewGroupTitle string `json:"new_chat_title"` + + // For a service message, represents all available + // thumbnails of the new chat photo. + // + // Sender would lead to a User, capable of change. + NewGroupPhoto *Photo `json:"new_chat_photo"` + + // For a service message, new members that were added to + // the group or supergroup and information about them + // (the bot itself may be one of these members). + UsersJoined []User `json:"new_chat_members"` + + // For a service message, true if chat photo just + // got removed. + // + // Sender would lead to a User, capable of change. + GroupPhotoDeleted bool `json:"delete_chat_photo"` + + // For a service message, true if group has been created. + // + // You would receive such a message if you are one of + // initial group chat members. + // + // Sender would lead to creator of the chat. + GroupCreated bool `json:"group_chat_created"` + + // For a service message, true if supergroup has been created. + // + // You would receive such a message if you are one of + // initial group chat members. + // + // Sender would lead to creator of the chat. + SuperGroupCreated bool `json:"supergroup_chat_created"` + + // For a service message, true if channel has been created. + // + // You would receive such a message if you are one of + // initial channel administrators. + // + // Sender would lead to creator of the chat. + ChannelCreated bool `json:"channel_chat_created"` + + // For a service message, the destination (supergroup) you + // migrated to. + // + // You would receive such a message when your chat has migrated + // to a supergroup. + // + // Sender would lead to creator of the migration. + MigrateTo int64 `json:"migrate_to_chat_id"` + + // For a service message, the Origin (normal group) you migrated + // from. + // + // You would receive such a message when your chat has migrated + // to a supergroup. + // + // Sender would lead to creator of the migration. + MigrateFrom int64 `json:"migrate_from_chat_id"` + + // Specified message was pinned. Note that the Message object + // in this field will not contain further ReplyTo fields even + // if it is itself a reply. + PinnedMessage *Message `json:"pinned_message"` + + // Message is an invoice for a payment. + Invoice *Invoice `json:"invoice"` + + // Message is a service message about a successful payment. + Payment *Payment `json:"successful_payment"` + + // The domain name of the website on which the user has logged in. + ConnectedWebsite string `json:"connected_website,omitempty"` + + // For a service message, a video chat started in the chat. + VideoChatStarted *VideoChatStarted `json:"video_chat_started,omitempty"` + + // For a service message, a video chat ended in the chat. + VideoChatEnded *VideoChatEnded `json:"video_chat_ended,omitempty"` + + // For a service message, some users were invited in the video chat. + VideoChatParticipants *VideoChatParticipants `json:"video_chat_participants_invited,omitempty"` + + // For a service message, a video chat schedule in the chat. + VideoChatScheduled *VideoChatScheduled `json:"video_chat_scheduled,omitempty"` + + // For a data sent by a Web App. + WebAppData *WebAppData `json:"web_app_data,omitempty"` + + // For a service message, represents the content of a service message, + // sent whenever a user in the chat triggers a proximity alert set by another user. + ProximityAlert *ProximityAlert `json:"proximity_alert_triggered,omitempty"` + + // For a service message, represents about a change in auto-delete timer settings. + AutoDeleteTimer *AutoDeleteTimer `json:"message_auto_delete_timer_changed,omitempty"` + + // Inline keyboard attached to the message. + ReplyMarkup *ReplyMarkup `json:"reply_markup,omitempty"` +} + +// MessageEntity object represents "special" parts of text messages, +// including hashtags, usernames, URLs, etc. +type MessageEntity struct { + // Specifies entity type. + Type EntityType `json:"type"` + + // Offset in UTF-16 code units to the start of the entity. + Offset int `json:"offset"` + + // Length of the entity in UTF-16 code units. + Length int `json:"length"` + + // (Optional) For EntityTextLink entity type only. + // + // URL will be opened after user taps on the text. + URL string `json:"url,omitempty"` + + // (Optional) For EntityTMention entity type only. + User *User `json:"user,omitempty"` + + // (Optional) For EntityCodeBlock entity type only. + Language string `json:"language,omitempty"` + + // (Optional) For EntityCustomEmoji entity type only. + CustomEmoji string `json:"custom_emoji_id"` +} + +// EntityType is a MessageEntity type. +type EntityType string + +const ( + EntityMention EntityType = "mention" + EntityTMention EntityType = "text_mention" + EntityHashtag EntityType = "hashtag" + EntityCashtag EntityType = "cashtag" + EntityCommand EntityType = "bot_command" + EntityURL EntityType = "url" + EntityEmail EntityType = "email" + EntityPhone EntityType = "phone_number" + EntityBold EntityType = "bold" + EntityItalic EntityType = "italic" + EntityUnderline EntityType = "underline" + EntityStrikethrough EntityType = "strikethrough" + EntityCode EntityType = "code" + EntityCodeBlock EntityType = "pre" + EntityTextLink EntityType = "text_link" + EntitySpoiler EntityType = "spoiler" + EntityCustomEmoji EntityType = "custom_emoji" +) + +// Entities is used to set message's text entities as a send option. +type Entities []MessageEntity + +// ProximityAlert sent whenever a user in the chat triggers +// a proximity alert set by another user. +type ProximityAlert struct { + Traveler *User `json:"traveler,omitempty"` + Watcher *User `json:"watcher,omitempty"` + Distance int `json:"distance"` +} + +// AutoDeleteTimer represents a service message about a change in auto-delete timer settings. +type AutoDeleteTimer struct { + Unixtime int `json:"message_auto_delete_time"` +} + +// MessageSig satisfies Editable interface (see Editable.) +func (m *Message) MessageSig() (string, int64) { + return strconv.Itoa(m.ID), m.Chat.ID +} + +// Time returns the moment of message creation in local time. +func (m *Message) Time() time.Time { + return time.Unix(m.Unixtime, 0) +} + +// LastEdited returns time.Time of last edit. +func (m *Message) LastEdited() time.Time { + return time.Unix(m.LastEdit, 0) +} + +// IsForwarded says whether message is forwarded copy of another +// message or not. +func (m *Message) IsForwarded() bool { + return m.OriginalSender != nil || m.OriginalChat != nil +} + +// IsReply says whether message is a reply to another message. +func (m *Message) IsReply() bool { + return m.ReplyTo != nil +} + +// Private returns true, if it's a personal message. +func (m *Message) Private() bool { + return m.Chat.Type == ChatPrivate +} + +// FromGroup returns true, if message came from a group OR a supergroup. +func (m *Message) FromGroup() bool { + return m.Chat.Type == ChatGroup || m.Chat.Type == ChatSuperGroup +} + +// FromChannel returns true, if message came from a channel. +func (m *Message) FromChannel() bool { + return m.Chat.Type == ChatChannel +} + +// IsService returns true, if message is a service message, +// returns false otherwise. +// +// Service messages are automatically sent messages, which +// typically occur on some global action. For instance, when +// anyone leaves the chat or chat title changes. +// +func (m *Message) IsService() bool { + fact := false + + fact = fact || m.UserJoined != nil + fact = fact || len(m.UsersJoined) > 0 + fact = fact || m.UserLeft != nil + fact = fact || m.NewGroupTitle != "" + fact = fact || m.NewGroupPhoto != nil + fact = fact || m.GroupPhotoDeleted + fact = fact || m.GroupCreated || m.SuperGroupCreated + fact = fact || (m.MigrateTo != m.MigrateFrom) + + return fact +} + +// EntityText returns the substring of the message identified by the +// given MessageEntity. +// +// It's safer than manually slicing Text because Telegram uses +// UTF-16 indices whereas Go string are []byte. +// +func (m *Message) EntityText(e MessageEntity) string { + text := m.Text + if text == "" { + text = m.Caption + } + + a := utf16.Encode([]rune(text)) + off, end := e.Offset, e.Offset+e.Length + + if off < 0 || end > len(a) { + return "" + } + + return string(utf16.Decode(a[off:end])) +} + +// Media returns the message's media if it contains either photo, +// voice, audio, animation, sticker, document, video or video note. +func (m *Message) Media() Media { + switch { + case m.Photo != nil: + return m.Photo + case m.Voice != nil: + return m.Voice + case m.Audio != nil: + return m.Audio + case m.Animation != nil: + return m.Animation + case m.Sticker != nil: + return m.Sticker + case m.Document != nil: + return m.Document + case m.Video != nil: + return m.Video + case m.VideoNote != nil: + return m.VideoNote + default: + return nil + } +} diff --git a/vendor/gopkg.in/telebot.v3/middleware.go b/vendor/gopkg.in/telebot.v3/middleware.go new file mode 100644 index 00000000000..aa21ca2e061 --- /dev/null +++ b/vendor/gopkg.in/telebot.v3/middleware.go @@ -0,0 +1,29 @@ +package telebot + +// MiddlewareFunc represents a middleware processing function, +// which get called before the endpoint group or specific handler. +type MiddlewareFunc func(HandlerFunc) HandlerFunc + +func applyMiddleware(h HandlerFunc, m ...MiddlewareFunc) HandlerFunc { + for i := len(m) - 1; i >= 0; i-- { + h = m[i](h) + } + return h +} + +// Group is a separated group of handlers, united by the general middleware. +type Group struct { + b *Bot + middleware []MiddlewareFunc +} + +// Use adds middleware to the chain. +func (g *Group) Use(middleware ...MiddlewareFunc) { + g.middleware = append(g.middleware, middleware...) +} + +// Handle adds endpoint handler to the bot, combining group's middleware +// with the optional given middleware. +func (g *Group) Handle(endpoint interface{}, h HandlerFunc, m ...MiddlewareFunc) { + g.b.Handle(endpoint, h, append(g.middleware, m...)...) +} diff --git a/vendor/gopkg.in/telebot.v3/options.go b/vendor/gopkg.in/telebot.v3/options.go new file mode 100644 index 00000000000..2aa28055ec8 --- /dev/null +++ b/vendor/gopkg.in/telebot.v3/options.go @@ -0,0 +1,213 @@ +package telebot + +import ( + "encoding/json" + "strconv" +) + +// Option is a shortcut flag type for certain message features +// (so-called options). It means that instead of passing +// fully-fledged SendOptions* to Send(), you can use these +// flags instead. +// +// Supported options are defined as iota-constants. +// +type Option int + +const ( + // NoPreview = SendOptions.DisableWebPagePreview + NoPreview Option = iota + + // Silent = SendOptions.DisableNotification + Silent + + // AllowWithoutReply = SendOptions.AllowWithoutReply + AllowWithoutReply + + // Protected = SendOptions.Protected + Protected + + // ForceReply = ReplyMarkup.ForceReply + ForceReply + + // OneTimeKeyboard = ReplyMarkup.OneTimeKeyboard + OneTimeKeyboard + + // RemoveKeyboard = ReplyMarkup.RemoveKeyboard + RemoveKeyboard +) + +// Placeholder is used to set input field placeholder as a send option. +func Placeholder(text string) *SendOptions { + return &SendOptions{ + ReplyMarkup: &ReplyMarkup{ + ForceReply: true, + Placeholder: text, + }, + } +} + +// SendOptions has most complete control over in what way the message +// must be sent, providing an API-complete set of custom properties +// and options. +// +// Despite its power, SendOptions is rather inconvenient to use all +// the way through bot logic, so you might want to consider storing +// and re-using it somewhere or be using Option flags instead. +// +type SendOptions struct { + // If the message is a reply, original message. + ReplyTo *Message + + // See ReplyMarkup struct definition. + ReplyMarkup *ReplyMarkup + + // For text messages, disables previews for links in this message. + DisableWebPagePreview bool + + // Sends the message silently. iOS users will not receive a notification, Android users will receive a notification with no sound. + DisableNotification bool + + // ParseMode controls how client apps render your message. + ParseMode ParseMode + + // Entities is a list of special entities that appear in message text, which can be specified instead of parse_mode. + Entities Entities + + // AllowWithoutReply allows sending messages not a as reply if the replied-to message has already been deleted. + AllowWithoutReply bool + + // Protected protects the contents of the sent message from forwarding and saving + Protected bool +} + +func (og *SendOptions) copy() *SendOptions { + cp := *og + if cp.ReplyMarkup != nil { + cp.ReplyMarkup = cp.ReplyMarkup.copy() + } + return &cp +} + +func extractOptions(how []interface{}) *SendOptions { + opts := &SendOptions{} + + for _, prop := range how { + switch opt := prop.(type) { + case *SendOptions: + opts = opt.copy() + case *ReplyMarkup: + if opt != nil { + opts.ReplyMarkup = opt.copy() + } + case Option: + switch opt { + case NoPreview: + opts.DisableWebPagePreview = true + case Silent: + opts.DisableNotification = true + case AllowWithoutReply: + opts.AllowWithoutReply = true + case ForceReply: + if opts.ReplyMarkup == nil { + opts.ReplyMarkup = &ReplyMarkup{} + } + opts.ReplyMarkup.ForceReply = true + case OneTimeKeyboard: + if opts.ReplyMarkup == nil { + opts.ReplyMarkup = &ReplyMarkup{} + } + opts.ReplyMarkup.OneTimeKeyboard = true + case RemoveKeyboard: + if opts.ReplyMarkup == nil { + opts.ReplyMarkup = &ReplyMarkup{} + } + opts.ReplyMarkup.RemoveKeyboard = true + case Protected: + opts.Protected = true + default: + panic("telebot: unsupported flag-option") + } + case ParseMode: + opts.ParseMode = opt + case Entities: + opts.Entities = opt + default: + panic("telebot: unsupported send-option") + } + } + + return opts +} + +func (b *Bot) embedSendOptions(params map[string]string, opt *SendOptions) { + if b.parseMode != ModeDefault { + params["parse_mode"] = b.parseMode + } + + if opt == nil { + return + } + + if opt.ReplyTo != nil && opt.ReplyTo.ID != 0 { + params["reply_to_message_id"] = strconv.Itoa(opt.ReplyTo.ID) + } + + if opt.DisableWebPagePreview { + params["disable_web_page_preview"] = "true" + } + + if opt.DisableNotification { + params["disable_notification"] = "true" + } + + if opt.ParseMode != ModeDefault { + params["parse_mode"] = opt.ParseMode + } + + if len(opt.Entities) > 0 { + delete(params, "parse_mode") + entities, _ := json.Marshal(opt.Entities) + + if params["caption"] != "" { + params["caption_entities"] = string(entities) + } else { + params["entities"] = string(entities) + } + } + + if opt.AllowWithoutReply { + params["allow_sending_without_reply"] = "true" + } + + if opt.ReplyMarkup != nil { + processButtons(opt.ReplyMarkup.InlineKeyboard) + replyMarkup, _ := json.Marshal(opt.ReplyMarkup) + params["reply_markup"] = string(replyMarkup) + } + + if opt.Protected { + params["protect_content"] = "true" + } +} + +func processButtons(keys [][]InlineButton) { + if keys == nil || len(keys) < 1 || len(keys[0]) < 1 { + return + } + + for i := range keys { + for j := range keys[i] { + key := &keys[i][j] + if key.Unique != "" { + // Format: "\f|" + data := key.Data + if data == "" { + key.Data = "\f" + key.Unique + } else { + key.Data = "\f" + key.Unique + "|" + data + } + } + } + } +} diff --git a/vendor/gopkg.in/telebot.v3/payments.go b/vendor/gopkg.in/telebot.v3/payments.go new file mode 100644 index 00000000000..c32f8a13a93 --- /dev/null +++ b/vendor/gopkg.in/telebot.v3/payments.go @@ -0,0 +1,188 @@ +package telebot + +import ( + "encoding/json" + "math" + "strconv" +) + +// ShippingQuery contains information about an incoming shipping query. +type ShippingQuery struct { + Sender *User `json:"from"` + ID string `json:"id"` + Payload string `json:"invoice_payload"` + Address ShippingAddress `json:"shipping_address"` +} + +// ShippingAddress represents a shipping address. +type ShippingAddress struct { + CountryCode string `json:"country_code"` + State string `json:"state"` + City string `json:"city"` + StreetLine1 string `json:"street_line1"` + StreetLine2 string `json:"street_line2"` + PostCode string `json:"post_code"` +} + +// ShippingOption represents one shipping option. +type ShippingOption struct { + ID string `json:"id"` + Title string `json:"title"` + Prices []Price `json:"prices"` +} + +// Payment contains basic information about a successful payment. +type Payment struct { + Currency string `json:"currency"` + Total int `json:"total_amount"` + Payload string `json:"invoice_payload"` + OptionID string `json:"shipping_option_id"` + Order Order `json:"order_info"` + TelegramChargeID string `json:"telegram_payment_charge_id"` + ProviderChargeID string `json:"provider_payment_charge_id"` +} + +// PreCheckoutQuery contains information about an incoming pre-checkout query. +type PreCheckoutQuery struct { + Sender *User `json:"from"` + ID string `json:"id"` + Currency string `json:"currency"` + Payload string `json:"invoice_payload"` + Total int `json:"total_amount"` + OptionID string `json:"shipping_option_id"` + Order Order `json:"order_info"` +} + +// Order represents information about an order. +type Order struct { + Name string `json:"name"` + PhoneNumber string `json:"phone_number"` + Email string `json:"email"` + Address ShippingAddress `json:"shipping_address"` +} + +// Invoice contains basic information about an invoice. +type Invoice struct { + Title string `json:"title"` + Description string `json:"description"` + Payload string `json:"payload"` + Currency string `json:"currency"` + Prices []Price `json:"prices"` + Token string `json:"provider_token"` + Data string `json:"provider_data"` + + Photo *Photo `json:"photo"` + PhotoSize int `json:"photo_size"` + + // Unique deep-linking parameter that can be used to + // generate this invoice when used as a start parameter (0). + Start string `json:"start_parameter"` + + // Shows the total price in the smallest units of the currency. + // For example, for a price of US$ 1.45 pass amount = 145. + Total int `json:"total_amount"` + + MaxTipAmount int `json:"max_tip_amount"` + SuggestedTipAmounts []int `json:"suggested_tip_amounts"` + + NeedName bool `json:"need_name"` + NeedPhoneNumber bool `json:"need_phone_number"` + NeedEmail bool `json:"need_email"` + NeedShippingAddress bool `json:"need_shipping_address"` + SendPhoneNumber bool `json:"send_phone_number_to_provider"` + SendEmail bool `json:"send_email_to_provider"` + Flexible bool `json:"is_flexible"` +} + +func (i Invoice) params() map[string]string { + params := map[string]string{ + "title": i.Title, + "description": i.Description, + "start_parameter": i.Start, + "payload": i.Payload, + "provider_token": i.Token, + "provider_data": i.Data, + "currency": i.Currency, + "max_tip_amount": strconv.Itoa(i.MaxTipAmount), + "need_name": strconv.FormatBool(i.NeedName), + "need_phone_number": strconv.FormatBool(i.NeedPhoneNumber), + "need_email": strconv.FormatBool(i.NeedEmail), + "need_shipping_address": strconv.FormatBool(i.NeedShippingAddress), + "send_phone_number_to_provider": strconv.FormatBool(i.SendPhoneNumber), + "send_email_to_provider": strconv.FormatBool(i.SendEmail), + "is_flexible": strconv.FormatBool(i.Flexible), + } + if i.Photo != nil { + if i.Photo.FileURL != "" { + params["photo_url"] = i.Photo.FileURL + } + if i.PhotoSize > 0 { + params["photo_size"] = strconv.Itoa(i.PhotoSize) + } + if i.Photo.Width > 0 { + params["photo_width"] = strconv.Itoa(i.Photo.Width) + } + if i.Photo.Height > 0 { + params["photo_height"] = strconv.Itoa(i.Photo.Height) + } + } + if len(i.Prices) > 0 { + data, _ := json.Marshal(i.Prices) + params["prices"] = string(data) + } + if len(i.SuggestedTipAmounts) > 0 { + var amounts []string + for _, n := range i.SuggestedTipAmounts { + amounts = append(amounts, strconv.Itoa(n)) + } + + data, _ := json.Marshal(amounts) + params["suggested_tip_amounts"] = string(data) + } + return params +} + +// Price represents a portion of the price for goods or services. +type Price struct { + Label string `json:"label"` + Amount int `json:"amount"` +} + +// Currency contains information about supported currency for payments. +type Currency struct { + Code string `json:"code"` + Title string `json:"title"` + Symbol string `json:"symbol"` + Native string `json:"native"` + ThousandsSep string `json:"thousands_sep"` + DecimalSep string `json:"decimal_sep"` + SymbolLeft bool `json:"symbol_left"` + SpaceBetween bool `json:"space_between"` + Exp int `json:"exp"` + MinAmount interface{} `json:"min_amount"` + MaxAmount interface{} `json:"max_amount"` +} + +func (c Currency) FromTotal(total int) float64 { + return float64(total) / math.Pow(10, float64(c.Exp)) +} + +func (c Currency) ToTotal(total float64) int { + return int(total) * int(math.Pow(10, float64(c.Exp))) +} + +// CreateInvoiceLink creates a link for a payment invoice. +func (b *Bot) CreateInvoiceLink(i Invoice) (string, error) { + data, err := b.Raw("createInvoiceLink", i.params()) + if err != nil { + return "", err + } + + var resp struct { + Result string + } + if err := json.Unmarshal(data, &resp); err != nil { + return "", wrapError(err) + } + return resp.Result, nil +} diff --git a/vendor/gopkg.in/telebot.v3/payments_data.go b/vendor/gopkg.in/telebot.v3/payments_data.go new file mode 100644 index 00000000000..a325c5b05ab --- /dev/null +++ b/vendor/gopkg.in/telebot.v3/payments_data.go @@ -0,0 +1,14 @@ +package telebot + +import "encoding/json" + +const dataCurrencies = `{"AED":{"code":"AED","title":"United Arab Emirates Dirham","symbol":"AED","native":"\u062f.\u0625.\u200f","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"367","max_amount":"3673200"},"AFN":{"code":"AFN","title":"Afghan Afghani","symbol":"AFN","native":"\u060b","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"7554","max_amount":"75540495"},"ALL":{"code":"ALL","title":"Albanian Lek","symbol":"ALL","native":"Lek","thousands_sep":".","decimal_sep":",","symbol_left":false,"space_between":false,"exp":2,"min_amount":"10908","max_amount":"109085036"},"AMD":{"code":"AMD","title":"Armenian Dram","symbol":"AMD","native":"\u0564\u0580.","thousands_sep":",","decimal_sep":".","symbol_left":false,"space_between":true,"exp":2,"min_amount":"48398","max_amount":"483984962"},"ARS":{"code":"ARS","title":"Argentine Peso","symbol":"ARS","native":"$","thousands_sep":".","decimal_sep":",","symbol_left":true,"space_between":true,"exp":2,"min_amount":"3720","max_amount":"37202998"},"AUD":{"code":"AUD","title":"Australian Dollar","symbol":"AU$","native":"$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"139","max_amount":"1392750"},"AZN":{"code":"AZN","title":"Azerbaijani Manat","symbol":"AZN","native":"\u043c\u0430\u043d.","thousands_sep":"\u00a0","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"170","max_amount":"1702500"},"BAM":{"code":"BAM","title":"Bosnia & Herzegovina Convertible Mark","symbol":"BAM","native":"KM","thousands_sep":".","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"171","max_amount":"1715550"},"BDT":{"code":"BDT","title":"Bangladeshi Taka","symbol":"BDT","native":"\u09f3","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"8336","max_amount":"83367500"},"BGN":{"code":"BGN","title":"Bulgarian Lev","symbol":"BGN","native":"\u043b\u0432.","thousands_sep":"\u00a0","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"171","max_amount":"1716850"},"BND":{"code":"BND","title":"Brunei Dollar","symbol":"BND","native":"$","thousands_sep":".","decimal_sep":",","symbol_left":true,"space_between":false,"exp":2,"min_amount":"134","max_amount":"1349850"},"BOB":{"code":"BOB","title":"Bolivian Boliviano","symbol":"BOB","native":"Bs","thousands_sep":".","decimal_sep":",","symbol_left":true,"space_between":true,"exp":2,"min_amount":"687","max_amount":"6877150"},"BRL":{"code":"BRL","title":"Brazilian Real","symbol":"R$","native":"R$","thousands_sep":".","decimal_sep":",","symbol_left":true,"space_between":true,"exp":2,"min_amount":"377","max_amount":"3775397"},"CAD":{"code":"CAD","title":"Canadian Dollar","symbol":"CA$","native":"$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"132","max_amount":"1321950"},"CHF":{"code":"CHF","title":"Swiss Franc","symbol":"CHF","native":"CHF","thousands_sep":"'","decimal_sep":".","symbol_left":false,"space_between":true,"exp":2,"min_amount":"99","max_amount":"993220"},"CLP":{"code":"CLP","title":"Chilean Peso","symbol":"CLP","native":"$","thousands_sep":".","decimal_sep":",","symbol_left":true,"space_between":true,"exp":0,"min_amount":"666","max_amount":"6665199"},"CNY":{"code":"CNY","title":"Chinese Renminbi Yuan","symbol":"CN\u00a5","native":"CN\u00a5","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"674","max_amount":"6747298"},"COP":{"code":"COP","title":"Colombian Peso","symbol":"COP","native":"$","thousands_sep":".","decimal_sep":",","symbol_left":true,"space_between":true,"exp":2,"min_amount":"315595","max_amount":"3155950000"},"CRC":{"code":"CRC","title":"Costa Rican Col\u00f3n","symbol":"CRC","native":"\u20a1","thousands_sep":".","decimal_sep":",","symbol_left":true,"space_between":false,"exp":2,"min_amount":"60113","max_amount":"601130282"},"CZK":{"code":"CZK","title":"Czech Koruna","symbol":"CZK","native":"K\u010d","thousands_sep":"\u00a0","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"2251","max_amount":"22510978"},"DKK":{"code":"DKK","title":"Danish Krone","symbol":"DKK","native":"kr","thousands_sep":"","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"654","max_amount":"6545403"},"DOP":{"code":"DOP","title":"Dominican Peso","symbol":"DOP","native":"$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"5032","max_amount":"50329504"},"DZD":{"code":"DZD","title":"Algerian Dinar","symbol":"DZD","native":"\u062f.\u062c.\u200f","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"11872","max_amount":"118729869"},"EGP":{"code":"EGP","title":"Egyptian Pound","symbol":"EGP","native":"\u062c.\u0645.\u200f","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"1791","max_amount":"17912012"},"EUR":{"code":"EUR","title":"Euro","symbol":"\u20ac","native":"\u20ac","thousands_sep":"\u00a0","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"87","max_amount":"877155"},"GBP":{"code":"GBP","title":"British Pound","symbol":"\u00a3","native":"\u00a3","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"75","max_amount":"757605"},"GEL":{"code":"GEL","title":"Georgian Lari","symbol":"GEL","native":"GEL","thousands_sep":"\u00a0","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"266","max_amount":"2663750"},"GTQ":{"code":"GTQ","title":"Guatemalan Quetzal","symbol":"GTQ","native":"Q","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"768","max_amount":"7689850"},"HKD":{"code":"HKD","title":"Hong Kong Dollar","symbol":"HK$","native":"$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"784","max_amount":"7845505"},"HNL":{"code":"HNL","title":"Honduran Lempira","symbol":"HNL","native":"L","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"2427","max_amount":"24277502"},"HRK":{"code":"HRK","title":"Croatian Kuna","symbol":"HRK","native":"kn","thousands_sep":".","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"650","max_amount":"6506302"},"HUF":{"code":"HUF","title":"Hungarian Forint","symbol":"HUF","native":"Ft","thousands_sep":"\u00a0","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"27844","max_amount":"278440341"},"IDR":{"code":"IDR","title":"Indonesian Rupiah","symbol":"IDR","native":"Rp","thousands_sep":".","decimal_sep":",","symbol_left":true,"space_between":false,"exp":2,"min_amount":"1406555","max_amount":"14065550000"},"ILS":{"code":"ILS","title":"Israeli New Sheqel","symbol":"\u20aa","native":"\u20aa","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"366","max_amount":"3668230"},"INR":{"code":"INR","title":"Indian Rupee","symbol":"\u20b9","native":"\u20b9","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"7090","max_amount":"70900503"},"ISK":{"code":"ISK","title":"Icelandic Kr\u00f3na","symbol":"ISK","native":"kr","thousands_sep":".","decimal_sep":",","symbol_left":false,"space_between":true,"exp":0,"min_amount":"119","max_amount":"1195599"},"JMD":{"code":"JMD","title":"Jamaican Dollar","symbol":"JMD","native":"$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"13153","max_amount":"131539958"},"JPY":{"code":"JPY","title":"Japanese Yen","symbol":"\u00a5","native":"\uffe5","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":0,"min_amount":"109","max_amount":"1095549"},"KES":{"code":"KES","title":"Kenyan Shilling","symbol":"KES","native":"Ksh","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"10032","max_amount":"100322011"},"KGS":{"code":"KGS","title":"Kyrgyzstani Som","symbol":"KGS","native":"KGS","thousands_sep":"\u00a0","decimal_sep":"-","symbol_left":false,"space_between":true,"exp":2,"min_amount":"6982","max_amount":"69820300"},"KRW":{"code":"KRW","title":"South Korean Won","symbol":"\u20a9","native":"\u20a9","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":0,"min_amount":"1119","max_amount":"11190001"},"KZT":{"code":"KZT","title":"Kazakhstani Tenge","symbol":"KZT","native":"\u20b8","thousands_sep":"\u00a0","decimal_sep":"-","symbol_left":true,"space_between":false,"exp":2,"min_amount":"37767","max_amount":"377674954"},"LBP":{"code":"LBP","title":"Lebanese Pound","symbol":"LBP","native":"\u0644.\u0644.\u200f","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"150080","max_amount":"1500802255"},"LKR":{"code":"LKR","title":"Sri Lankan Rupee","symbol":"LKR","native":"\u0dbb\u0dd4.","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"18078","max_amount":"180789638"},"MAD":{"code":"MAD","title":"Moroccan Dirham","symbol":"MAD","native":"\u062f.\u0645.\u200f","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"955","max_amount":"9554850"},"MDL":{"code":"MDL","title":"Moldovan Leu","symbol":"MDL","native":"MDL","thousands_sep":",","decimal_sep":".","symbol_left":false,"space_between":true,"exp":2,"min_amount":"1703","max_amount":"17038967"},"MNT":{"code":"MNT","title":"Mongolian T\u00f6gr\u00f6g","symbol":"MNT","native":"MNT","thousands_sep":"\u00a0","decimal_sep":",","symbol_left":true,"space_between":false,"exp":2,"min_amount":"261750","max_amount":"2617500000"},"MUR":{"code":"MUR","title":"Mauritian Rupee","symbol":"MUR","native":"MUR","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"3438","max_amount":"34384499"},"MVR":{"code":"MVR","title":"Maldivian Rufiyaa","symbol":"MVR","native":"MVR","thousands_sep":",","decimal_sep":".","symbol_left":false,"space_between":true,"exp":2,"min_amount":"1550","max_amount":"15501063"},"MXN":{"code":"MXN","title":"Mexican Peso","symbol":"MX$","native":"$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"1898","max_amount":"18988704"},"MYR":{"code":"MYR","title":"Malaysian Ringgit","symbol":"MYR","native":"RM","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"412","max_amount":"4124501"},"MZN":{"code":"MZN","title":"Mozambican Metical","symbol":"MZN","native":"MTn","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"6188","max_amount":"61889913"},"NGN":{"code":"NGN","title":"Nigerian Naira","symbol":"NGN","native":"\u20a6","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"36174","max_amount":"361749532"},"NIO":{"code":"NIO","title":"Nicaraguan C\u00f3rdoba","symbol":"NIO","native":"C$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"3241","max_amount":"32415503"},"NOK":{"code":"NOK","title":"Norwegian Krone","symbol":"NOK","native":"kr","thousands_sep":"\u00a0","decimal_sep":",","symbol_left":true,"space_between":true,"exp":2,"min_amount":"851","max_amount":"8510100"},"NPR":{"code":"NPR","title":"Nepalese Rupee","symbol":"NPR","native":"\u0928\u0947\u0930\u0942","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"11299","max_amount":"112995016"},"NZD":{"code":"NZD","title":"New Zealand Dollar","symbol":"NZ$","native":"$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"146","max_amount":"1461850"},"PAB":{"code":"PAB","title":"Panamanian Balboa","symbol":"PAB","native":"B\/.","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"99","max_amount":"995290"},"PEN":{"code":"PEN","title":"Peruvian Nuevo Sol","symbol":"PEN","native":"S\/.","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"333","max_amount":"3331250"},"PHP":{"code":"PHP","title":"Philippine Peso","symbol":"PHP","native":"\u20b1","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"5260","max_amount":"52602981"},"PKR":{"code":"PKR","title":"Pakistani Rupee","symbol":"PKR","native":"\u20a8","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"13921","max_amount":"139214990"},"PLN":{"code":"PLN","title":"Polish Z\u0142oty","symbol":"PLN","native":"z\u0142","thousands_sep":"\u00a0","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"376","max_amount":"3764026"},"PYG":{"code":"PYG","title":"Paraguayan Guaran\u00ed","symbol":"PYG","native":"\u20b2","thousands_sep":".","decimal_sep":",","symbol_left":true,"space_between":true,"exp":0,"min_amount":"6013","max_amount":"60134502"},"QAR":{"code":"QAR","title":"Qatari Riyal","symbol":"QAR","native":"\u0631.\u0642.\u200f","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"364","max_amount":"3641101"},"RON":{"code":"RON","title":"Romanian Leu","symbol":"RON","native":"RON","thousands_sep":".","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"417","max_amount":"4172003"},"RSD":{"code":"RSD","title":"Serbian Dinar","symbol":"RSD","native":"\u0434\u0438\u043d.","thousands_sep":".","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"10391","max_amount":"103910127"},"RUB":{"code":"RUB","title":"Russian Ruble","symbol":"RUB","native":"\u0440\u0443\u0431.","thousands_sep":"\u00a0","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"6598","max_amount":"65986027"},"SAR":{"code":"SAR","title":"Saudi Riyal","symbol":"SAR","native":"\u0631.\u0633.\u200f","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"373","max_amount":"3732650"},"SEK":{"code":"SEK","title":"Swedish Krona","symbol":"SEK","native":"kr","thousands_sep":".","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"904","max_amount":"9047896"},"SGD":{"code":"SGD","title":"Singapore Dollar","symbol":"SGD","native":"$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"135","max_amount":"1353897"},"THB":{"code":"THB","title":"Thai Baht","symbol":"\u0e3f","native":"\u0e3f","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"3156","max_amount":"31563499"},"TJS":{"code":"TJS","title":"Tajikistani Somoni","symbol":"TJS","native":"TJS","thousands_sep":"\u00a0","decimal_sep":";","symbol_left":false,"space_between":true,"exp":2,"min_amount":"938","max_amount":"9389950"},"TRY":{"code":"TRY","title":"Turkish Lira","symbol":"TRY","native":"TL","thousands_sep":".","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"526","max_amount":"5267200"},"TTD":{"code":"TTD","title":"Trinidad and Tobago Dollar","symbol":"TTD","native":"$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"675","max_amount":"6757850"},"TWD":{"code":"TWD","title":"New Taiwan Dollar","symbol":"NT$","native":"NT$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"3072","max_amount":"30722993"},"TZS":{"code":"TZS","title":"Tanzanian Shilling","symbol":"TZS","native":"TSh","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"230200","max_amount":"2302000188"},"UAH":{"code":"UAH","title":"Ukrainian Hryvnia","symbol":"UAH","native":"\u20b4","thousands_sep":"\u00a0","decimal_sep":",","symbol_left":false,"space_between":false,"exp":2,"min_amount":"2764","max_amount":"27648991"},"UGX":{"code":"UGX","title":"Ugandan Shilling","symbol":"UGX","native":"USh","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":0,"min_amount":"3657","max_amount":"36575502"},"USD":{"code":"USD","title":"United States Dollar","symbol":"$","native":"$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"100","max_amount":1000000},"UYU":{"code":"UYU","title":"Uruguayan Peso","symbol":"UYU","native":"$","thousands_sep":".","decimal_sep":",","symbol_left":true,"space_between":true,"exp":2,"min_amount":"3246","max_amount":"32469503"},"UZS":{"code":"UZS","title":"Uzbekistani Som","symbol":"UZS","native":"UZS","thousands_sep":"\u00a0","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"832759","max_amount":"8327599915"},"VND":{"code":"VND","title":"Vietnamese \u0110\u1ed3ng","symbol":"\u20ab","native":"\u20ab","thousands_sep":".","decimal_sep":",","symbol_left":false,"space_between":true,"exp":0,"min_amount":"23084","max_amount":"230840500"},"YER":{"code":"YER","title":"Yemeni Rial","symbol":"YER","native":"\u0631.\u064a.\u200f","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"25030","max_amount":"250301249"},"ZAR":{"code":"ZAR","title":"South African Rand","symbol":"ZAR","native":"R","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"1362","max_amount":"13620106"}}` + +var SupportedCurrencies = make(map[string]Currency) + +func init() { + err := json.Unmarshal([]byte(dataCurrencies), &SupportedCurrencies) + if err != nil { + panic(err) + } +} diff --git a/vendor/gopkg.in/telebot.v3/poll.go b/vendor/gopkg.in/telebot.v3/poll.go new file mode 100644 index 00000000000..8e2e5091db1 --- /dev/null +++ b/vendor/gopkg.in/telebot.v3/poll.go @@ -0,0 +1,75 @@ +package telebot + +import "time" + +// PollType defines poll types. +type PollType string + +const ( + // NOTE: + // Despite "any" type isn't described in documentation, + // it needed for proper KeyboardButtonPollType marshaling. + PollAny PollType = "any" + + PollQuiz PollType = "quiz" + PollRegular PollType = "regular" +) + +// Poll contains information about a poll. +type Poll struct { + ID string `json:"id"` + Type PollType `json:"type"` + Question string `json:"question"` + Options []PollOption `json:"options"` + VoterCount int `json:"total_voter_count"` + + // (Optional) + Closed bool `json:"is_closed,omitempty"` + CorrectOption int `json:"correct_option_id,omitempty"` + MultipleAnswers bool `json:"allows_multiple_answers,omitempty"` + Explanation string `json:"explanation,omitempty"` + ParseMode ParseMode `json:"explanation_parse_mode,omitempty"` + Entities []MessageEntity `json:"explanation_entities"` + + // True by default, shouldn't be omitted. + Anonymous bool `json:"is_anonymous"` + + // (Mutually exclusive) + OpenPeriod int `json:"open_period,omitempty"` + CloseUnixdate int64 `json:"close_date,omitempty"` +} + +// PollOption contains information about one answer option in a poll. +type PollOption struct { + Text string `json:"text"` + VoterCount int `json:"voter_count"` +} + +// PollAnswer represents an answer of a user in a non-anonymous poll. +type PollAnswer struct { + PollID string `json:"poll_id"` + Sender *User `json:"user"` + Options []int `json:"option_ids"` +} + +// IsRegular says whether poll is a regular. +func (p *Poll) IsRegular() bool { + return p.Type == PollRegular +} + +// IsQuiz says whether poll is a quiz. +func (p *Poll) IsQuiz() bool { + return p.Type == PollQuiz +} + +// CloseDate returns the close date of poll in local time. +func (p *Poll) CloseDate() time.Time { + return time.Unix(p.CloseUnixdate, 0) +} + +// AddOptions adds text options to the poll. +func (p *Poll) AddOptions(opts ...string) { + for _, t := range opts { + p.Options = append(p.Options, PollOption{Text: t}) + } +} diff --git a/vendor/gopkg.in/telebot.v3/poller.go b/vendor/gopkg.in/telebot.v3/poller.go new file mode 100644 index 00000000000..d45f2a5435a --- /dev/null +++ b/vendor/gopkg.in/telebot.v3/poller.go @@ -0,0 +1,115 @@ +package telebot + +import "time" + +// Poller is a provider of Updates. +// +// All pollers must implement Poll(), which accepts bot +// pointer and subscription channel and start polling +// synchronously straight away. +// +type Poller interface { + // Poll is supposed to take the bot object + // subscription channel and start polling + // for Updates immediately. + // + // Poller must listen for stop constantly and close + // it as soon as it's done polling. + Poll(b *Bot, updates chan Update, stop chan struct{}) +} + +// LongPoller is a classic LongPoller with timeout. +type LongPoller struct { + Limit int + Timeout time.Duration + LastUpdateID int + + // AllowedUpdates contains the update types + // you want your bot to receive. + // + // Possible values: + // message + // edited_message + // channel_post + // edited_channel_post + // inline_query + // chosen_inline_result + // callback_query + // shipping_query + // pre_checkout_query + // poll + // poll_answer + // + AllowedUpdates []string `yaml:"allowed_updates"` +} + +// Poll does long polling. +func (p *LongPoller) Poll(b *Bot, dest chan Update, stop chan struct{}) { + for { + select { + case <-stop: + return + default: + } + + updates, err := b.getUpdates(p.LastUpdateID+1, p.Limit, p.Timeout, p.AllowedUpdates) + if err != nil { + b.debug(err) + continue + } + + for _, update := range updates { + p.LastUpdateID = update.ID + dest <- update + } + } +} + +// MiddlewarePoller is a special kind of poller that acts +// like a filter for updates. It could be used for spam +// handling, banning or whatever. +// +// For heavy middleware, use increased capacity. +// +type MiddlewarePoller struct { + Capacity int // Default: 1 + Poller Poller + Filter func(*Update) bool +} + +// NewMiddlewarePoller wait for it... constructs a new middleware poller. +func NewMiddlewarePoller(original Poller, filter func(*Update) bool) *MiddlewarePoller { + return &MiddlewarePoller{ + Poller: original, + Filter: filter, + } +} + +// Poll sieves updates through middleware filter. +func (p *MiddlewarePoller) Poll(b *Bot, dest chan Update, stop chan struct{}) { + if p.Capacity < 1 { + p.Capacity = 1 + } + + middle := make(chan Update, p.Capacity) + stopPoller := make(chan struct{}) + stopConfirm := make(chan struct{}) + + go func() { + p.Poller.Poll(b, middle, stopPoller) + close(stopConfirm) + }() + + for { + select { + case <-stop: + close(stopPoller) + <-stopConfirm + return + case upd := <-middle: + if p.Filter(&upd) { + dest <- upd + } + } + } +} diff --git a/vendor/gopkg.in/telebot.v3/sendable.go b/vendor/gopkg.in/telebot.v3/sendable.go new file mode 100644 index 00000000000..2e782e33a7a --- /dev/null +++ b/vendor/gopkg.in/telebot.v3/sendable.go @@ -0,0 +1,408 @@ +package telebot + +import ( + "encoding/json" + "fmt" + "path/filepath" + "strconv" +) + +// Recipient is any possible endpoint you can send +// messages to: either user, group or a channel. +type Recipient interface { + Recipient() string // must return legit Telegram chat_id or username +} + +// Sendable is any object that can send itself. +// +// This is pretty cool, since it lets bots implement +// custom Sendables for complex kind of media or +// chat objects spanning across multiple messages. +// +type Sendable interface { + Send(*Bot, Recipient, *SendOptions) (*Message, error) +} + +// Send delivers media through bot b to recipient. +func (p *Photo) Send(b *Bot, to Recipient, opt *SendOptions) (*Message, error) { + params := map[string]string{ + "chat_id": to.Recipient(), + "caption": p.Caption, + } + b.embedSendOptions(params, opt) + + msg, err := b.sendMedia(p, params, nil) + if err != nil { + return nil, err + } + + msg.Photo.File.stealRef(&p.File) + *p = *msg.Photo + p.Caption = msg.Caption + + return msg, nil +} + +// Send delivers media through bot b to recipient. +func (a *Audio) Send(b *Bot, to Recipient, opt *SendOptions) (*Message, error) { + params := map[string]string{ + "chat_id": to.Recipient(), + "caption": a.Caption, + "performer": a.Performer, + "title": a.Title, + "file_name": a.FileName, + } + b.embedSendOptions(params, opt) + + if a.Duration != 0 { + params["duration"] = strconv.Itoa(a.Duration) + } + + msg, err := b.sendMedia(a, params, thumbnailToFilemap(a.Thumbnail)) + if err != nil { + return nil, err + } + + if msg.Audio != nil { + msg.Audio.File.stealRef(&a.File) + *a = *msg.Audio + a.Caption = msg.Caption + } + + if msg.Document != nil { + msg.Document.File.stealRef(&a.File) + a.File = msg.Document.File + } + + return msg, nil +} + +// Send delivers media through bot b to recipient. +func (d *Document) Send(b *Bot, to Recipient, opt *SendOptions) (*Message, error) { + params := map[string]string{ + "chat_id": to.Recipient(), + "caption": d.Caption, + "file_name": d.FileName, + } + b.embedSendOptions(params, opt) + + if d.FileSize != 0 { + params["file_size"] = strconv.FormatInt(d.FileSize, 10) + } + if d.DisableTypeDetection { + params["disable_content_type_detection"] = "true" + } + + msg, err := b.sendMedia(d, params, thumbnailToFilemap(d.Thumbnail)) + if err != nil { + return nil, err + } + + if doc := msg.Document; doc != nil { + doc.File.stealRef(&d.File) + *d = *doc + d.Caption = msg.Caption + } else if vid := msg.Video; vid != nil { + vid.File.stealRef(&d.File) + d.Caption = vid.Caption + d.MIME = vid.MIME + d.Thumbnail = vid.Thumbnail + } + + return msg, nil +} + +// Send delivers media through bot b to recipient. +func (s *Sticker) Send(b *Bot, to Recipient, opt *SendOptions) (*Message, error) { + params := map[string]string{ + "chat_id": to.Recipient(), + } + b.embedSendOptions(params, opt) + + msg, err := b.sendMedia(s, params, nil) + if err != nil { + return nil, err + } + + msg.Sticker.File.stealRef(&s.File) + *s = *msg.Sticker + + return msg, nil +} + +// Send delivers media through bot b to recipient. +func (v *Video) Send(b *Bot, to Recipient, opt *SendOptions) (*Message, error) { + params := map[string]string{ + "chat_id": to.Recipient(), + "caption": v.Caption, + "file_name": v.FileName, + } + b.embedSendOptions(params, opt) + + if v.Duration != 0 { + params["duration"] = strconv.Itoa(v.Duration) + } + if v.Width != 0 { + params["width"] = strconv.Itoa(v.Width) + } + if v.Height != 0 { + params["height"] = strconv.Itoa(v.Height) + } + if v.Streaming { + params["supports_streaming"] = "true" + } + + msg, err := b.sendMedia(v, params, thumbnailToFilemap(v.Thumbnail)) + if err != nil { + return nil, err + } + + if vid := msg.Video; vid != nil { + vid.File.stealRef(&v.File) + *v = *vid + v.Caption = msg.Caption + } else if doc := msg.Document; doc != nil { + // If video has no sound, Telegram can turn it into Document (GIF) + doc.File.stealRef(&v.File) + + v.Caption = doc.Caption + v.MIME = doc.MIME + v.Thumbnail = doc.Thumbnail + } + + return msg, nil +} + +// Send delivers animation through bot b to recipient. +func (a *Animation) Send(b *Bot, to Recipient, opt *SendOptions) (*Message, error) { + params := map[string]string{ + "chat_id": to.Recipient(), + "caption": a.Caption, + "file_name": a.FileName, + } + b.embedSendOptions(params, opt) + + if a.Duration != 0 { + params["duration"] = strconv.Itoa(a.Duration) + } + if a.Width != 0 { + params["width"] = strconv.Itoa(a.Width) + } + if a.Height != 0 { + params["height"] = strconv.Itoa(a.Height) + } + + // file_name is required, without it animation sends as a document + if params["file_name"] == "" && a.File.OnDisk() { + params["file_name"] = filepath.Base(a.File.FileLocal) + } + + msg, err := b.sendMedia(a, params, thumbnailToFilemap(a.Thumbnail)) + if err != nil { + return nil, err + } + + if anim := msg.Animation; anim != nil { + anim.File.stealRef(&a.File) + *a = *msg.Animation + } else if doc := msg.Document; doc != nil { + *a = Animation{ + File: doc.File, + Thumbnail: doc.Thumbnail, + MIME: doc.MIME, + FileName: doc.FileName, + } + } + + a.Caption = msg.Caption + return msg, nil +} + +// Send delivers media through bot b to recipient. +func (v *Voice) Send(b *Bot, to Recipient, opt *SendOptions) (*Message, error) { + params := map[string]string{ + "chat_id": to.Recipient(), + "caption": v.Caption, + } + b.embedSendOptions(params, opt) + + if v.Duration != 0 { + params["duration"] = strconv.Itoa(v.Duration) + } + + msg, err := b.sendMedia(v, params, nil) + if err != nil { + return nil, err + } + + msg.Voice.File.stealRef(&v.File) + *v = *msg.Voice + + return msg, nil +} + +// Send delivers media through bot b to recipient. +func (v *VideoNote) Send(b *Bot, to Recipient, opt *SendOptions) (*Message, error) { + params := map[string]string{ + "chat_id": to.Recipient(), + } + b.embedSendOptions(params, opt) + + if v.Duration != 0 { + params["duration"] = strconv.Itoa(v.Duration) + } + if v.Length != 0 { + params["length"] = strconv.Itoa(v.Length) + } + + msg, err := b.sendMedia(v, params, thumbnailToFilemap(v.Thumbnail)) + if err != nil { + return nil, err + } + + msg.VideoNote.File.stealRef(&v.File) + *v = *msg.VideoNote + + return msg, nil +} + +// Send delivers media through bot b to recipient. +func (x *Location) Send(b *Bot, to Recipient, opt *SendOptions) (*Message, error) { + params := map[string]string{ + "chat_id": to.Recipient(), + "latitude": fmt.Sprintf("%f", x.Lat), + "longitude": fmt.Sprintf("%f", x.Lng), + "live_period": strconv.Itoa(x.LivePeriod), + } + if x.HorizontalAccuracy != nil { + params["horizontal_accuracy"] = fmt.Sprintf("%f", *x.HorizontalAccuracy) + } + if x.Heading != 0 { + params["heading"] = strconv.Itoa(x.Heading) + } + if x.AlertRadius != 0 { + params["proximity_alert_radius"] = strconv.Itoa(x.Heading) + } + b.embedSendOptions(params, opt) + + data, err := b.Raw("sendLocation", params) + if err != nil { + return nil, err + } + + return extractMessage(data) +} + +// Send delivers media through bot b to recipient. +func (v *Venue) Send(b *Bot, to Recipient, opt *SendOptions) (*Message, error) { + params := map[string]string{ + "chat_id": to.Recipient(), + "latitude": fmt.Sprintf("%f", v.Location.Lat), + "longitude": fmt.Sprintf("%f", v.Location.Lng), + "title": v.Title, + "address": v.Address, + "foursquare_id": v.FoursquareID, + "foursquare_type": v.FoursquareType, + "google_place_id": v.GooglePlaceID, + "google_place_type": v.GooglePlaceType, + } + b.embedSendOptions(params, opt) + + data, err := b.Raw("sendVenue", params) + if err != nil { + return nil, err + } + + return extractMessage(data) +} + +// Send delivers invoice through bot b to recipient. +func (i *Invoice) Send(b *Bot, to Recipient, opt *SendOptions) (*Message, error) { + params := i.params() + params["chat_id"] = to.Recipient() + b.embedSendOptions(params, opt) + + data, err := b.Raw("sendInvoice", params) + if err != nil { + return nil, err + } + + return extractMessage(data) +} + +// Send delivers poll through bot b to recipient. +func (p *Poll) Send(b *Bot, to Recipient, opt *SendOptions) (*Message, error) { + params := map[string]string{ + "chat_id": to.Recipient(), + "question": p.Question, + "type": string(p.Type), + "is_closed": strconv.FormatBool(p.Closed), + "is_anonymous": strconv.FormatBool(p.Anonymous), + "allows_multiple_answers": strconv.FormatBool(p.MultipleAnswers), + "correct_option_id": strconv.Itoa(p.CorrectOption), + } + if p.Explanation != "" { + params["explanation"] = p.Explanation + params["explanation_parse_mode"] = p.ParseMode + } + if p.OpenPeriod != 0 { + params["open_period"] = strconv.Itoa(p.OpenPeriod) + } else if p.CloseUnixdate != 0 { + params["close_date"] = strconv.FormatInt(p.CloseUnixdate, 10) + } + b.embedSendOptions(params, opt) + + var options []string + for _, o := range p.Options { + options = append(options, o.Text) + } + + opts, _ := json.Marshal(options) + params["options"] = string(opts) + + data, err := b.Raw("sendPoll", params) + if err != nil { + return nil, err + } + + return extractMessage(data) +} + +// Send delivers dice through bot b to recipient. +func (d *Dice) Send(b *Bot, to Recipient, opt *SendOptions) (*Message, error) { + params := map[string]string{ + "chat_id": to.Recipient(), + "emoji": string(d.Type), + } + b.embedSendOptions(params, opt) + + data, err := b.Raw("sendDice", params) + if err != nil { + return nil, err + } + + return extractMessage(data) +} + +// Send delivers game through bot b to recipient. +func (g *Game) Send(b *Bot, to Recipient, opt *SendOptions) (*Message, error) { + params := map[string]string{ + "chat_id": to.Recipient(), + "game_short_name": g.Name, + } + b.embedSendOptions(params, opt) + + data, err := b.Raw("sendGame", params) + if err != nil { + return nil, err + } + + return extractMessage(data) +} + +func thumbnailToFilemap(thumb *Photo) map[string]File { + if thumb != nil { + return map[string]File{"thumb": thumb.File} + } + return nil +} diff --git a/vendor/gopkg.in/telebot.v3/stickers.go b/vendor/gopkg.in/telebot.v3/stickers.go new file mode 100644 index 00000000000..3e0a6264b05 --- /dev/null +++ b/vendor/gopkg.in/telebot.v3/stickers.go @@ -0,0 +1,212 @@ +package telebot + +import ( + "encoding/json" + "strconv" +) + +type StickerSetType = string + +const ( + StickerRegular = "regular" + StickerMask = "mask" + StickerCustomEmoji = "custom_emoji" +) + +// StickerSet represents a sticker set. +type StickerSet struct { + Type StickerSetType `json:"sticker_type"` + Name string `json:"name"` + Title string `json:"title"` + Animated bool `json:"is_animated"` + Video bool `json:"is_video"` + Stickers []Sticker `json:"stickers"` + Thumbnail *Photo `json:"thumb"` + PNG *File `json:"png_sticker"` + TGS *File `json:"tgs_sticker"` + WebM *File `json:"webm_sticker"` + Emojis string `json:"emojis"` + ContainsMasks bool `json:"contains_masks"` // FIXME: can be removed + MaskPosition *MaskPosition `json:"mask_position"` +} + +// MaskPosition describes the position on faces where +// a mask should be placed by default. +type MaskPosition struct { + Feature MaskFeature `json:"point"` + XShift float32 `json:"x_shift"` + YShift float32 `json:"y_shift"` + Scale float32 `json:"scale"` +} + +// MaskFeature defines sticker mask position. +type MaskFeature string + +const ( + FeatureForehead MaskFeature = "forehead" + FeatureEyes MaskFeature = "eyes" + FeatureMouth MaskFeature = "mouth" + FeatureChin MaskFeature = "chin" +) + +// UploadSticker uploads a PNG file with a sticker for later use. +func (b *Bot) UploadSticker(to Recipient, png *File) (*File, error) { + files := map[string]File{ + "png_sticker": *png, + } + params := map[string]string{ + "user_id": to.Recipient(), + } + + data, err := b.sendFiles("uploadStickerFile", files, params) + if err != nil { + return nil, err + } + + var resp struct { + Result File + } + if err := json.Unmarshal(data, &resp); err != nil { + return nil, wrapError(err) + } + return &resp.Result, nil +} + +// StickerSet returns a sticker set on success. +func (b *Bot) StickerSet(name string) (*StickerSet, error) { + data, err := b.Raw("getStickerSet", map[string]string{"name": name}) + if err != nil { + return nil, err + } + + var resp struct { + Result *StickerSet + } + if err := json.Unmarshal(data, &resp); err != nil { + return nil, wrapError(err) + } + return resp.Result, nil +} + +// CreateStickerSet creates a new sticker set. +func (b *Bot) CreateStickerSet(to Recipient, s StickerSet) error { + files := make(map[string]File) + if s.PNG != nil { + files["png_sticker"] = *s.PNG + } + if s.TGS != nil { + files["tgs_sticker"] = *s.TGS + } + if s.WebM != nil { + files["webm_sticker"] = *s.WebM + } + + params := map[string]string{ + "user_id": to.Recipient(), + "sticker_type": s.Type, + "name": s.Name, + "title": s.Title, + "emojis": s.Emojis, + "contains_masks": strconv.FormatBool(s.ContainsMasks), + } + + if s.MaskPosition != nil { + data, _ := json.Marshal(&s.MaskPosition) + params["mask_position"] = string(data) + } + + _, err := b.sendFiles("createNewStickerSet", files, params) + return err +} + +// AddSticker adds a new sticker to the existing sticker set. +func (b *Bot) AddSticker(to Recipient, s StickerSet) error { + files := make(map[string]File) + if s.PNG != nil { + files["png_sticker"] = *s.PNG + } else if s.TGS != nil { + files["tgs_sticker"] = *s.TGS + } else if s.WebM != nil { + files["webm_sticker"] = *s.WebM + } + + params := map[string]string{ + "user_id": to.Recipient(), + "name": s.Name, + "emojis": s.Emojis, + } + + if s.MaskPosition != nil { + data, _ := json.Marshal(&s.MaskPosition) + params["mask_position"] = string(data) + } + + _, err := b.sendFiles("addStickerToSet", files, params) + return err +} + +// SetStickerPosition moves a sticker in set to a specific position. +func (b *Bot) SetStickerPosition(sticker string, position int) error { + params := map[string]string{ + "sticker": sticker, + "position": strconv.Itoa(position), + } + + _, err := b.Raw("setStickerPositionInSet", params) + return err +} + +// DeleteSticker deletes a sticker from a set created by the bot. +func (b *Bot) DeleteSticker(sticker string) error { + _, err := b.Raw("deleteStickerFromSet", map[string]string{"sticker": sticker}) + return err + +} + +// SetStickerSetThumb sets a thumbnail of the sticker set. +// Animated thumbnails can be set for animated sticker sets only. +// +// Thumbnail must be a PNG image, up to 128 kilobytes in size +// and have width and height exactly 100px, or a TGS animation +// up to 32 kilobytes in size. +// +// Animated sticker set thumbnail can't be uploaded via HTTP URL. +// +func (b *Bot) SetStickerSetThumb(to Recipient, s StickerSet) error { + files := make(map[string]File) + if s.PNG != nil { + files["thumb"] = *s.PNG + } else if s.TGS != nil { + files["thumb"] = *s.TGS + } + + params := map[string]string{ + "name": s.Name, + "user_id": to.Recipient(), + } + + _, err := b.sendFiles("setStickerSetThumb", files, params) + return err +} + +// CustomEmojiStickers returns the information about custom emoji stickers by their ids. +func (b *Bot) CustomEmojiStickers(ids []string) ([]Sticker, error) { + data, _ := json.Marshal(ids) + + params := map[string]string{ + "custom_emoji_ids": string(data), + } + + data, err := b.Raw("getCustomEmojiStickers", params) + if err != nil { + return nil, err + } + + var resp struct { + Result []Sticker + } + if err := json.Unmarshal(data, &resp); err != nil { + return nil, wrapError(err) + } + return resp.Result, nil +} diff --git a/vendor/gopkg.in/telebot.v3/telebot.go b/vendor/gopkg.in/telebot.v3/telebot.go new file mode 100644 index 00000000000..2aa1cd9a811 --- /dev/null +++ b/vendor/gopkg.in/telebot.v3/telebot.go @@ -0,0 +1,136 @@ +// Package telebot is a framework for Telegram bots. +// +// Example: +// +// package main +// +// import ( +// "time" +// tele "gopkg.in/telebot.v3" +// ) +// +// func main() { +// b, err := tele.NewBot(tele.Settings{ +// Token: "...", +// Poller: &tele.LongPoller{Timeout: 10 * time.Second}, +// }) +// if err != nil { +// return +// } +// +// b.Handle("/start", func(c tele.Context) error { +// return c.Send("Hello world!") +// }) +// +// b.Start() +// } +// +package telebot + +import "errors" + +var ( + ErrBadRecipient = errors.New("telebot: recipient is nil") + ErrUnsupportedWhat = errors.New("telebot: unsupported what argument") + ErrCouldNotUpdate = errors.New("telebot: could not fetch new updates") + ErrTrueResult = errors.New("telebot: result is True") + ErrBadContext = errors.New("telebot: context does not contain message") +) + +const DefaultApiURL = "https://api.telegram.org" + +// These are one of the possible events Handle() can deal with. +// +// For convenience, all Telebot-provided endpoints start with +// an "alert" character \a. +// +const ( + // Basic message handlers. + OnText = "\atext" + OnEdited = "\aedited" + OnPhoto = "\aphoto" + OnAudio = "\aaudio" + OnAnimation = "\aanimation" + OnDocument = "\adocument" + OnSticker = "\asticker" + OnVideo = "\avideo" + OnVoice = "\avoice" + OnVideoNote = "\avideo_note" + OnContact = "\acontact" + OnLocation = "\alocation" + OnVenue = "\avenue" + OnDice = "\adice" + OnInvoice = "\ainvoice" + OnPayment = "\apayment" + OnGame = "\agame" + OnPoll = "\apoll" + OnPollAnswer = "\apoll_answer" + OnPinned = "\apinned" + OnChannelPost = "\achannel_post" + OnEditedChannelPost = "\aedited_channel_post" + + OnAddedToGroup = "\aadded_to_group" + OnUserJoined = "\auser_joined" + OnUserLeft = "\auser_left" + OnNewGroupTitle = "\anew_chat_title" + OnNewGroupPhoto = "\anew_chat_photo" + OnGroupPhotoDeleted = "\achat_photo_deleted" + OnGroupCreated = "\agroup_created" + OnSuperGroupCreated = "\asupergroup_created" + OnChannelCreated = "\achannel_created" + + // OnMigration happens when group switches to + // a supergroup. You might want to update + // your internal references to this chat + // upon switching as its ID will change. + OnMigration = "\amigration" + + OnMedia = "\amedia" + OnCallback = "\acallback" + OnQuery = "\aquery" + OnInlineResult = "\ainline_result" + OnShipping = "\ashipping_query" + OnCheckout = "\apre_checkout_query" + OnMyChatMember = "\amy_chat_member" + OnChatMember = "\achat_member" + OnChatJoinRequest = "\achat_join_request" + OnProximityAlert = "\aproximity_alert_triggered" + OnAutoDeleteTimer = "\amessage_auto_delete_timer_changed" + OnWebApp = "\aweb_app" + + OnVideoChatStarted = "\avideo_chat_started" + OnVideoChatEnded = "\avideo_chat_ended" + OnVideoChatParticipants = "\avideo_chat_participants_invited" + OnVideoChatScheduled = "\avideo_chat_scheduled" +) + +// ChatAction is a client-side status indicating bot activity. +type ChatAction string + +const ( + Typing ChatAction = "typing" + UploadingPhoto ChatAction = "upload_photo" + UploadingVideo ChatAction = "upload_video" + UploadingAudio ChatAction = "upload_audio" + UploadingDocument ChatAction = "upload_document" + UploadingVNote ChatAction = "upload_video_note" + RecordingVideo ChatAction = "record_video" + RecordingAudio ChatAction = "record_audio" + RecordingVNote ChatAction = "record_video_note" + FindingLocation ChatAction = "find_location" + ChoosingSticker ChatAction = "choose_sticker" +) + +// ParseMode determines the way client applications treat the text of the message +type ParseMode = string + +const ( + ModeDefault ParseMode = "" + ModeMarkdown ParseMode = "Markdown" + ModeMarkdownV2 ParseMode = "MarkdownV2" + ModeHTML ParseMode = "HTML" +) + +// M is a shortcut for map[string]interface{}. Use it for passing +// arguments to the layout functions. +type M = map[string]interface{} diff --git a/vendor/gopkg.in/telebot.v3/update.go b/vendor/gopkg.in/telebot.v3/update.go new file mode 100644 index 00000000000..76da7cd8f70 --- /dev/null +++ b/vendor/gopkg.in/telebot.v3/update.go @@ -0,0 +1,346 @@ +package telebot + +import "strings" + +// Update object represents an incoming update. +type Update struct { + ID int `json:"update_id"` + + Message *Message `json:"message,omitempty"` + EditedMessage *Message `json:"edited_message,omitempty"` + ChannelPost *Message `json:"channel_post,omitempty"` + EditedChannelPost *Message `json:"edited_channel_post,omitempty"` + Callback *Callback `json:"callback_query,omitempty"` + Query *Query `json:"inline_query,omitempty"` + InlineResult *InlineResult `json:"chosen_inline_result,omitempty"` + ShippingQuery *ShippingQuery `json:"shipping_query,omitempty"` + PreCheckoutQuery *PreCheckoutQuery `json:"pre_checkout_query,omitempty"` + Poll *Poll `json:"poll,omitempty"` + PollAnswer *PollAnswer `json:"poll_answer,omitempty"` + MyChatMember *ChatMemberUpdate `json:"my_chat_member,omitempty"` + ChatMember *ChatMemberUpdate `json:"chat_member,omitempty"` + ChatJoinRequest *ChatJoinRequest `json:"chat_join_request,omitempty"` +} + +// ProcessUpdate processes a single incoming update. +// A started bot calls this function automatically. +func (b *Bot) ProcessUpdate(u Update) { + c := b.NewContext(u) + + if u.Message != nil { + m := u.Message + + if m.PinnedMessage != nil { + b.handle(OnPinned, c) + return + } + + // Commands + if m.Text != "" { + // Filtering malicious messages + if m.Text[0] == '\a' { + return + } + + match := cmdRx.FindAllStringSubmatch(m.Text, -1) + if match != nil { + // Syntax: "@ " + command, botName := match[0][1], match[0][3] + + if botName != "" && !strings.EqualFold(b.Me.Username, botName) { + return + } + + m.Payload = match[0][5] + if b.handle(command, c) { + return + } + } + + // 1:1 satisfaction + if b.handle(m.Text, c) { + return + } + + b.handle(OnText, c) + return + } + + if b.handleMedia(c) { + return + } + + if m.Contact != nil { + b.handle(OnContact, c) + return + } + if m.Location != nil { + b.handle(OnLocation, c) + return + } + if m.Venue != nil { + b.handle(OnVenue, c) + return + } + if m.Game != nil { + b.handle(OnGame, c) + return + } + if m.Dice != nil { + b.handle(OnDice, c) + return + } + if m.Invoice != nil { + b.handle(OnInvoice, c) + return + } + if m.Payment != nil { + b.handle(OnPayment, c) + return + } + + wasAdded := (m.UserJoined != nil && m.UserJoined.ID == b.Me.ID) || + (m.UsersJoined != nil && isUserInList(b.Me, m.UsersJoined)) + if m.GroupCreated || m.SuperGroupCreated || wasAdded { + b.handle(OnAddedToGroup, c) + return + } + + if m.UserJoined != nil { + b.handle(OnUserJoined, c) + return + } + + if m.UsersJoined != nil { + for _, user := range m.UsersJoined { + m.UserJoined = &user + b.handle(OnUserJoined, c) + } + return + } + + if m.UserLeft != nil { + b.handle(OnUserLeft, c) + return + } + + if m.NewGroupTitle != "" { + b.handle(OnNewGroupTitle, c) + return + } + + if m.NewGroupPhoto != nil { + b.handle(OnNewGroupPhoto, c) + return + } + + if m.GroupPhotoDeleted { + b.handle(OnGroupPhotoDeleted, c) + return + } + + if m.GroupCreated { + b.handle(OnGroupCreated, c) + return + } + + if m.SuperGroupCreated { + b.handle(OnSuperGroupCreated, c) + return + } + + if m.ChannelCreated { + b.handle(OnChannelCreated, c) + return + } + + if m.MigrateTo != 0 { + m.MigrateFrom = m.Chat.ID + b.handle(OnMigration, c) + return + } + + if m.VideoChatStarted != nil { + b.handle(OnVideoChatStarted, c) + return + } + + if m.VideoChatEnded != nil { + b.handle(OnVideoChatEnded, c) + return + } + + if m.VideoChatParticipants != nil { + b.handle(OnVideoChatParticipants, c) + return + } + + if m.VideoChatScheduled != nil { + b.handle(OnVideoChatScheduled, c) + return + } + + if m.WebAppData != nil { + b.handle(OnWebApp, c) + } + + if m.ProximityAlert != nil { + b.handle(OnProximityAlert, c) + return + } + + if m.AutoDeleteTimer != nil { + b.handle(OnAutoDeleteTimer, c) + return + } + } + + if u.EditedMessage != nil { + b.handle(OnEdited, c) + return + } + + if u.ChannelPost != nil { + m := u.ChannelPost + + if m.PinnedMessage != nil { + b.handle(OnPinned, c) + return + } + + b.handle(OnChannelPost, c) + return + } + + if u.EditedChannelPost != nil { + b.handle(OnEditedChannelPost, c) + return + } + + if u.Callback != nil { + if data := u.Callback.Data; data != "" && data[0] == '\f' { + match := cbackRx.FindAllStringSubmatch(data, -1) + if match != nil { + unique, payload := match[0][1], match[0][3] + if handler, ok := b.handlers["\f"+unique]; ok { + u.Callback.Unique = unique + u.Callback.Data = payload + b.runHandler(handler, c) + return + } + } + } + + b.handle(OnCallback, c) + return + } + + if u.Query != nil { + b.handle(OnQuery, c) + return + } + + if u.InlineResult != nil { + b.handle(OnInlineResult, c) + return + } + + if u.ShippingQuery != nil { + b.handle(OnShipping, c) + return + } + + if u.PreCheckoutQuery != nil { + b.handle(OnCheckout, c) + return + } + + if u.Poll != nil { + b.handle(OnPoll, c) + return + } + + if u.PollAnswer != nil { + b.handle(OnPollAnswer, c) + return + } + + if u.MyChatMember != nil { + b.handle(OnMyChatMember, c) + return + } + + if u.ChatMember != nil { + b.handle(OnChatMember, c) + return + } + + if u.ChatJoinRequest != nil { + b.handle(OnChatJoinRequest, c) + return + } +} + +func (b *Bot) handle(end string, c Context) bool { + if handler, ok := b.handlers[end]; ok { + b.runHandler(handler, c) + return true + } + return false +} + +func (b *Bot) handleMedia(c Context) bool { + var ( + m = c.Message() + fired = true + ) + + switch { + case m.Photo != nil: + fired = b.handle(OnPhoto, c) + case m.Voice != nil: + fired = b.handle(OnVoice, c) + case m.Audio != nil: + fired = b.handle(OnAudio, c) + case m.Animation != nil: + fired = b.handle(OnAnimation, c) + case m.Document != nil: + fired = b.handle(OnDocument, c) + case m.Sticker != nil: + fired = b.handle(OnSticker, c) + case m.Video != nil: + fired = b.handle(OnVideo, c) + case m.VideoNote != nil: + fired = b.handle(OnVideoNote, c) + default: + return false + } + + if !fired { + return b.handle(OnMedia, c) + } + + return true +} + +func (b *Bot) runHandler(h HandlerFunc, c Context) { + f := func() { + if err := h(c); err != nil { + b.OnError(err, c) + } + } + if b.synchronous { + f() + } else { + go f() + } +} + +func isUserInList(user *User, list []User) bool { + for _, user2 := range list { + if user.ID == user2.ID { + return true + } + } + return false +} diff --git a/vendor/gopkg.in/telebot.v3/video_chat.go b/vendor/gopkg.in/telebot.v3/video_chat.go new file mode 100644 index 00000000000..448db4161e0 --- /dev/null +++ b/vendor/gopkg.in/telebot.v3/video_chat.go @@ -0,0 +1,29 @@ +package telebot + +import "time" + +// VideoChatStarted represents a service message about a video chat +// started in the chat. +type VideoChatStarted struct{} + +// VideoChatEnded represents a service message about a video chat +// ended in the chat. +type VideoChatEnded struct { + Duration int `json:"duration"` // in seconds +} + +// VideoChatParticipants represents a service message about new +// members invited to a video chat +type VideoChatParticipants struct { + Users []User `json:"users"` +} + +// VideoChatScheduled represents a service message about a video chat scheduled in the chat. +type VideoChatScheduled struct { + Unixtime int64 `json:"start_date"` +} + +// StartsAt returns the point when the video chat is supposed to be started by a chat administrator. +func (v *VideoChatScheduled) StartsAt() time.Time { + return time.Unix(v.Unixtime, 0) +} diff --git a/vendor/gopkg.in/telebot.v3/web_app.go b/vendor/gopkg.in/telebot.v3/web_app.go new file mode 100644 index 00000000000..841378416a0 --- /dev/null +++ b/vendor/gopkg.in/telebot.v3/web_app.go @@ -0,0 +1,18 @@ +package telebot + +// WebApp represents a parameter of the inline keyboard button +// or the keyboard button used to launch Web App. +type WebApp struct { + URL string `json:"url"` +} + +// WebAppMessage describes an inline message sent by a Web App on behalf of a user. +type WebAppMessage struct { + InlineMessageID string `json:"inline_message_id"` +} + +// WebAppData object represents a data sent from a Web App to the bot +type WebAppData struct { + Data string `json:"data"` + Text string `json:"button_text"` +} diff --git a/vendor/gopkg.in/telebot.v3/webhook.go b/vendor/gopkg.in/telebot.v3/webhook.go new file mode 100644 index 00000000000..13e99bcdfd6 --- /dev/null +++ b/vendor/gopkg.in/telebot.v3/webhook.go @@ -0,0 +1,207 @@ +package telebot + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "strconv" +) + +// A WebhookTLS specifies the path to a key and a cert so the poller can open +// a TLS listener. +type WebhookTLS struct { + Key string `json:"key"` + Cert string `json:"cert"` +} + +// A WebhookEndpoint describes the endpoint to which telegram will send its requests. +// This must be a public URL and can be a loadbalancer or something similar. If the +// endpoint uses TLS and the certificate is self-signed you have to add the certificate +// path of this certificate so telegram will trust it. This field can be ignored if you +// have a trusted certificate (letsencrypt, ...). +type WebhookEndpoint struct { + PublicURL string `json:"public_url"` + Cert string `json:"cert"` +} + +// A Webhook configures the poller for webhooks. It opens a port on the given +// listen address. If TLS is filled, the listener will use the key and cert to open +// a secure port. Otherwise it will use plain HTTP. +// +// If you have a loadbalancer ore other infrastructure in front of your service, you +// must fill the Endpoint structure so this poller will send this data to telegram. If +// you leave these values empty, your local address will be sent to telegram which is mostly +// not what you want (at least while developing). If you have a single instance of your +// bot you should consider to use the LongPoller instead of a WebHook. +// +// You can also leave the Listen field empty. In this case it is up to the caller to +// add the Webhook to a http-mux. +// +type Webhook struct { + Listen string `json:"url"` + MaxConnections int `json:"max_connections"` + AllowedUpdates []string `json:"allowed_updates"` + IP string `json:"ip_address"` + DropUpdates bool `json:"drop_pending_updates"` + SecretToken string `json:"secret_token"` + + // (WebhookInfo) + HasCustomCert bool `json:"has_custom_certificate"` + PendingUpdates int `json:"pending_update_count"` + ErrorUnixtime int64 `json:"last_error_date"` + ErrorMessage string `json:"last_error_message"` + SyncErrorUnixtime int64 `json:"last_synchronization_error_date"` + + TLS *WebhookTLS + Endpoint *WebhookEndpoint + + dest chan<- Update + bot *Bot +} + +func (h *Webhook) getFiles() map[string]File { + m := make(map[string]File) + + if h.TLS != nil { + m["certificate"] = FromDisk(h.TLS.Cert) + } + // check if it is overwritten by an endpoint + if h.Endpoint != nil { + if h.Endpoint.Cert == "" { + // this can be the case if there is a loadbalancer or reverseproxy in + // front with a public cert. in this case we do not need to upload it + // to telegram. we delete the certificate from the map, because someone + // can have an internal TLS listener with a private cert + delete(m, "certificate") + } else { + // someone configured a certificate + m["certificate"] = FromDisk(h.Endpoint.Cert) + } + } + return m +} + +func (h *Webhook) getParams() map[string]string { + params := make(map[string]string) + + if h.MaxConnections != 0 { + params["max_connections"] = strconv.Itoa(h.MaxConnections) + } + if len(h.AllowedUpdates) > 0 { + data, _ := json.Marshal(h.AllowedUpdates) + params["allowed_updates"] = string(data) + } + if h.IP != "" { + params["ip_address"] = h.IP + } + if h.DropUpdates { + params["drop_pending_updates"] = strconv.FormatBool(h.DropUpdates) + } + if h.SecretToken != "" { + params["secret_token"] = h.SecretToken + } + + if h.TLS != nil { + params["url"] = "https://" + h.Listen + } else { + // this will not work with telegram, they want TLS + // but i allow this because telegram will send an error + // when you register this hook. in their docs they write + // that port 80/http is allowed ... + params["url"] = "http://" + h.Listen + } + if h.Endpoint != nil { + params["url"] = h.Endpoint.PublicURL + } + return params +} + +func (h *Webhook) Poll(b *Bot, dest chan Update, stop chan struct{}) { + if err := b.SetWebhook(h); err != nil { + b.debug(err) + close(stop) + return + } + + // store the variables so the HTTP-handler can use 'em + h.dest = dest + h.bot = b + + if h.Listen == "" { + h.waitForStop(stop) + return + } + + s := &http.Server{ + Addr: h.Listen, + Handler: h, + } + + go func(stop chan struct{}) { + h.waitForStop(stop) + s.Shutdown(context.Background()) + }(stop) + + if h.TLS != nil { + s.ListenAndServeTLS(h.TLS.Cert, h.TLS.Key) + } else { + s.ListenAndServe() + } +} + +func (h *Webhook) waitForStop(stop chan struct{}) { + <-stop + close(stop) +} + +// The handler simply reads the update from the body of the requests +// and writes them to the update channel. +func (h *Webhook) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if h.SecretToken != "" && r.Header.Get("X-Telegram-Bot-Api-Secret-Token") != h.SecretToken { + h.bot.debug(fmt.Errorf("invalid secret token in request")) + return + } + + var update Update + if err := json.NewDecoder(r.Body).Decode(&update); err != nil { + h.bot.debug(fmt.Errorf("cannot decode update: %v", err)) + return + } + h.dest <- update +} + +// Webhook returns the current webhook status. +func (b *Bot) Webhook() (*Webhook, error) { + data, err := b.Raw("getWebhookInfo", nil) + if err != nil { + return nil, err + } + + var resp struct { + Result Webhook + } + if err := json.Unmarshal(data, &resp); err != nil { + return nil, wrapError(err) + } + return &resp.Result, nil +} + +// SetWebhook configures a bot to receive incoming +// updates via an outgoing webhook. +func (b *Bot) SetWebhook(w *Webhook) error { + _, err := b.sendFiles("setWebhook", w.getFiles(), w.getParams()) + return err +} + +// RemoveWebhook removes webhook integration. +func (b *Bot) RemoveWebhook(dropPending ...bool) error { + drop := false + if len(dropPending) > 0 { + drop = dropPending[0] + } + _, err := b.Raw("deleteWebhook", map[string]bool{ + "drop_pending_updates": drop, + }) + return err +} diff --git a/vendor/modules.txt b/vendor/modules.txt index e9f30563366..d561d2a54b8 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -681,13 +681,16 @@ github.com/prometheus/alertmanager/inhibit github.com/prometheus/alertmanager/nflog github.com/prometheus/alertmanager/nflog/nflogpb github.com/prometheus/alertmanager/notify +github.com/prometheus/alertmanager/notify/discord github.com/prometheus/alertmanager/notify/email github.com/prometheus/alertmanager/notify/opsgenie github.com/prometheus/alertmanager/notify/pagerduty github.com/prometheus/alertmanager/notify/pushover github.com/prometheus/alertmanager/notify/slack github.com/prometheus/alertmanager/notify/sns +github.com/prometheus/alertmanager/notify/telegram github.com/prometheus/alertmanager/notify/victorops +github.com/prometheus/alertmanager/notify/webex github.com/prometheus/alertmanager/notify/webhook github.com/prometheus/alertmanager/notify/wechat github.com/prometheus/alertmanager/pkg/labels @@ -1365,6 +1368,9 @@ gopkg.in/alecthomas/kingpin.v2 # gopkg.in/ini.v1 v1.67.0 ## explicit gopkg.in/ini.v1 +# gopkg.in/telebot.v3 v3.1.3 +## explicit; go 1.13 +gopkg.in/telebot.v3 # gopkg.in/yaml.v2 v2.4.0 ## explicit; go 1.15 gopkg.in/yaml.v2