Skip to content

Commit

Permalink
Add "bot" mode (#11)
Browse files Browse the repository at this point in the history
This commit adds a `start-bot` command, which connects to WhatsApp and allows executing a few commands:

- `!psr` to get an updated summary of the SAMU activities of tomorrow
- `!bspp` to get the current summary of the BSPP activities of tomorrow
  • Loading branch information
fabien-chebel authored Jun 19, 2022
1 parent a7316d4 commit f60bf7e
Show file tree
Hide file tree
Showing 5 changed files with 241 additions and 58 deletions.
37 changes: 37 additions & 0 deletions bot-service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package main

import (
"fmt"
"github.com/fabien-chebel/pegass-cli/whatsapp"
log "github.com/sirupsen/logrus"
"go.mau.fi/whatsmeow/types"
"time"
)

type BotService struct {
pegassClient *PegassClient
chatClient *whatsapp.WhatsAppClient
}

func (b *BotService) SendActivitySummary(recipient types.JID, kind ActivityKind) {

err := b.chatClient.SendMessage("🤖 C'est reçu. Je génère l'état des postes de demain.", recipient)
if err != nil {
log.Errorf("failed to send whatsapp message: %s", err.Error())
return
}

day := time.Now().AddDate(0, 0, 1).Format("2006-01-02")
log.Info("Fetching activity summary for day ", day)
summary, err := b.pegassClient.GetActivityOnDay(day, kind)
if err != nil {
log.Errorf("failed to generate activity summary for day '%s' and kind '%d'", day, kind)
return
}
summary = fmt.Sprintf("Etat du réseau de secours de demain (%s):\n%s", day, summary)

err = b.chatClient.SendMessage(
summary,
recipient,
)
}
55 changes: 49 additions & 6 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,20 @@ import (
"gopkg.in/urfave/cli.v1"
"os"
"strconv"
"strings"
"time"
)

var pegassClient = PegassClient{}
var pegassClient PegassClient

func initClient() (Config, error) {
configData := parseConfig()
return configData, pegassClient.Authenticate(configData.Username, configData.Password, configData.TotpSecretKey)
pegassClient = PegassClient{
Username: configData.Username,
Password: configData.Password,
TotpSecretKey: configData.TotpSecretKey,
}
return configData, pegassClient.Authenticate()
}

func initLogs(verbose bool) {
Expand All @@ -42,8 +48,11 @@ func main() {
Name: "login",
Usage: "Authenticate to Pegass",
Action: func(c *cli.Context) error {
configData := parseConfig()
err := pegassClient.Authenticate(configData.Username, configData.Password, configData.TotpSecretKey)
_, err := initClient()
if err != nil {
return nil
}
err = pegassClient.Authenticate()
if err != nil {
return err
}
Expand Down Expand Up @@ -231,7 +240,7 @@ func main() {

day := time.Now().AddDate(0, 0, 1).Format("2006-01-02")
log.Info("Fetching activity summary for day ", day)
summary, err := pegassClient.GetActivityOnDay(day)
summary, err := pegassClient.GetActivityOnDay(day, SAMU)
if err != nil {
return err
}
Expand All @@ -246,7 +255,7 @@ func main() {
if err != nil {
return err
}
err = whatsAppClient.SendMessageToGroup(
err = whatsAppClient.SendMessage(
summary,
jid,
)
Expand All @@ -273,6 +282,40 @@ func main() {
return whatsAppClient.PrintGroupList()
},
},
{
Name: "start-bot",
Action: func(c *cli.Context) error {
_, err := initClient()
if err != nil {
return err
}

whatsAppClient := whatsapp.NewClient()
var botService = BotService{
pegassClient: &pegassClient,
chatClient: &whatsAppClient,
}
whatsAppClient.SetMessageCallback(func(senderName string, senderId types.JID, chatId types.JID, content string) {
log.Infof("Received message from '%s': %s", senderName, content)
var recipient = senderId
if chatId != (types.JID{}) {
recipient = chatId
}
lowerMessage := strings.ToLower(content)

if strings.HasPrefix(lowerMessage, "!psr") {
botService.SendActivitySummary(recipient, SAMU)
} else if strings.HasPrefix(lowerMessage, "!bspp") {
botService.SendActivitySummary(recipient, BSPP)
}
})
err = whatsAppClient.StartBot()
if err != nil {
return err
}
return nil
},
},
}
err := app.Run(os.Args)
if err != nil {
Expand Down
75 changes: 65 additions & 10 deletions pegass-client.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,26 @@ import (
"time"
)

type ActivityKind int

const (
SAMU ActivityKind = iota
BSPP
)

const (
ACTIVITY_RESEAU_15_ID = 10115
ACTIVITY_RESEAU_18_ID = 10116
ACTIVITY_REGULATION_ID = 10114
)

type PegassClient struct {
cookieJar *cookiejar.Jar
httpClient http.Client
structures map[int]string
cookieJar *cookiejar.Jar
httpClient http.Client
structures map[int]string
Username string
Password string
TotpSecretKey string
}

func (p *PegassClient) init() error {
Expand Down Expand Up @@ -106,13 +122,13 @@ func (p PegassClient) obtainOktaSessionToken(factorId string, mfaCode string, st
return mfaAuthResponse.SessionToken, nil
}

func (p *PegassClient) Authenticate(username string, password string, totpSecretKey string) error {
func (p *PegassClient) Authenticate() error {
err := p.init()
if err != nil {
return err
}

passwordAuthResponse, err := p.kickOffAuthentication(username, password)
passwordAuthResponse, err := p.kickOffAuthentication(p.Username, p.Password)
if err != nil {
return err
}
Expand All @@ -129,7 +145,7 @@ func (p *PegassClient) Authenticate(username string, password string, totpSecret
return fmt.Errorf("unable to find any TOTP generator registered to this account: %w", err)
}

code, err := totp.GenerateCode(totpSecretKey, time.Now())
code, err := totp.GenerateCode(p.TotpSecretKey, time.Now())
if err != nil {
return fmt.Errorf("failed to generate TOTP code: %w", err)
}
Expand Down Expand Up @@ -499,6 +515,28 @@ func (p PegassClient) GetActivityStats() (map[string]redcross.RegulationStats, e
return statsMap, nil
}

func (p *PegassClient) GetMainMoyenComForUser(nivol string) (string, error) {
response, err := p.httpClient.Get(fmt.Sprintf("https://pegass.croix-rouge.fr/crf/rest/moyencomutilisateur?utilisateur=%s", nivol))
if err != nil {
return "", err
}
defer response.Body.Close()

var moyenComs []redcross.Coordonnees
err = json.NewDecoder(response.Body).Decode(&moyenComs)
if err != nil {
return "", err
}

for _, com := range moyenComs {
if com.MoyenComID == "POR" {
return com.Libelle, nil
}
}

return "", nil
}

func (p PegassClient) GetUsersForRole(role redcross.Role) ([]redcross.Utilisateur, error) {
err := p.init()
if err != nil {
Expand Down Expand Up @@ -715,20 +753,28 @@ func (p *PegassClient) lintActivity(activity redcross.Activity) (string, error)

inscriptions := redcross.InscriptionList{}
err = json.NewDecoder(response.Body).Decode(&inscriptions)
var chiefContactDetails string

var minorCount, chiefCount, driverCount, pse2Count, pse1Count, traineeCount, dispatcherCount, radioOperatorCount, unknownCount int
for _, inscription := range inscriptions {
userDetails, err := p.GetUserDetails(inscription.Utilisateur.ID)
if err != nil {
return "", err
}
phoneNumber, err := p.GetMainMoyenComForUser(inscription.Utilisateur.ID)
if err != nil {
log.Warnf("failed to fetch phone number of user '%s'", inscription.Utilisateur.ID)
}
if userDetails.Mineur {
minorCount++
}

if inscription.Type == "NOMI" && (inscription.Role == "254" || inscription.Role == "255") {
// CI RESEAU || CI BSPP
chiefCount++
if phoneNumber != "" {
chiefContactDetails = fmt.Sprintf("📞 %s %s %s", userDetails.Prenom, userDetails.Nom, phoneNumber)
}
} else if inscription.Type == "COMP" && inscription.Role == "10" {
// CH
driverCount++
Expand Down Expand Up @@ -768,6 +814,9 @@ func (p *PegassClient) lintActivity(activity redcross.Activity) (string, error)
if minorCount > 0 {
buf.WriteString(fmt.Sprintf("\n\t\t⚠️ %d 🔞", minorCount))
}
if chiefContactDetails != "" {
buf.WriteString("\n\t\t" + chiefContactDetails)
}

if activity.Libelle != "REGULATION" {
if pse1Count > 1 {
Expand All @@ -781,7 +830,7 @@ func (p *PegassClient) lintActivity(activity redcross.Activity) (string, error)
return buf.String(), nil
}

func (p *PegassClient) GetActivityOnDay(day string) (string, error) {
func (p *PegassClient) GetActivityOnDay(day string, kind ActivityKind) (string, error) {
err := p.init()
if err != nil {
return "", err
Expand Down Expand Up @@ -827,9 +876,15 @@ func (p *PegassClient) GetActivityOnDay(day string) (string, error) {
continue
}

if act.TypeActivite.ID != 10115 && act.TypeActivite.ID != 10114 {
// Only keep REGULATION and SAMU activities
continue
if kind == SAMU {
if act.TypeActivite.ID != ACTIVITY_RESEAU_15_ID && act.TypeActivite.ID != ACTIVITY_REGULATION_ID {
// Only keep REGULATION and SAMU activities
continue
}
} else if kind == BSPP {
if act.TypeActivite.ID != ACTIVITY_RESEAU_18_ID {
continue
}
}

var isCRFActivity = true
Expand Down
38 changes: 20 additions & 18 deletions redcross/utilisateur-payloads.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,24 +31,26 @@ type Structure struct {
}

type Utilisateur struct {
ID string `json:"id"`
Structure Structure `json:"structure"`
Nom string `json:"nom"`
Prenom string `json:"prenom"`
Coordonnees []struct {
ID string `json:"id"`
UtilisateurID string `json:"utilisateurId"`
MoyenComID string `json:"moyenComId"`
Numero int `json:"numero"`
Libelle string `json:"libelle"`
Flag string `json:"flag"`
Visible bool `json:"visible"`
CanDelete bool `json:"canDelete"`
CanUpdate bool `json:"canUpdate"`
} `json:"coordonnees"`
Actif bool `json:"actif"`
Mineur bool `json:"mineur"`
Commentaire string `json:"commentaire,omitempty"`
ID string `json:"id"`
Structure Structure `json:"structure"`
Nom string `json:"nom"`
Prenom string `json:"prenom"`
Coordonnees []Coordonnees `json:"coordonnees"`
Actif bool `json:"actif"`
Mineur bool `json:"mineur"`
Commentaire string `json:"commentaire,omitempty"`
}

type Coordonnees struct {
ID string `json:"id"`
UtilisateurID string `json:"utilisateurId"`
MoyenComID string `json:"moyenComId"`
Numero int `json:"numero"`
Libelle string `json:"libelle"`
Flag string `json:"flag"`
Visible bool `json:"visible"`
CanDelete bool `json:"canDelete"`
CanUpdate bool `json:"canUpdate"`
}

type RegulationStats struct {
Expand Down
Loading

0 comments on commit f60bf7e

Please sign in to comment.