Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add telegram notification support #33

Merged
merged 11 commits into from
Jun 7, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 27 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,53 +30,72 @@ $ ./covaccine-notifier --help
CoWIN Vaccine availability notifier India

Usage:
covaccine-notifier [FLAGS] [flags]
covaccine-notifier [command]

Available Commands:
email Notify slots availability using Email
help Help about any command
telegram Notify slots availability using Telegram

Flags:
-a, --age int Search appointment for age
-a, --age int Search appointment for age (required)
-d, --district string Search by district name
-o, --dose int Dose preference - 1 or 2. Default: 0 (both)
-e, --email string Email address to send notifications
-f, --fee string Fee preferences - free (or) paid. Default: No preference
-h, --help help for covaccine-notifier
-i, --interval int Interval to repeat the search. Default: (60) second
-m, --min-capacity int Filter by minimum vaccination capacity. Default: (1)
-p, --password string Email ID password for auth
-c, --pincode string Search by pin code
-s, --state string Search by state name
-v, --vaccine string Vaccine preferences - covishield (or) covaxin. Default: No preference

Use "covaccine-notifier [command] --help" for more information about a command.
```
example
```
$ ./covaccine-notifier email --help
```

**Note:** Gmail password won't work for 2FA enabled accounts. Follow [this](https://support.google.com/accounts/answer/185833?p=InvalidSecondFactor&visit_id=637554658548216477-2576856839&rd=1) guide to generate app token password and use it with `--password` arg

## Integration with Telegram

For telegram bot integration with covaccine-notifier follow [this](./docs/telegram-integration.md).

## Examples

### Terminal

#### Search by State and District

```
covaccine-notifier --state Maharashtra --district Akola --age 27 --email <email-id> --password <email-password>
covaccine-notifier email --state Maharashtra --district Akola --age 27 --username <email-id> --password <email-password>
```

#### Search by Pin Code

```
covaccine-notifier --pincode 444002 --age 27 --email <email-id> --password <email-password>
covaccine-notifier email --pincode 444002 --age 27 --username <email-id> --password <email-password>
```

#### Enable Telegram Notification

```
covaccine-notifier telegram --pincode 444002 --age 27 --token <telegram-token> --username <telegram-username>
```

### Docker

```
docker run --rm -ti ghcr.io/prasadg193/covaccine-notifier:v0.2.0 --state Maharashtra --district Akola --age 27 --email <email-id> --password <email-password>
docker run --rm -ti ghcr.io/prasadg193/covaccine-notifier:v0.2.0 email --state Maharashtra --district Akola --age 27 --username <email-id> --password <email-password>
```

### Running on Kubernetes Cluster

If you are not willing to keep your terminal on all the time :smile:, you can also create a Pod on K8s cluster

```
kubectl run covaccine-notifier --image=ghcr.io/prasadg193/covaccine-notifier:v0.2.0 --command -- /covaccine-notifier --state Maharashtra --district Akola --age 27 --email <email-id> --password <email-password>
kubectl run covaccine-notifier --image=ghcr.io/prasadg193/covaccine-notifier:v0.2.0 --command -- /covaccine-notifier email --state Maharashtra --district Akola --age 27 --username <email-id> --password <email-password>
```

## Contributing
Expand Down
Binary file added docs/images/telegram-bot-creation-1.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/telegram-bot-creation-2.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/telegram-bot-creation-3.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/telegram-bot-creation-4.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/telegram-bot-creation-5.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
44 changes: 44 additions & 0 deletions docs/telegram-integration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Telegram Intergration with Covaccine Notifier

The way to setup telegram bot with covaccine notifier

* **Step 1 : Create Telegram bot**
* Conduct search for ```@Botfather``` or you can directly open the URL https://telegram.me/botfather to start conversations with BotFather. BotFather will then introduce itself and will display a START button at the bottom for the user.

![bot image 1](./images/telegram-bot-creation-1.jpg)

* Once you click on the START button, BotFather will provide you with all the commands that can be used for creating a new bot, as shown in the following screenshot

![bot image 2](./images/telegram-bot-creation-2.jpg)

* Now, click on the link /newbot from your conversation with BotFather. With this command, BotFather will ask you to choose a name for your bot.

![bot image 3](./images/telegram-bot-creation-3.jpg)

At this point, BotFather has created your bot and has also provided a token for your bot. This token(blurred with yellow color) can be used whilst wiring up your bot with Telegram bot APIs.


* **Step 2 : Start conversation with newly created bot**

* To start conversation with newly created bot click on link recevied in above message

![bot image 4](./images/telegram-bot-creation-4.jpg)


* Check whether username is set for your telegram account ?
If not set the username.

* **Step 3 : Start covaccine-notifier with following command**

```
covaccine-notifier telegram --pincode 444002 --age 47 --token <telegram-token> --username <telegram-username>
```

Sample Screenshot

![bot image 5](./images/telegram-bot-creation-5.jpg)





2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ module github.com/PrasadG193/covaccine-notifier
go 1.16

require (
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible
github.com/pkg/errors v0.8.1
github.com/spf13/cobra v1.1.3
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
)
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible h1:2cauKuaELYAEARXRkq2LrJ0yDDv1rW7+wrTEdVL3uaU=
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
Expand Down Expand Up @@ -160,6 +162,8 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM=
github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
Expand Down
66 changes: 46 additions & 20 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,38 @@ import (

"github.com/pkg/errors"
"github.com/spf13/cobra"

"github.com/PrasadG193/covaccine-notifier/pkg/notify"
)

var (
pinCode, state, district, email, password, date, vaccine, fee string

age, interval, minCapacity, dose int
pinCode, state, district, vaccine, fee string
username, password, token string
age, interval, minCapacity, dose int

rootCmd = &cobra.Command{
Use: "covaccine-notifier [FLAGS]",
Short: "CoWIN Vaccine availability notifier India",
}

telegramCmd = &cobra.Command{
Use: "telegram [FLAGS]",
Short: "Notify slots availability using Telegram",
RunE: func(cmd *cobra.Command, args []string) error {
notifier, err := notify.NewTelegram(username, token)
if err != nil {
return err
}
return Run(args, notifier)
},
}

emailCmd = &cobra.Command{
Use: "email [FLAGS]",
Short: "Notify slots availability using Email",
RunE: func(cmd *cobra.Command, args []string) error {
return Run(args)
notifier := notify.NewEmail(username, password)
return Run(args, notifier)
},
}
)
Expand All @@ -35,6 +55,8 @@ const (
searchIntervalEnv = "SEARCH_INTERVAL"
vaccineEnv = "VACCINE"
feeEnv = "FEE"
tgApiTokenEnv = "TG_TOKEN"
tgUsernameEnv = "TG_USERNAME"
minCapacityEnv = "MIN_CAPACITY"
doseEnv = "DOSE"

Expand All @@ -49,17 +71,28 @@ const (
)

func init() {
rootCmd.PersistentFlags().IntVarP(&age, "age", "a", getIntEnv(ageEnv), "Search appointment for age (required)")
rootCmd.MarkPersistentFlagRequired("age")
rootCmd.PersistentFlags().StringVarP(&pinCode, "pincode", "c", os.Getenv(pinCodeEnv), "Search by pin code")
rootCmd.PersistentFlags().StringVarP(&state, "state", "s", os.Getenv(stateNameEnv), "Search by state name")
rootCmd.PersistentFlags().StringVarP(&district, "district", "d", os.Getenv(districtNameEnv), "Search by district name")
rootCmd.PersistentFlags().IntVarP(&age, "age", "a", getIntEnv(ageEnv), "Search appointment for age")
rootCmd.PersistentFlags().StringVarP(&email, "email", "e", os.Getenv(emailIDEnv), "Email address to send notifications")
rootCmd.PersistentFlags().StringVarP(&password, "password", "p", os.Getenv(emailPasswordEnv), "Email ID password for auth")
rootCmd.PersistentFlags().IntVarP(&interval, "interval", "i", getIntEnv(searchIntervalEnv), fmt.Sprintf("Interval to repeat the search. Default: (%v) second", defaultSearchInterval))
rootCmd.PersistentFlags().StringVarP(&vaccine, "vaccine", "v", os.Getenv(vaccineEnv), fmt.Sprintf("Vaccine preferences - covishield (or) covaxin. Default: No preference"))
rootCmd.PersistentFlags().StringVarP(&fee, "fee", "f", os.Getenv(feeEnv), fmt.Sprintf("Fee preferences - free (or) paid. Default: No preference"))
rootCmd.PersistentFlags().IntVarP(&minCapacity, "min-capacity", "m", getIntEnv(minCapacityEnv), fmt.Sprintf("Filter by minimum vaccination capacity. Default: (%v)", defaultMinCapacity))
rootCmd.PersistentFlags().IntVarP(&dose, "dose", "o", getIntEnv(doseEnv), "Dose preference - 1 or 2. Default: 0 (both)")

rootCmd.AddCommand(emailCmd, telegramCmd)

emailCmd.PersistentFlags().StringVarP(&username, "username", "u", os.Getenv(emailIDEnv), "Email address to send notifications")
emailCmd.MarkPersistentFlagRequired("username")
emailCmd.PersistentFlags().StringVarP(&password, "password", "p", os.Getenv(emailPasswordEnv), "Email ID password for auth")
emailCmd.MarkPersistentFlagRequired("password")

telegramCmd.PersistentFlags().StringVarP(&username, "username", "u", os.Getenv(tgUsernameEnv), "telegram username")
telegramCmd.MarkPersistentFlagRequired("username")
telegramCmd.PersistentFlags().StringVarP(&token, "token", "t", os.Getenv(tgApiTokenEnv), "telegram bot API token")
telegramCmd.MarkPersistentFlagRequired("token")
}

// Execute executes the main command
Expand All @@ -76,12 +109,6 @@ func checkFlags() error {
if len(pinCode) == 0 && (len(state) == 0 || len(district) == 0) {
return errors.New("Missing state or district name option")
}
if age == 0 {
return errors.New("Missing age option")
}
if len(email) == 0 || len(password) == 0 {
return errors.New("Missing email creds")
}
if interval == 0 {
interval = defaultSearchInterval
}
Expand Down Expand Up @@ -116,30 +143,29 @@ func getIntEnv(envVar string) int {
return i
}

func Run(args []string) error {
func Run(args []string, notifier notify.Notifier) error {
if err := checkFlags(); err != nil {
return err
}
if err := checkSlots(); err != nil {
if err := checkSlots(notifier); err != nil {
return err
}
ticker := time.NewTicker(time.Second * time.Duration(interval))
defer ticker.Stop()
for {
select {
case <-ticker.C:
if err := checkSlots(); err != nil {
if err := checkSlots(notifier); err != nil {
return err
}
}
}
return nil
}

func checkSlots() error {
func checkSlots(notifier notify.Notifier) error {
// Search for slots
if len(pinCode) != 0 {
return searchByPincode(pinCode)
return searchByPincode(notifier, pinCode)
}
return searchByStateDistrict(age, state, district)
return searchByStateDistrict(notifier, state, district)
}
22 changes: 0 additions & 22 deletions notifier.go

This file was deleted.

43 changes: 43 additions & 0 deletions pkg/notify/email.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//Package notify has functions and types used for sending notifications on different communication channel
package notify

import (
"fmt"
"net/smtp"
)

const (
smtpServerAddress = "smtp.gmail.com"
smtpServerPort = "587"
)

type Email struct {
ID string
Pass string
}

// NewEmail returns the instance of Email
func NewEmail(id, pass string) Notifier {
return &Email{
ID: id,
Pass: pass,
}
}

// SendMessage takes message body and send it to the given email-id
func (e *Email) SendMessage(body string) error {
msg := "From: " + e.ID + "\n" +
"To: " + e.ID + "\n" +
"Subject: Vaccination slots are available\n\n" +
"Vaccination slots are available at the following centers:\n\n" +
body

err := smtp.SendMail(fmt.Sprintf("%s:%s", smtpServerAddress, smtpServerPort),
smtp.PlainAuth("", e.ID, e.Pass, smtpServerAddress),
e.ID, []string{e.ID}, []byte(msg))

if err != nil {
return err
}
return nil
}
7 changes: 7 additions & 0 deletions pkg/notify/notify.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Package notify has functions and types used for sending notifications on different communication channel
package notify

// Notifier can be any type that can SendMessage
type Notifier interface {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a comment on exported functions and types. This applies to other changes as well

SendMessage(string) error
}
Loading