INITIAL COMMIT
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 5s
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 5s
This commit is contained in:
parent
41b4cee5c4
commit
ceb402a65d
19
.gitea/workflows/demo.yaml
Normal file
19
.gitea/workflows/demo.yaml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
name: Gitea Actions Demo
|
||||||
|
run-name: ${{ gitea.actor }} is testing out Gitea Actions 🚀
|
||||||
|
on: [push]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
Explore-Gitea-Actions:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- run: echo "🎉 The job was automatically and automagically triggered by a ${{ gitea.event_name }} event."
|
||||||
|
- run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by Gitea!"
|
||||||
|
- run: echo "🔎 The name of your branch is ${{ gitea.ref }} and your repository is ${{ gitea.repository }}."
|
||||||
|
- name: Check out repository code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- run: echo "💡 The ${{ gitea.repository }} repository has been cloned to the runner."
|
||||||
|
- run: echo "🖥️ The workflow is now ready to test your code on the runner."
|
||||||
|
- name: List files in the repository
|
||||||
|
run: |
|
||||||
|
ls ${{ gitea.workspace }}
|
||||||
|
- run: echo "🍏 This job's status is ${{ job.status }}."
|
||||||
15
Dockerfile
15
Dockerfile
@ -1,14 +1,17 @@
|
|||||||
FROM golang:latest as builder
|
#FROM golang:latest as builder
|
||||||
WORKDIR /app
|
#WORKDIR /app
|
||||||
COPY . .
|
#COPY . .
|
||||||
RUN go mod download && go mod tidy
|
#RUN go mod download && go mod tidy
|
||||||
|
|
||||||
RUN go build -o ./bin/us-dop-bot ./cmd/bot
|
#RUN go build -o ./bin/us-dop-bot ./cmd/bot
|
||||||
|
|
||||||
FROM debian:unstable-slim
|
FROM debian:unstable-slim
|
||||||
|
ARG BINAME=us-dop-bot-linux-arm64-0.0.0_1
|
||||||
RUN apt-get update
|
RUN apt-get update
|
||||||
RUN apt-get install -y ca-certificates
|
RUN apt-get install -y ca-certificates
|
||||||
|
|
||||||
COPY --from=builder /app/bin/us-dop-bot /app/us-dop-bot
|
COPY ./bin/${BINAME} /app/us-dop-bot
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
RUN echo "bin name ${BINAME}"
|
||||||
|
# RUN mv /app/${BINAME} /app/us-dop-bot
|
||||||
CMD ["/app/us-dop-bot"]
|
CMD ["/app/us-dop-bot"]
|
||||||
|
|||||||
14
Dockerfile.old
Normal file
14
Dockerfile.old
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
FROM golang:latest as builder
|
||||||
|
WORKDIR /app
|
||||||
|
COPY . .
|
||||||
|
RUN go mod download && go mod tidy
|
||||||
|
|
||||||
|
RUN go build -o ./bin/us-dop-bot ./cmd/bot
|
||||||
|
|
||||||
|
FROM debian:unstable-slim
|
||||||
|
RUN apt-get update
|
||||||
|
RUN apt-get install -y ca-certificates
|
||||||
|
|
||||||
|
COPY --from=builder /app/bin/us-dop-bot /app/us-dop-bot
|
||||||
|
WORKDIR /app
|
||||||
|
CMD ["/app/us-dop-bot"]
|
||||||
10
Makefile
10
Makefile
@ -20,11 +20,15 @@ K8SRSNAME=$(shell kubectl get rs --no-headers -o custom-columns=":metadata.name"
|
|||||||
.phony: all clean build test clean-image build-image build-image-debug run-image run-image-debug run-local
|
.phony: all clean build test clean-image build-image build-image-debug run-image run-image-debug run-local
|
||||||
|
|
||||||
|
|
||||||
build-image:
|
build-image: build
|
||||||
# here we made the images and push to registry with buildx
|
# here we made the images and push to registry with buildx
|
||||||
@$(CONTAINERS) buildx build --platform linux/arm64 --push -t $(REGADDR)/us-dop-bot:latest .
|
@$(CONTAINERS) buildx build --build-arg="BINAME=$(BINAMEARM)" --platform linux/arm64 --push -t $(REGADDR)/us-dop-bot:latest .
|
||||||
|
|
||||||
# Here we upload it to local
|
# Here we upload it to local
|
||||||
|
|
||||||
|
build-test-image:
|
||||||
|
@$(CONTAINERS) buildx build --platform linux/arm64 --push -t $(REGADDR)/us-dop-bot:latest .
|
||||||
|
|
||||||
run-image: build-image
|
run-image: build-image
|
||||||
@$(CONTAINERS) compose -f docker-compose.yaml up
|
@$(CONTAINERS) compose -f docker-compose.yaml up
|
||||||
|
|
||||||
@ -37,7 +41,7 @@ run-image-debug: build-image-debug
|
|||||||
run-local:clean build
|
run-local:clean build
|
||||||
@bin/$(BINAME)
|
@bin/$(BINAME)
|
||||||
|
|
||||||
build:
|
build: clean
|
||||||
#@mkdir dolardb
|
#@mkdir dolardb
|
||||||
@env GOOS=$(OS) GOARCH=$(arch) go build -o ./bin/$(BINAME) ./cmd/bot/.
|
@env GOOS=$(OS) GOARCH=$(arch) go build -o ./bin/$(BINAME) ./cmd/bot/.
|
||||||
@env GOOS=$(OS) GOARCH=arm64 go build -o ./bin/$(BINAMEARM) ./cmd/bot/.
|
@env GOOS=$(OS) GOARCH=arm64 go build -o ./bin/$(BINAMEARM) ./cmd/bot/.
|
||||||
|
|||||||
@ -1,62 +0,0 @@
|
|||||||
package broadcast
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"log/slog"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
tb "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
|
||||||
"github.com/maximotejeda/us_dop_bot/db"
|
|
||||||
message "github.com/maximotejeda/us_dop_bot/edb"
|
|
||||||
"github.com/maximotejeda/us_dop_bot/helpers"
|
|
||||||
)
|
|
||||||
|
|
||||||
func SendList(ctx context.Context, userDB *db.DB, log *slog.Logger, data []byte) []tb.MessageConfig {
|
|
||||||
user := db.NewUser(userDB, log)
|
|
||||||
// convert data to map
|
|
||||||
m := message.Message{}
|
|
||||||
listMsg := []tb.MessageConfig{}
|
|
||||||
err := json.Unmarshal(data, &m)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("unmarshaling data", "error", err)
|
|
||||||
}
|
|
||||||
if m.Message == "change registered" {
|
|
||||||
|
|
||||||
userList, err := user.GetAll(m.Data.After.Name)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("querying DB data", "error", err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
cancelBTN := map[string]string{}
|
|
||||||
cancelBTN["Eliminar ❌"] = "cancelar=true"
|
|
||||||
keyboard := helpers.CreateKeyboard(cancelBTN)
|
|
||||||
log.Info("printing change", "user list", userList, "name", m.Data.After.Name)
|
|
||||||
compraCHG := comparer(m.Data.Before.Compra, m.Data.After.Compra)
|
|
||||||
ventaCHG := comparer(m.Data.Before.Venta, m.Data.After.Venta)
|
|
||||||
text := fmt.Sprintf("Cambio Registrado:\n\nInstitucion: %s\n\t Compra: %.2f %s %.2f\n\t Venta: %.2f %s %.2f\n\n\t %s", m.Data.After.Name, m.Data.Before.Compra, compraCHG, m.Data.After.Compra, m.Data.Before.Venta, ventaCHG, m.Data.After.Venta, m.Data.After.Parsed.Format(time.DateTime))
|
|
||||||
|
|
||||||
for _, user := range userList {
|
|
||||||
if user.TguID != 0 {
|
|
||||||
msg := tb.NewMessage(int64(user.TguID), text)
|
|
||||||
msg.ReplyMarkup = keyboard
|
|
||||||
listMsg = append(listMsg, msg)
|
|
||||||
} else {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return listMsg
|
|
||||||
}
|
|
||||||
|
|
||||||
func comparer(before, after float64) string {
|
|
||||||
if before > after {
|
|
||||||
return "⬇️"
|
|
||||||
} else if before < after {
|
|
||||||
return "⬆️"
|
|
||||||
} else {
|
|
||||||
return "🟰"
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
199
cmd/bot/main.go
199
cmd/bot/main.go
@ -2,148 +2,147 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"strings"
|
"runtime"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
|
||||||
|
|
||||||
tb "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||||
"github.com/maximotejeda/us_dop_bot/broadcast"
|
"github.com/maximotejeda/us_dop_bot/config"
|
||||||
commands "github.com/maximotejeda/us_dop_bot/command"
|
"github.com/maximotejeda/us_dop_bot/internal/adapters/dolar"
|
||||||
"github.com/maximotejeda/us_dop_bot/db"
|
"github.com/maximotejeda/us_dop_bot/internal/adapters/user"
|
||||||
edb "github.com/maximotejeda/us_dop_bot/edb"
|
"github.com/maximotejeda/us_dop_bot/internal/application/api"
|
||||||
"github.com/maximotejeda/us_dop_bot/query"
|
"github.com/maximotejeda/us_dop_bot/internal/application/broadcaster"
|
||||||
|
"github.com/maximotejeda/us_dop_bot/internal/ports"
|
||||||
|
"golang.org/x/sync/semaphore"
|
||||||
|
|
||||||
"github.com/nats-io/nats.go"
|
"github.com/nats-io/nats.go"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
maxWorkers = runtime.GOMAXPROCS(0)
|
||||||
|
sem = semaphore.NewWeighted(int64(maxWorkers) * 2)
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
dbUserUri := os.Getenv("DBURIUSER")
|
|
||||||
|
|
||||||
dbInstUri := os.Getenv("DBURINST")
|
|
||||||
token := os.Getenv("TOKEN")
|
|
||||||
natsURI := os.Getenv("NATSURI")
|
|
||||||
log := slog.New(slog.NewJSONHandler(os.Stderr, nil))
|
log := slog.New(slog.NewJSONHandler(os.Stderr, nil))
|
||||||
nc, _ := nats.Connect(natsURI)
|
log = log.With("location", "main")
|
||||||
|
nc, _ := nats.Connect(config.GetNatsURI())
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
userDB := db.Dial(ctx, db.DEFAULT_DRIVER, dbUserUri)
|
|
||||||
instDB := edb.Dial(dbInstUri, log)
|
|
||||||
|
|
||||||
bot, err := tb.NewBotAPI(token)
|
bot, err := tgbotapi.NewBotAPI(config.GetToken())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Error("token not found", "error", err)
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
bot.Debug = false
|
|
||||||
log.Info("Bot Authorized", "username", bot.Self.UserName)
|
botName := bot.Self.UserName
|
||||||
u := tb.NewUpdate(0)
|
|
||||||
|
bot.Debug = config.GetEnvironment() == "development"
|
||||||
|
log.Info("Bot Authorized", "username", botName)
|
||||||
|
log.Info("Initiated with a concurrency limit", "max concurrency", maxWorkers*2)
|
||||||
|
u := tgbotapi.NewUpdate(0)
|
||||||
u.Timeout = 60
|
u.Timeout = 60
|
||||||
|
|
||||||
// bot user update channel
|
// bot user update channel
|
||||||
updtChan := bot.GetUpdatesChan(u)
|
updtChan := bot.GetUpdatesChan(u)
|
||||||
// subs chann
|
// subs chann
|
||||||
ch := make(chan *nats.Msg, 64)
|
changeChan := make(chan *nats.Msg, 64)
|
||||||
defer close(ch)
|
broadcastChan := make(chan *nats.Msg, 64)
|
||||||
sub, err := nc.ChanSubscribe("dolar-crawler", ch)
|
defer close(changeChan)
|
||||||
|
defer close(broadcastChan)
|
||||||
|
sub, err := nc.ChanSubscribe("dolar-bot-change", changeChan)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("subscribing", "error", err.Error())
|
||||||
|
}
|
||||||
|
info, err := nc.ChanSubscribe("dolar-bot", broadcastChan)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("subscribing", "error", err.Error())
|
log.Error("subscribing", "error", err.Error())
|
||||||
}
|
}
|
||||||
defer sub.Drain()
|
defer sub.Drain()
|
||||||
|
defer info.Drain()
|
||||||
defer nc.Close()
|
defer nc.Close()
|
||||||
|
|
||||||
// exit channel
|
// exit channel
|
||||||
sign := make(chan os.Signal, 1)
|
sign := make(chan os.Signal, 1)
|
||||||
signal.Notify(sign, syscall.SIGINT, syscall.SIGTERM)
|
signal.Notify(sign, syscall.SIGINT, syscall.SIGTERM)
|
||||||
defer close(sign)
|
defer close(sign)
|
||||||
|
app := api.NewApi(bot)
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case update := <-updtChan:
|
case update := <-updtChan:
|
||||||
usr := db.NewUser(userDB, log)
|
if err = sem.Acquire(ctx, 1); err != nil {
|
||||||
_, err := usr.Get(update.SentFrom().ID)
|
bot.Send(tgbotapi.NewMessage(update.FromChat().ID, "error adquiring update"))
|
||||||
if err != nil {
|
continue
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
|
||||||
usr.Add(update.SentFrom().ID)
|
|
||||||
}
|
}
|
||||||
|
go func() {
|
||||||
|
defer sem.Release(1)
|
||||||
|
dol, user, dolarConn, userConn := CreateAdaptersGRPC()
|
||||||
|
app.Run(&update, dol, user)
|
||||||
|
dolarConn.Close()
|
||||||
|
userConn.Close()
|
||||||
|
}()
|
||||||
|
case message := <-changeChan:
|
||||||
|
log.Info("broadcasting Change")
|
||||||
|
dol, user, dolarConn, userConn := CreateAdaptersGRPC()
|
||||||
|
|
||||||
}
|
bcast := broadcaster.NewBroadCast(ctx, user, dol, message.Data)
|
||||||
|
userList := bcast.SendList()
|
||||||
|
|
||||||
if update.Message != nil {
|
for _, msg := range userList {
|
||||||
msg := tb.NewMessage(update.Message.Chat.ID, "")
|
|
||||||
if update.Message.Text != "" && !update.Message.IsCommand() {
|
|
||||||
log.Info("update", "username", update.Message.From.UserName, "message", update.Message.Text)
|
|
||||||
msg.Text = update.Message.Text
|
|
||||||
msg.ReplyToMessageID = update.Message.MessageID
|
|
||||||
bot.Send(msg)
|
|
||||||
} else if update.Message.IsCommand() {
|
|
||||||
go func(update tb.Update) {
|
|
||||||
msg = commands.CommandHandler(ctx, userDB, instDB, log, update)
|
|
||||||
|
|
||||||
if resp, err := bot.Request(tb.NewDeleteMessage(update.Message.From.ID, update.Message.MessageID)); err != nil || !resp.Ok {
|
|
||||||
log.Error(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
bot.Send(msg)
|
|
||||||
}(update)
|
|
||||||
}
|
|
||||||
} else if update.CallbackQuery != nil {
|
|
||||||
ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
|
|
||||||
go func(update tb.Update) {
|
|
||||||
msg := query.QueryHandler(ctx, userDB, instDB, log, update.CallbackQuery)
|
|
||||||
|
|
||||||
//del := tb.NewDeleteMessage(update.CallbackQuery.From.ID, update.CallbackQuery.Message.MessageID)
|
|
||||||
if resp, err := bot.Request(tb.NewDeleteMessage(update.CallbackQuery.From.ID, update.CallbackQuery.Message.MessageID)); err != nil || !resp.Ok {
|
|
||||||
log.Error(err.Error())
|
|
||||||
}
|
|
||||||
if msg != nil {
|
|
||||||
if _, err := bot.Send(msg); err != nil {
|
|
||||||
log.Error(err.Error())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
data := update.CallbackQuery.Data
|
|
||||||
|
|
||||||
dataList := strings.Split(data, "&")
|
|
||||||
|
|
||||||
dataMap := map[string]string{}
|
|
||||||
for _, val := range dataList {
|
|
||||||
subData := strings.Split(val, "=")
|
|
||||||
dataMap[subData[0]] = subData[1]
|
|
||||||
}
|
|
||||||
queryinf := tb.CallbackConfig{}
|
|
||||||
name := dataMap["name"]
|
|
||||||
if _, ok := dataMap["subs"]; ok {
|
|
||||||
queryinf.Text = fmt.Sprintf("Te haz suscrito a %s:\nRecibiras un mensaje cuando cambie el precio del dolar.", name)
|
|
||||||
} else if _, ok := dataMap["unsubs"]; ok {
|
|
||||||
queryinf.Text = fmt.Sprintf("Haz eliminado a %s de tus suscripciones\nNo recibiras notificaciones de %s", name, name)
|
|
||||||
} else if _, ok := dataMap["reset"]; ok {
|
|
||||||
queryinf.Text = "Haz eliminado todas tus suscripciones"
|
|
||||||
}
|
|
||||||
if queryinf.Text != "" {
|
|
||||||
queryinf.ShowAlert = true
|
|
||||||
queryinf.CallbackQueryID = update.CallbackQuery.ID
|
|
||||||
if resp, err := bot.Request(queryinf); err != nil || !resp.Ok {
|
|
||||||
log.Error(err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cancel()
|
|
||||||
}(update)
|
|
||||||
|
|
||||||
}
|
|
||||||
case message := <-ch:
|
|
||||||
// we are outside update so we will be querying db to
|
|
||||||
// get users interested in specific updates ex bpd, brd, apa
|
|
||||||
// userID inst=> comma separated string
|
|
||||||
msgList := broadcast.SendList(ctx, userDB, log, message.Data)
|
|
||||||
//log.Info(string(message.Data))
|
|
||||||
for _, msg := range msgList {
|
|
||||||
go bot.Send(msg)
|
go bot.Send(msg)
|
||||||
}
|
}
|
||||||
|
dolarConn.Close()
|
||||||
|
userConn.Close()
|
||||||
|
|
||||||
|
case message := <-broadcastChan:
|
||||||
|
dol, user, dolarConn, userConn := CreateAdaptersGRPC()
|
||||||
|
|
||||||
|
bcast := broadcaster.NewBroadCast(ctx, user, dol, message.Data)
|
||||||
|
msgs := bcast.SendAllUsers(ctx, log, message.Data, bot.Self.UserName)
|
||||||
|
log.Info("broadcast", "data", string(message.Data), "msg", msgs)
|
||||||
|
for _, msg := range msgs {
|
||||||
|
go bot.Send(msg)
|
||||||
|
}
|
||||||
|
dolarConn.Close()
|
||||||
|
userConn.Close()
|
||||||
case <-sign:
|
case <-sign:
|
||||||
log.Error("killing app due to syscall ")
|
log.Error("killing app due to syscall ")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CreateAdaptersGRPC() (ports.DolarService, ports.UserService, *grpc.ClientConn, *grpc.ClientConn) {
|
||||||
|
log := slog.Default()
|
||||||
|
// we are outside update so we will be querying db to
|
||||||
|
// get users interested in specific updates ex bpd, brd, apa
|
||||||
|
// userID inst=> comma separated string
|
||||||
|
var opts []grpc.DialOption
|
||||||
|
opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||||
|
dolarConn, err := grpc.Dial(config.GetDollarServiceURL(), opts...)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("creating gerpc conn", "error", err)
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
userConn, err := grpc.Dial(config.GetUserServiceURL(), opts...)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("creating gerpc conn", "error", err)
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
dol, err := dolar.NewAdapter(dolarConn)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("creating service adapter", "error", err)
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
user, err := user.NewAdapter(userConn)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("creating service adapter", "error", err)
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return dol, user, dolarConn, userConn
|
||||||
|
}
|
||||||
|
|||||||
@ -1,165 +0,0 @@
|
|||||||
package commands
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log/slog"
|
|
||||||
"slices"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
tgbot "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
|
||||||
"github.com/maximotejeda/us_dop_bot/db"
|
|
||||||
edb "github.com/maximotejeda/us_dop_bot/edb"
|
|
||||||
"github.com/maximotejeda/us_dop_bot/helpers"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CommandHandler
|
|
||||||
// Options for user to create queries on the bot
|
|
||||||
func CommandHandler(ctx context.Context, userDB *db.DB, instDB *edb.DB, log *slog.Logger, update tgbot.Update) tgbot.MessageConfig {
|
|
||||||
var (
|
|
||||||
err error
|
|
||||||
msg tgbot.MessageConfig
|
|
||||||
usr = db.NewUser(userDB, log)
|
|
||||||
)
|
|
||||||
command := update.Message.Command()
|
|
||||||
chatID := update.Message.Chat.ID
|
|
||||||
|
|
||||||
msg.ChatID = chatID
|
|
||||||
|
|
||||||
switch strings.ToLower(command) {
|
|
||||||
case "list", "lista":
|
|
||||||
msg, err = lista(update, userDB, instDB, log, "bancos")
|
|
||||||
if err != nil {
|
|
||||||
log.Error("command-status", "err", err)
|
|
||||||
}
|
|
||||||
case "listabancos":
|
|
||||||
msg, err = lista(update, userDB, instDB, log, "bancos")
|
|
||||||
if err != nil {
|
|
||||||
log.Error("command-status", "err", err)
|
|
||||||
}
|
|
||||||
case "listacajas":
|
|
||||||
msg, err = lista(update, userDB, instDB, log, "cajas")
|
|
||||||
if err != nil {
|
|
||||||
log.Error("command-status", "err", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
case "listagentes":
|
|
||||||
msg, err = lista(update, userDB, instDB, log, "agentes")
|
|
||||||
if err != nil {
|
|
||||||
log.Error("command-status", "err", err)
|
|
||||||
}
|
|
||||||
case "consulta":
|
|
||||||
_, err := usr.Get(update.Message.From.ID)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("command-status", "err", err)
|
|
||||||
}
|
|
||||||
btnSTR := map[string]string{}
|
|
||||||
for _, inst := range usr.Subs {
|
|
||||||
switch inst {
|
|
||||||
case "asociacion popular de ahorros y prestamos":
|
|
||||||
btnSTR[inst] = "consultar=true&name=apap"
|
|
||||||
case "asociacion cibao de ahorros y prestamos":
|
|
||||||
btnSTR[inst] = "consultar=true&name=acap"
|
|
||||||
case "asociacion la nacional de ahorros y prestamos":
|
|
||||||
btnSTR[inst] = "consultar=true&name=anap"
|
|
||||||
default:
|
|
||||||
btnSTR[inst] = "consultar=true&name=" + inst
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
btnSTR["cancelar ❌"] = "cancelar=true"
|
|
||||||
keyboard := helpers.CreateKeyboard(btnSTR)
|
|
||||||
msg.ReplyMarkup = keyboard
|
|
||||||
msg.Text = "Suscripciones actuales 💰\nPuedes hacer click para Ver cambios en los precios de las suscripcion\n o presionar cancelar"
|
|
||||||
|
|
||||||
case "status", "info":
|
|
||||||
_, err := usr.Get(update.Message.From.ID)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("command-status", "err", err)
|
|
||||||
}
|
|
||||||
btnSTR := map[string]string{}
|
|
||||||
for _, inst := range usr.Subs {
|
|
||||||
btnSTR[inst] = "unsubs=true&name=" + inst
|
|
||||||
}
|
|
||||||
btnSTR["cancelar ❌"] = "cancelar=true"
|
|
||||||
keyboard := helpers.CreateKeyboard(btnSTR)
|
|
||||||
msg.ReplyMarkup = keyboard
|
|
||||||
msg.Text = "Suscripciones actuales 💰\n Puedes hacer click en una para eliminar su subscripcion\nPresionar cancelar para omitir."
|
|
||||||
|
|
||||||
case "reset":
|
|
||||||
reset := map[string]string{"Reset": "reset=true", "cancelar ❌": "cancelar=true"}
|
|
||||||
keyboard := helpers.CreateKeyboard(reset)
|
|
||||||
msg.ReplyMarkup = keyboard
|
|
||||||
msg.Text = "Elimina todas las suscripciones del usuario."
|
|
||||||
case "help", "start", "ayuda", "h":
|
|
||||||
|
|
||||||
help := `
|
|
||||||
Asistente de cambio US <-> DOP
|
|
||||||
🇺🇸 ↔️ 🇩🇴
|
|
||||||
Tracker del precio del dolar para RD.
|
|
||||||
|
|
||||||
Funciona suscribiendo instituciones 💸.
|
|
||||||
- Tracker precio del dolar ❇️.
|
|
||||||
- Notificacion mensaje automatico 📈.
|
|
||||||
- Precio actual ⌚.
|
|
||||||
- Historico de precios 📅.
|
|
||||||
|
|
||||||
Comandos Conocidos por el bot:
|
|
||||||
|
|
||||||
/help: Muestra este mensaje ❓
|
|
||||||
/listabancos: Muestra bancos 🏦
|
|
||||||
/listacajas: Muestra asociaciones
|
|
||||||
/listagentes: Muestra agentes 📊
|
|
||||||
/consulta: Consulta entidad suscrita 🛎️
|
|
||||||
/reset: Borra tada suscripcion 🧹
|
|
||||||
/status: Estado del usuario 📋
|
|
||||||
`
|
|
||||||
|
|
||||||
msg.Text = help
|
|
||||||
default:
|
|
||||||
msg.Text = "Commando desconocido intenta con\n/help: to get bot info."
|
|
||||||
}
|
|
||||||
return msg
|
|
||||||
}
|
|
||||||
|
|
||||||
func lista(update tgbot.Update, user *db.DB, insts *edb.DB, log *slog.Logger, instSTR string) (msg tgbot.MessageConfig, err error) {
|
|
||||||
var instList []string
|
|
||||||
usr := db.NewUser(user, log)
|
|
||||||
msg = tgbot.MessageConfig{}
|
|
||||||
msg.ChatID = update.Message.Chat.ID
|
|
||||||
_, err = usr.Get(update.Message.From.ID)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("command-status", "err", err)
|
|
||||||
return msg, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO list all institutions
|
|
||||||
|
|
||||||
switch instSTR {
|
|
||||||
case "bancos":
|
|
||||||
instList, err = insts.GetBancos()
|
|
||||||
case "cajas":
|
|
||||||
instList, err = insts.GetCajas()
|
|
||||||
case "agentes":
|
|
||||||
instList, err = insts.GetAgentes()
|
|
||||||
default:
|
|
||||||
return msg, fmt.Errorf("tipio de institucion desconocida")
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
log.Error("[inst-list-query]", "error", err)
|
|
||||||
return msg, err
|
|
||||||
}
|
|
||||||
instMap := map[string]string{}
|
|
||||||
for _, i := range instList {
|
|
||||||
if slices.Contains[[]string](usr.Subs, i) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
instMap[i] = "subs=true&name=" + i
|
|
||||||
}
|
|
||||||
instMap["cancelar ❌"] = "cancelar=true"
|
|
||||||
keyboard := helpers.CreateKeyboard(instMap)
|
|
||||||
|
|
||||||
msg.Text = "Differentes cajas disponibles para track el precio del cambio\n\n\tasociacion popular\n\n\tasociacion cibao\n\n"
|
|
||||||
msg.ReplyMarkup = keyboard
|
|
||||||
return msg, nil
|
|
||||||
}
|
|
||||||
37
config/config.go
Normal file
37
config/config.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetToken() string {
|
||||||
|
return getEnvVariable("TOKEN")
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDBUSERURI() string {
|
||||||
|
return getEnvVariable("DBURIUSER")
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetNatsURI() string {
|
||||||
|
return getEnvVariable("NATSURI")
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDollarServiceURL() string {
|
||||||
|
return getEnvVariable("DOLLAR_SERVICE_URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUserServiceURL() string {
|
||||||
|
return getEnvVariable("TGBUSER_SERVICE_URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetEnvironment() string {
|
||||||
|
return getEnvVariable("ENV")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getEnvVariable(key string) string {
|
||||||
|
if os.Getenv(key) == "" {
|
||||||
|
log.Fatal("error getting key", key)
|
||||||
|
}
|
||||||
|
return os.Getenv(key)
|
||||||
|
}
|
||||||
BIN
crawler.db
Normal file
BIN
crawler.db
Normal file
Binary file not shown.
43
db/db.go
43
db/db.go
@ -1,43 +0,0 @@
|
|||||||
package db
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
_ "embed"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
_ "modernc.org/sqlite"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
DEFAULT_DRIVER = "sqlite"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
//go:embed schema.sql
|
|
||||||
schema string
|
|
||||||
)
|
|
||||||
|
|
||||||
type DB struct {
|
|
||||||
*sql.DB
|
|
||||||
}
|
|
||||||
|
|
||||||
func Dial(ctx context.Context, driver, uri string) *DB {
|
|
||||||
db, err := sql.Open(driver, uri)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Failed to connect to database:", err)
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
err = db.PingContext(ctx)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Pinging with context: %s", err)
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = db.ExecContext(ctx, schema)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &DB{db}
|
|
||||||
}
|
|
||||||
326
db/models.go
326
db/models.go
@ -1,326 +0,0 @@
|
|||||||
package db
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log/slog"
|
|
||||||
"slices"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type User struct {
|
|
||||||
db *DB
|
|
||||||
log *slog.Logger
|
|
||||||
ID int
|
|
||||||
TguID int
|
|
||||||
Subs []string
|
|
||||||
Created time.Time
|
|
||||||
Edited time.Time
|
|
||||||
Deleted time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
type Inst struct {
|
|
||||||
db *DB
|
|
||||||
log *slog.Logger
|
|
||||||
List []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewUser(db *DB, log *slog.Logger) User {
|
|
||||||
return User{
|
|
||||||
log: log,
|
|
||||||
db: db,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAll
|
|
||||||
// Get all users in db
|
|
||||||
func (u *User) GetAll(name string) (users []User, err error) {
|
|
||||||
stmt, err := u.db.Prepare("SELECT users.id, users.tgu_id, users.subs FROM users WHERE users.subs LIKE ?")
|
|
||||||
if err != nil {
|
|
||||||
u.log.Error("[user-GetAll]", "error", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
rows, err := stmt.Query("%" + name + "%")
|
|
||||||
if err != nil {
|
|
||||||
u.log.Error("[user-GetAll-stmt]", "error", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer rows.Close()
|
|
||||||
users = []User{}
|
|
||||||
for rows.Next() {
|
|
||||||
user := User{}
|
|
||||||
subs := ""
|
|
||||||
if err = rows.Scan(&user.ID, &user.TguID, &subs); err != nil {
|
|
||||||
u.log.Error("[user-GetAll-scanning]", "error", err)
|
|
||||||
return users, err
|
|
||||||
}
|
|
||||||
if subs == "" {
|
|
||||||
u.log.Info("[user-GetAll-scanning] returning subs empty")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
user.Subs = strings.Split(subs, ",")
|
|
||||||
u.log.Info("user", "no", user)
|
|
||||||
users = append(users, user)
|
|
||||||
}
|
|
||||||
if err := rows.Err(); err != nil {
|
|
||||||
u.log.Error("[user-GetAll-returning]", "error", err)
|
|
||||||
return users, err
|
|
||||||
}
|
|
||||||
return users, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get
|
|
||||||
// Get an specific user
|
|
||||||
func (u *User) Get(telegramID int64) (user User, err error) {
|
|
||||||
stmt, err := u.db.Prepare("SELECT users.id, users.tgu_id, users.subs FROM users WHERE tgu_id=?")
|
|
||||||
if err != nil {
|
|
||||||
u.log.Error("[user-Get]", "error", err)
|
|
||||||
return user, err
|
|
||||||
}
|
|
||||||
subs := ""
|
|
||||||
err = stmt.QueryRow(telegramID).Scan(&user.ID, &user.TguID, &subs)
|
|
||||||
if err != nil {
|
|
||||||
u.log.Error("[user-Get-stmt]", "error", err)
|
|
||||||
return user, err
|
|
||||||
}
|
|
||||||
if subs != "" {
|
|
||||||
user.Subs = strings.Split(subs, ",")
|
|
||||||
} else {
|
|
||||||
user.Subs = []string{}
|
|
||||||
}
|
|
||||||
u.ID, u.TguID, u.Subs = user.ID, user.TguID, user.Subs
|
|
||||||
return user, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add
|
|
||||||
// Add user to database
|
|
||||||
func (u *User) Add(telegramID int64) (bool, error) {
|
|
||||||
stmt, err := u.db.Prepare("INSERT INTO users ('tgu_id', 'subs', 'created', 'edited') VALUES(?,?,datetime(),datetime())")
|
|
||||||
if err != nil {
|
|
||||||
u.log.Error("[user-Add-stmt]", "error", err)
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
_, err = stmt.Exec(telegramID, "")
|
|
||||||
if err != nil {
|
|
||||||
u.log.Error("[user-Add-exec]", "error", err)
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
return true, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Edit
|
|
||||||
// edit user info in database
|
|
||||||
func (u *User) Subscribe(name string) (err error) {
|
|
||||||
if u.TguID == 0 || u.ID == 0 {
|
|
||||||
err = fmt.Errorf("user needs to have a telegram id or reference on db")
|
|
||||||
u.log.Error("[user-Subscribe-check]", "error", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
idx := slices.Index[[]string](u.Subs, name)
|
|
||||||
if idx >= 0 {
|
|
||||||
return fmt.Errorf("user alredy subscribed to inst")
|
|
||||||
}
|
|
||||||
u.Subs = append(u.Subs, name)
|
|
||||||
stmt, err := u.db.Prepare("UPDATE users SET subs=? WHERE tgu_id=?")
|
|
||||||
if err != nil {
|
|
||||||
u.log.Error("[user-Subscribe-stmt]", "error", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
strSubs := strings.Join(u.Subs, ",")
|
|
||||||
_, err = stmt.Exec(strSubs, u.TguID)
|
|
||||||
if err != nil {
|
|
||||||
u.log.Error("[user-Subscribe-exec]", "error", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete
|
|
||||||
// Delete user from database
|
|
||||||
func (u *User) Unsubscribe(name string) (err error) {
|
|
||||||
if u.TguID == 0 || u.ID == 0 {
|
|
||||||
err = fmt.Errorf("user needs to have a telegram id or reference on db")
|
|
||||||
u.log.Error("[user-UnSubscribe-check]", "error", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(u.Subs) <= 0 {
|
|
||||||
err = fmt.Errorf("user needs to have a subscription to unsubcribe")
|
|
||||||
u.log.Error("[user-UnSubscribe-check]", "error", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
idx := slices.Index[[]string](u.Subs, name)
|
|
||||||
if idx >= 0 {
|
|
||||||
u.Subs = slices.Delete[[]string](u.Subs, idx, idx+1)
|
|
||||||
} else {
|
|
||||||
err = fmt.Errorf("user is not subscribed to %s", name)
|
|
||||||
u.log.Error("[user-UnSubscribe-check]", "error", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
stmt, err := u.db.Prepare("UPDATE users SET subs=? WHERE tgu_id=?")
|
|
||||||
if err != nil {
|
|
||||||
u.log.Error("[user-UnSubscribe-stmt]", "error", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
strSubs := strings.Join(u.Subs, ",")
|
|
||||||
_, err = stmt.Exec(strSubs, u.TguID)
|
|
||||||
if err != nil {
|
|
||||||
u.log.Error("[user-UnSubscribe-exec]", "error", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset
|
|
||||||
// Clean user subscriptions on system
|
|
||||||
func (u *User) Reset() (err error) {
|
|
||||||
if u.TguID == 0 || u.ID == 0 {
|
|
||||||
err = fmt.Errorf("user needs to have a telegram id or reference on db")
|
|
||||||
u.log.Error("[user-Subscribe-check]", "error", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
stmt, err := u.db.Prepare("UPDATE users SET subs=? WHERE tgu_id=?")
|
|
||||||
if err != nil {
|
|
||||||
u.log.Error("[user-Subscribe-stmt]", "error", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = stmt.Exec("", u.TguID)
|
|
||||||
if err != nil {
|
|
||||||
u.log.Error("[user-Subscribe-exec]", "error", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewInst(dbx *DB, log *slog.Logger) Inst {
|
|
||||||
return Inst{
|
|
||||||
log: log,
|
|
||||||
db: dbx,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Inst) GetAll() ([]string, error) {
|
|
||||||
stmt, err := i.db.Prepare("SELECT DISTINCT dolars.name FROM dolars WHERE name LIKE '%ban%' OR name LIKE '%scoti%' OR name LIKE '%asociacion%'")
|
|
||||||
if err != nil {
|
|
||||||
i.log.Error("[inst-GetAll]", "error", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rows, err := stmt.Query()
|
|
||||||
if err != nil {
|
|
||||||
i.log.Error("[inst-GetAll-stmt]", "error", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
insts := []string{}
|
|
||||||
for rows.Next() {
|
|
||||||
inst := ""
|
|
||||||
|
|
||||||
if err = rows.Scan(&inst); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if inst == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
insts = append(insts, inst)
|
|
||||||
}
|
|
||||||
if err := rows.Err(); err != nil {
|
|
||||||
return insts, err
|
|
||||||
}
|
|
||||||
return insts, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
func (i *Inst) GetBancos() ([]string, error) {
|
|
||||||
stmt, err := i.db.Prepare("SELECT DISTINCT dolars.name FROM dolars WHERE name LIKE '%ban%' OR name LIKE '%scoti%'")
|
|
||||||
if err != nil {
|
|
||||||
i.log.Error("[inst-GetAll]", "error", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rows, err := stmt.Query()
|
|
||||||
if err != nil {
|
|
||||||
i.log.Error("[inst-GetAll-stmt]", "error", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
insts := []string{}
|
|
||||||
for rows.Next() {
|
|
||||||
inst := ""
|
|
||||||
|
|
||||||
if err = rows.Scan(&inst); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if inst == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
insts = append(insts, inst)
|
|
||||||
}
|
|
||||||
if err := rows.Err(); err != nil {
|
|
||||||
return insts, err
|
|
||||||
}
|
|
||||||
return insts, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
func (i *Inst) GetCajas() ([]string, error) {
|
|
||||||
stmt, err := i.db.Prepare("SELECT DISTINCT dolars.name FROM dolars WHERE name LIKE '%asociacion%'")
|
|
||||||
if err != nil {
|
|
||||||
i.log.Error("[inst-GetAll]", "error", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rows, err := stmt.Query()
|
|
||||||
if err != nil {
|
|
||||||
i.log.Error("[inst-GetAll-stmt]", "error", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
insts := []string{}
|
|
||||||
for rows.Next() {
|
|
||||||
inst := ""
|
|
||||||
|
|
||||||
if err = rows.Scan(&inst); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if inst == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
insts = append(insts, inst)
|
|
||||||
}
|
|
||||||
if err := rows.Err(); err != nil {
|
|
||||||
return insts, err
|
|
||||||
}
|
|
||||||
return insts, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Inst) GetAgentes() ([]string, error) {
|
|
||||||
stmt, err := i.db.Prepare("SELECT DISTINCT dolars.name FROM dolars WHERE name NOT LIKE '%ban%' AND name NOT LIKE '%scoti%' AND name NOT LIKE '%asociacion%'")
|
|
||||||
if err != nil {
|
|
||||||
i.log.Error("[inst-GetAll]", "error", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rows, err := stmt.Query()
|
|
||||||
if err != nil {
|
|
||||||
i.log.Error("[inst-GetAll-stmt]", "error", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
insts := []string{}
|
|
||||||
for rows.Next() {
|
|
||||||
inst := ""
|
|
||||||
|
|
||||||
if err = rows.Scan(&inst); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if inst == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
insts = append(insts, inst)
|
|
||||||
}
|
|
||||||
if err := rows.Err(); err != nil {
|
|
||||||
return insts, err
|
|
||||||
}
|
|
||||||
return insts, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
PRAGMA foreign_keys = ON;
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS users (
|
|
||||||
id INTEGER PRIMARY KEY NOT NULL,
|
|
||||||
tgu_id INTEGER NOT NULL UNIQUE,
|
|
||||||
subs TEXT NOT NULL,
|
|
||||||
created TEXT NOT NULL,
|
|
||||||
edited TEXT NOT NULL,
|
|
||||||
deleted TEXT
|
|
||||||
);
|
|
||||||
302
edb/db.go
302
edb/db.go
@ -1,302 +0,0 @@
|
|||||||
package db
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
_ "embed"
|
|
||||||
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"log/slog"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/maximotejeda/us_dop_bot/models"
|
|
||||||
_ "modernc.org/sqlite"
|
|
||||||
)
|
|
||||||
|
|
||||||
type DB struct {
|
|
||||||
*sql.DB
|
|
||||||
log *slog.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
type change struct {
|
|
||||||
Before models.Institucion `json:"before"`
|
|
||||||
After models.Institucion `json:"after"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Message struct {
|
|
||||||
Message string `json:"message"`
|
|
||||||
Data change `json:"data"`
|
|
||||||
Error error `json:"error"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dial
|
|
||||||
func Dial(path string, log *slog.Logger) *DB {
|
|
||||||
db, err := sql.Open("sqlite", path)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("opening database: %s", err.Error())
|
|
||||||
panic("opening database")
|
|
||||||
}
|
|
||||||
if err := db.Ping(); err != nil {
|
|
||||||
fmt.Printf("pinging database: %s", err.Error())
|
|
||||||
panic("pinging database")
|
|
||||||
}
|
|
||||||
return &DB{db, log}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inspect
|
|
||||||
// Handle behavior of the changes
|
|
||||||
// Will report errors to a nats consumer
|
|
||||||
func (db *DB) Inspect(enter models.Institucion) error {
|
|
||||||
if db == nil {
|
|
||||||
return fmt.Errorf("nil or empty database")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get last row added
|
|
||||||
inst, err := db.GetLatest(enter.Parser, enter.Name)
|
|
||||||
// if no rows are found because of first enter a name - parser ?
|
|
||||||
if errors.Is(sql.ErrNoRows, err) {
|
|
||||||
db.log.Info("adding new item to table: ", "parse", enter.Parser, "name", enter.Name)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
db.log.Error("marshaling struct", "error", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return db.AddNew(enter)
|
|
||||||
}
|
|
||||||
// check prices compra venta
|
|
||||||
if inst == nil {
|
|
||||||
db.log.Error("row is nil", "name", enter.Name, "parser", enter.Parser)
|
|
||||||
return fmt.Errorf("row is nil, not entering row")
|
|
||||||
}
|
|
||||||
if enter.Compra == inst.Compra && enter.Venta == inst.Venta {
|
|
||||||
return nil
|
|
||||||
} else {
|
|
||||||
// if one of them changes create a new row
|
|
||||||
db.log.Info("change registered, adding item", "parse", enter.Parser, "name", enter.Name, "compra enter", enter.Compra, "compra db", inst.Compra, "venta enter", enter.Venta, "venta db", inst.Venta)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
db.log.Error("marshaling struct", "error", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return db.AddNew(enter)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLatest
|
|
||||||
// returns the latest row in a specific parser and name
|
|
||||||
// we are using DateTime in DB and date.Datetime in go
|
|
||||||
func (db *DB) GetLatest(parser string, name string) (inst *models.Institucion, err error) {
|
|
||||||
var parsed string
|
|
||||||
inst = &models.Institucion{}
|
|
||||||
stmt, err := db.Prepare("SELECT name, parser, compra, venta, parsed FROM dolars WHERE parser = ? AND name = ? ORDER BY parsed DESC LIMIT 1;")
|
|
||||||
if err != nil {
|
|
||||||
db.log.Error("preparing", "error", err.Error())
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer stmt.Close()
|
|
||||||
|
|
||||||
if err := stmt.QueryRow(parser, name).Scan(&inst.Name, &inst.Parser, &inst.Compra, &inst.Venta, &parsed); err != nil {
|
|
||||||
db.log.Error("getting latest", "error", err.Error(), "parser", parser, "name", name)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
inst.Parsed, err = time.Parse(time.DateTime, parsed)
|
|
||||||
if err != nil {
|
|
||||||
//db.log.Error("parsed", "error", err.Error())
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return inst, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddNew
|
|
||||||
// Add a new row in the dolar table
|
|
||||||
// Will send to nats changes on prices
|
|
||||||
func (db *DB) AddNew(row models.Institucion) error {
|
|
||||||
stmt, err := db.Prepare("INSERT INTO dolars (name, compra, venta, parser, parsed) VALUES(?,?,?,?,?);")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer stmt.Close()
|
|
||||||
parsed := row.Parsed.Format(time.DateTime)
|
|
||||||
_, err = stmt.Exec(&row.Name, &row.Compra, &row.Venta, &row.Parser, &parsed)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *DB) GetAll() ([]string, error) {
|
|
||||||
stmt, err := db.Prepare("SELECT DISTINCT dolars.name FROM dolars WHERE name LIKE '%ban%' OR name LIKE '%scoti%' OR name LIKE '%asociacion%'")
|
|
||||||
if err != nil {
|
|
||||||
db.log.Error("[db-GetAll]", "error", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rows, err := stmt.Query()
|
|
||||||
if err != nil {
|
|
||||||
db.log.Error("[db-GetAll-stmt]", "error", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
insts := []string{}
|
|
||||||
for rows.Next() {
|
|
||||||
inst := ""
|
|
||||||
|
|
||||||
if err = rows.Scan(&inst); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if inst == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
insts = append(insts, inst)
|
|
||||||
}
|
|
||||||
if err := rows.Err(); err != nil {
|
|
||||||
return insts, err
|
|
||||||
}
|
|
||||||
return insts, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
func (db *DB) GetBancos() ([]string, error) {
|
|
||||||
stmt, err := db.Prepare("SELECT DISTINCT dolars.name FROM dolars WHERE name LIKE '%ban%' OR name LIKE '%scoti%'")
|
|
||||||
if err != nil {
|
|
||||||
db.log.Error("[inst-GetAll]", "error", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rows, err := stmt.Query()
|
|
||||||
if err != nil {
|
|
||||||
db.log.Error("[inst-GetAll-stmt]", "error", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
insts := []string{}
|
|
||||||
for rows.Next() {
|
|
||||||
inst := ""
|
|
||||||
|
|
||||||
if err = rows.Scan(&inst); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if inst == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
insts = append(insts, inst)
|
|
||||||
}
|
|
||||||
if err := rows.Err(); err != nil {
|
|
||||||
return insts, err
|
|
||||||
}
|
|
||||||
return insts, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
func (db *DB) GetCajas() ([]string, error) {
|
|
||||||
stmt, err := db.Prepare("SELECT DISTINCT dolars.name FROM dolars WHERE name LIKE '%asociacion%'")
|
|
||||||
if err != nil {
|
|
||||||
db.log.Error("[inst-GetAll]", "error", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rows, err := stmt.Query()
|
|
||||||
if err != nil {
|
|
||||||
db.log.Error("[inst-GetAll-stmt]", "error", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
insts := []string{}
|
|
||||||
for rows.Next() {
|
|
||||||
inst := ""
|
|
||||||
|
|
||||||
if err = rows.Scan(&inst); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if inst == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
insts = append(insts, inst)
|
|
||||||
}
|
|
||||||
if err := rows.Err(); err != nil {
|
|
||||||
return insts, err
|
|
||||||
}
|
|
||||||
return insts, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *DB) GetAgentes() ([]string, error) {
|
|
||||||
stmt, err := db.Prepare("SELECT DISTINCT dolars.name FROM dolars WHERE name NOT LIKE '%ban%' AND name NOT LIKE '%scoti%' AND name NOT LIKE '%asociacion%'")
|
|
||||||
if err != nil {
|
|
||||||
db.log.Error("[inst-GetAll]", "error", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rows, err := stmt.Query()
|
|
||||||
if err != nil {
|
|
||||||
db.log.Error("[inst-GetAll-stmt]", "error", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
insts := []string{}
|
|
||||||
for rows.Next() {
|
|
||||||
inst := ""
|
|
||||||
|
|
||||||
if err = rows.Scan(&inst); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if inst == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
insts = append(insts, inst)
|
|
||||||
}
|
|
||||||
if err := rows.Err(); err != nil {
|
|
||||||
return insts, err
|
|
||||||
}
|
|
||||||
return insts, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *DB) GetLastPrice(name string) (inst *models.Institucion, err error) {
|
|
||||||
var parsed string
|
|
||||||
inst = &models.Institucion{}
|
|
||||||
stmt, err := db.Prepare("SELECT name, parser, compra, venta, parsed FROM dolars WHERE name = ? ORDER BY parsed DESC LIMIT 1;")
|
|
||||||
if err != nil {
|
|
||||||
db.log.Error("preparing", "error", err.Error())
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer stmt.Close()
|
|
||||||
|
|
||||||
if err := stmt.QueryRow(name).Scan(&inst.Name, &inst.Parser, &inst.Compra, &inst.Venta, &parsed); err != nil {
|
|
||||||
db.log.Error("getting last price", "error", err.Error(), "name", name)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
inst.Parsed, err = time.Parse(time.DateTime, parsed)
|
|
||||||
if err != nil {
|
|
||||||
//db.log.Error("parsed", "error", err.Error())
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return inst, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
func (db *DB) GetChangeSince(name string, duration time.Duration) (insts []*models.Institucion, err error) {
|
|
||||||
date := time.Now().Add(-duration).Format(time.DateTime)
|
|
||||||
stmt, err := db.Prepare("SELECT name, parser, compra, venta, parsed FROM dolars WHERE name = ? AND parsed > ? ORDER BY parsed DESC;")
|
|
||||||
if err != nil {
|
|
||||||
db.log.Error("[GetChangeSince] preparing", "error", err.Error())
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer stmt.Close()
|
|
||||||
rows, err := stmt.Query(name, date)
|
|
||||||
if err != nil {
|
|
||||||
db.log.Error("[GetChangeSince] preparing", "error", err.Error())
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
for rows.Next() {
|
|
||||||
inst := models.Institucion{}
|
|
||||||
parsed := ""
|
|
||||||
if err := rows.Scan(&inst.Name, &inst.Parser, &inst.Compra, &inst.Venta, &parsed); err != nil {
|
|
||||||
db.log.Error("[GetChangeSince] scanning", "error", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
inst.Parsed, err = time.Parse(time.DateTime, parsed)
|
|
||||||
if err != nil {
|
|
||||||
//db.log.Error("parsed", "error", err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
insts = append(insts, &inst)
|
|
||||||
}
|
|
||||||
return insts, nil
|
|
||||||
}
|
|
||||||
21
go.mod
21
go.mod
@ -2,4 +2,23 @@ module github.com/maximotejeda/us_dop_bot
|
|||||||
|
|
||||||
go 1.22.0
|
go 1.22.0
|
||||||
|
|
||||||
require github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
|
require (
|
||||||
|
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
|
||||||
|
github.com/maximotejeda/msvc-proto/golang/dolar v0.0.0-8
|
||||||
|
github.com/maximotejeda/msvc-proto/golang/tgbuser v0.0.0-11
|
||||||
|
github.com/nats-io/nats.go v1.34.1
|
||||||
|
golang.org/x/sync v0.6.0
|
||||||
|
golang.org/x/text v0.14.0
|
||||||
|
google.golang.org/grpc v1.63.2
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/klauspost/compress v1.17.2 // indirect
|
||||||
|
github.com/nats-io/nkeys v0.4.7 // indirect
|
||||||
|
github.com/nats-io/nuid v1.0.1 // indirect
|
||||||
|
golang.org/x/crypto v0.22.0 // indirect
|
||||||
|
golang.org/x/net v0.24.0 // indirect
|
||||||
|
golang.org/x/sys v0.19.0 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be // indirect
|
||||||
|
google.golang.org/protobuf v1.33.0 // indirect
|
||||||
|
)
|
||||||
|
|||||||
30
go.sum
30
go.sum
@ -1,2 +1,32 @@
|
|||||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
|
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
|
||||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
|
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
|
||||||
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4=
|
||||||
|
github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||||
|
github.com/maximotejeda/msvc-proto/golang/dolar v0.0.0-8 h1:ldphxrQiAhmctWBMCaNShDphZfHmOeKuoSWwCxV62Ho=
|
||||||
|
github.com/maximotejeda/msvc-proto/golang/dolar v0.0.0-8/go.mod h1:bAs0mlC1Vyn/BkHONL2Ik8ox9px9s9bhbJWgUQFMMWo=
|
||||||
|
github.com/maximotejeda/msvc-proto/golang/tgbuser v0.0.0-11 h1:4ePlM4kOlvqygH5o6K039DRJSRBEsJ9HbswrbaiA8+U=
|
||||||
|
github.com/maximotejeda/msvc-proto/golang/tgbuser v0.0.0-11/go.mod h1:UeWAtY6XdFuWJwdJPAK++BLE50F6KPotvX7F5DYgCls=
|
||||||
|
github.com/nats-io/nats.go v1.34.1 h1:syWey5xaNHZgicYBemv0nohUPPmaLteiBEUT6Q5+F/4=
|
||||||
|
github.com/nats-io/nats.go v1.34.1/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8=
|
||||||
|
github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI=
|
||||||
|
github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc=
|
||||||
|
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||||
|
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||||
|
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
||||||
|
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||||
|
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||||
|
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||||
|
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||||
|
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||||
|
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be h1:LG9vZxsWGOmUKieR8wPAUR3u3MpnYFQZROPIMaXh7/A=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
|
||||||
|
google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
|
||||||
|
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
|
||||||
|
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||||
|
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||||
|
|||||||
192
internal/adapters/chat/chat.go
Normal file
192
internal/adapters/chat/chat.go
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
package chat
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
|
tgb "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||||
|
"golang.org/x/text/runes"
|
||||||
|
"golang.org/x/text/transform"
|
||||||
|
"golang.org/x/text/unicode/norm"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ChatPool *sync.Pool
|
||||||
|
|
||||||
|
type ChatObj struct {
|
||||||
|
update *tgb.Update
|
||||||
|
bot *tgb.BotAPI
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewChatObj(bot *tgb.BotAPI, updt *tgb.Update) (chat *ChatObj) {
|
||||||
|
if ChatPool == nil {
|
||||||
|
ChatPool = &sync.Pool{
|
||||||
|
New: func() any { return &ChatObj{} },
|
||||||
|
}
|
||||||
|
for i := 0; i < 20; i++ {
|
||||||
|
ChatPool.Put(ChatPool.New())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Println("alredy populated")
|
||||||
|
}
|
||||||
|
|
||||||
|
chat = ChatPool.Get().(*ChatObj)
|
||||||
|
chat.update = updt
|
||||||
|
chat.bot = bot
|
||||||
|
return chat
|
||||||
|
}
|
||||||
|
|
||||||
|
func EmptyChat(chat *ChatObj) {
|
||||||
|
chat.update = nil
|
||||||
|
ChatPool.Put(chat)
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleChat(bot *tgb.BotAPI, updt *tgb.Update) {
|
||||||
|
chat := NewChatObj(bot, updt)
|
||||||
|
defer EmptyChat(chat)
|
||||||
|
text := NormalizeText(updt.Message.Text)
|
||||||
|
textList := strings.Split(text, " ")
|
||||||
|
msg := tgb.NewMessage(updt.Message.Chat.ID, "")
|
||||||
|
if len(textList) >= 1 && MessageChecker(text) == "digit" {
|
||||||
|
// in case of message match a cedula
|
||||||
|
ced, err := info.NewCedula(text)
|
||||||
|
if err != nil {
|
||||||
|
msg.Text = "cedula no reconocida " + err.Error()
|
||||||
|
} else {
|
||||||
|
msg, photoMsg := ProcessByCedula(*ced)
|
||||||
|
msg.ChatID = updt.Message.Chat.ID
|
||||||
|
if photoMsg != nil {
|
||||||
|
photoMsg.ChatID = updt.Message.Chat.ID
|
||||||
|
bot.Send(photoMsg)
|
||||||
|
}
|
||||||
|
bot.Send(msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if len(textList) >= 2 && MessageChecker(text) == "word" {
|
||||||
|
msg := ProcessByName(textList)
|
||||||
|
msg.ChatID = updt.Message.Chat.ID
|
||||||
|
bot.Send(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.ReplyToMessageID = updt.Message.MessageID
|
||||||
|
bot.Send(msg)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessByCedula
|
||||||
|
//
|
||||||
|
// When a text arrives the chat if it match a cedula try to query db
|
||||||
|
func ProcessByCedula(dbS *db.DB, ced info.Cedula) (message *tgb.MessageConfig, fotoMsg *tgb.PhotoConfig) {
|
||||||
|
msg := tgb.NewMessage(0, "")
|
||||||
|
message = &msg
|
||||||
|
ctx := context.Background()
|
||||||
|
info, err := info.GetByCedula(ctx, dbS, ced)
|
||||||
|
if err != nil {
|
||||||
|
msg.Text = "error buscando cedula " + err.Error()
|
||||||
|
}
|
||||||
|
if info != nil {
|
||||||
|
msg.Text = fmt.Sprintf(`
|
||||||
|
Nombres: %s
|
||||||
|
Apellidos: %s %s
|
||||||
|
Cedula: %s
|
||||||
|
Direccion: %s
|
||||||
|
Telefono: %s
|
||||||
|
`,
|
||||||
|
strings.ToLower(info.Nombres), strings.ToLower(info.Apellido1), strings.ToLower(info.Apellido2),
|
||||||
|
info.MunCed+info.SeqCed+info.VerCed, RemoveSpaces(info.Direccion), info.Telefono)
|
||||||
|
}
|
||||||
|
foto, err := fotos.GetImageByCedulas(ctx, dbS, ced)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Photo not found", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if foto != nil {
|
||||||
|
rq := tgb.FileReader{Name: fmt.Sprintf("%s-%s-%s", ced.MunCed, ced.SeqCed, ced.VerCed), Reader: bytes.NewReader(foto)}
|
||||||
|
fotost := tgb.NewPhoto(msg.ChatID, rq)
|
||||||
|
fotoMsg = &fotost
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ProcessByName(dbS *db.DB, nameList []string) (message *tgb.MessageConfig) {
|
||||||
|
ctx := context.Background()
|
||||||
|
var err error
|
||||||
|
page := 0
|
||||||
|
// look for if the last part of the list is a number
|
||||||
|
lastItem := nameList[len(nameList)-1]
|
||||||
|
|
||||||
|
if MessageChecker(lastItem) == "digit" && !strings.HasPrefix(lastItem, "0") {
|
||||||
|
pageInt, err := strconv.Atoi(lastItem)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
nameList = nameList[:len(nameList)-1]
|
||||||
|
if pageInt < 20 {
|
||||||
|
page = pageInt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rows := &info.MultipleResults{}
|
||||||
|
message = &tgb.MessageConfig{}
|
||||||
|
text := strings.Join(nameList, " ")
|
||||||
|
rows, err = info.GetByFTS(ctx, dbS, text, uint(page))
|
||||||
|
if err != nil {
|
||||||
|
message.Text = "no hubo resultados para la busqueda"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
textToSend := fmt.Sprintf(`
|
||||||
|
Busqueda:
|
||||||
|
Texto: %s
|
||||||
|
Pagina: %d
|
||||||
|
resultados desde: %d A %d
|
||||||
|
De un total de: %d
|
||||||
|
|
||||||
|
`,
|
||||||
|
text,
|
||||||
|
(rows.Page),
|
||||||
|
(rows.Page*10)-9, (rows.Page)*10,
|
||||||
|
rows.Total)
|
||||||
|
for _, v := range rows.Data {
|
||||||
|
textToSend = textToSend + fmt.Sprintf("\n %s %s %s \n %s-%s-%s\n", strings.ToLower(v.Nombres), strings.ToLower(v.Apellido1), strings.ToLower(v.Apellido2), v.MunCed, v.SeqCed, v.VerCed)
|
||||||
|
}
|
||||||
|
message.Text = textToSend
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func MessageChecker(text string) string {
|
||||||
|
// Check is text start with number
|
||||||
|
checkSpace := regexp.MustCompile(`^[\s]+.*`)
|
||||||
|
checkNumber := regexp.MustCompile(`^[\d]+.*`)
|
||||||
|
checkWord := regexp.MustCompile(`^[a-zA-Z% ]+.*`)
|
||||||
|
|
||||||
|
if checkNumber.MatchString(text) {
|
||||||
|
return "digit"
|
||||||
|
} else if checkWord.MatchString(text) {
|
||||||
|
return "word"
|
||||||
|
} else if checkSpace.MatchString(text) {
|
||||||
|
t := strings.TrimSpace(text)
|
||||||
|
return MessageChecker(t)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// NormalizeText
|
||||||
|
// remove foreign accent
|
||||||
|
func NormalizeText(text string) string {
|
||||||
|
t := transform.Chain(norm.NFD, runes.Remove(runes.In(unicode.Mn)), norm.NFC)
|
||||||
|
result, _, _ := transform.String(t, text)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func RemoveSpaces(text string) (res string) {
|
||||||
|
re := regexp.MustCompile(`[\s]+`)
|
||||||
|
|
||||||
|
res = re.ReplaceAllString(text, " ")
|
||||||
|
return
|
||||||
|
}
|
||||||
118
internal/adapters/dolar/dolar.go
Normal file
118
internal/adapters/dolar/dolar.go
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
package dolar
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/maximotejeda/msvc-proto/golang/dolar"
|
||||||
|
"github.com/maximotejeda/us_dop_bot/internal/application/domain"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Adapter struct {
|
||||||
|
dolar dolar.DollarClient
|
||||||
|
conn *grpc.ClientConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAdapter(conn *grpc.ClientConn) (*Adapter, error) {
|
||||||
|
client := dolar.NewDollarClient(conn)
|
||||||
|
return &Adapter{dolar: client, conn: conn}, nil
|
||||||
|
}
|
||||||
|
func (a *Adapter) GetLatest(name string) (*domain.History, error) {
|
||||||
|
hr, err := a.dolar.GetLatest(context.Background(), &dolar.GetLatestRequest{Name: name})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
history := &domain.History{
|
||||||
|
ID: hr.Actual.Id,
|
||||||
|
Institution: domain.Institution{
|
||||||
|
Name: hr.Actual.Name,
|
||||||
|
},
|
||||||
|
Compra: float64(hr.Actual.Compra),
|
||||||
|
Venta: float64(hr.Actual.Venta),
|
||||||
|
Parser: hr.Actual.Parser,
|
||||||
|
Parsed: hr.Actual.Parsed,
|
||||||
|
}
|
||||||
|
return history, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Adapter) GetSince(name string, duration int64) (list []*domain.History, err error) {
|
||||||
|
hrl, err := a.dolar.GetSince(context.Background(), &dolar.GetSinceRequest{
|
||||||
|
Name: name,
|
||||||
|
Duration: duration,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
list = []*domain.History{}
|
||||||
|
for _, hr := range hrl.Histories {
|
||||||
|
hist := &domain.History{
|
||||||
|
ID: hr.Id,
|
||||||
|
Institution: domain.Institution{
|
||||||
|
Name: hr.Name,
|
||||||
|
},
|
||||||
|
Compra: float64(hr.Compra),
|
||||||
|
Venta: float64(hr.Venta),
|
||||||
|
Parser: hr.Parser,
|
||||||
|
Parsed: hr.Parsed,
|
||||||
|
}
|
||||||
|
list = append(list, hist)
|
||||||
|
}
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Adapter) GetInstByType(name string) (list []string, err error) {
|
||||||
|
hrl, err := a.dolar.GetInstByType(context.Background(), &dolar.GetInstByTypeRequest{
|
||||||
|
Name: name,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
list = []string{}
|
||||||
|
|
||||||
|
list = append(list, hrl.InstList...)
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Adapter) Subscribe(tgbid int64, instName string) (bool, error) {
|
||||||
|
ctx := context.Background()
|
||||||
|
_, err := a.dolar.TGBSubscribe(ctx, &dolar.TGBSubscribeRequest{TgbId: tgbid, InstName: instName})
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Adapter) Unsubscribe(tgbid int64, instName string) (bool, error) {
|
||||||
|
ctx := context.Background()
|
||||||
|
_, err := a.dolar.TGBUnsubscribe(ctx, &dolar.TGBUnsubscribeRequest{TgbId: tgbid, InstName: instName})
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
func (a Adapter) GetSubscribedUsers(instName string) ([]int64, error) {
|
||||||
|
ctx := context.Background()
|
||||||
|
users, err := a.dolar.TGBGetSubscribedUsers(ctx, &dolar.TGBGetSubscribedUsersRequest{InstName: instName})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
list := []int64{}
|
||||||
|
list = append(list, users.TgbIds...)
|
||||||
|
|
||||||
|
return list, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
func (a Adapter) GetSubscribedInsts(tgbid int64) ([]string, error) {
|
||||||
|
ctx := context.Background()
|
||||||
|
insts, err := a.dolar.TGBGetSubscribedInsts(ctx, &dolar.TGBGetSubscribedInstRequest{TgbId: tgbid})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
list := []string{}
|
||||||
|
|
||||||
|
list = append(list, insts.InstName...)
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
143
internal/adapters/user/user.go
Normal file
143
internal/adapters/user/user.go
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log/slog"
|
||||||
|
|
||||||
|
tgb "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||||
|
"github.com/maximotejeda/msvc-proto/golang/tgbuser"
|
||||||
|
"github.com/maximotejeda/us_dop_bot/internal/application/domain"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Adapter struct {
|
||||||
|
user tgbuser.UserManagerClient
|
||||||
|
conn *grpc.ClientConn
|
||||||
|
log *slog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAdapter(conn *grpc.ClientConn) (*Adapter, error) {
|
||||||
|
log := slog.Default()
|
||||||
|
log = log.With("location", "user adapter")
|
||||||
|
client := tgbuser.NewUserManagerClient(conn)
|
||||||
|
return &Adapter{user: client, conn: conn, log: log}, nil
|
||||||
|
}
|
||||||
|
func (a *Adapter) Get(tgbid int64) (*domain.User, error) {
|
||||||
|
hr, err := a.user.Get(context.Background(), &tgbuser.GetTGBUserRequest{TgbId: tgbid})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
user := &domain.User{
|
||||||
|
ID: hr.User.Id,
|
||||||
|
TguID: hr.User.TgbId,
|
||||||
|
Created: hr.User.Created,
|
||||||
|
Edited: hr.User.Edited,
|
||||||
|
}
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Adapter) Create(user *tgb.User) (b bool, err error) {
|
||||||
|
_, err = a.user.Create(context.Background(), &tgbuser.CreateTGBUserRequest{
|
||||||
|
User: &tgbuser.User{
|
||||||
|
TgbId: user.ID,
|
||||||
|
FirstName: user.FirstName,
|
||||||
|
LastName: user.LastName,
|
||||||
|
Username: user.UserName,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Adapter) Edit(user *tgb.User) (b bool, err error) {
|
||||||
|
|
||||||
|
_, err = a.user.Edit(context.Background(), &tgbuser.EditTGBUserRequest{
|
||||||
|
User: &tgbuser.User{
|
||||||
|
Username: user.UserName,
|
||||||
|
FirstName: user.FirstName,
|
||||||
|
LastName: user.LastName,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
func (a Adapter) Delete(tgbid int64) (b bool, err error) {
|
||||||
|
_, err = a.user.Delete(context.Background(), &tgbuser.DeleteTGBUserRequest{
|
||||||
|
TgbId: tgbid,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Adapter) GetBots(tgbid int64) (s []string, err error) {
|
||||||
|
hr, err := a.user.GetBots(context.Background(), &tgbuser.GetBotsTGBUserRequest{
|
||||||
|
TgbId: tgbid,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
s = []string{}
|
||||||
|
|
||||||
|
if len(hr.Bots) <= 0 {
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, it := range hr.Bots {
|
||||||
|
s = append(s, it.BotName)
|
||||||
|
}
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Adapter) AddBot(tgbid int64, botname string) (b bool, err error) {
|
||||||
|
_, err = a.user.AddBot(context.Background(), &tgbuser.AddBotTGBUserRequest{
|
||||||
|
TgbId: tgbid,
|
||||||
|
BotName: botname,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Adapter) DeleteBot(tgbid int64, botname string) (b bool, err error) {
|
||||||
|
_, err = a.user.DeleteBot(context.Background(), &tgbuser.DeleteBotTGBUserRequest{
|
||||||
|
TgbId: tgbid,
|
||||||
|
BotName: botname,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Adapter) GetAllBotsUsers(botname string) ([]*domain.User, error) {
|
||||||
|
users, err := a.user.GetAllBotsUsers(context.Background(), &tgbuser.GetAllBotsUsersRequest{BotName: botname})
|
||||||
|
if err != nil {
|
||||||
|
a.log.Error("get all bots users", "error", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
a.log.Info("users", "result", users)
|
||||||
|
list := []*domain.User{}
|
||||||
|
for _, us := range users.Users {
|
||||||
|
user := &domain.User{
|
||||||
|
ID: us.Id,
|
||||||
|
TguID: us.TgbId,
|
||||||
|
Username: us.Username,
|
||||||
|
FirstName: us.FirstName,
|
||||||
|
LastName: us.LastName,
|
||||||
|
Created: us.Created,
|
||||||
|
Edited: us.Edited,
|
||||||
|
}
|
||||||
|
list = append(list, user)
|
||||||
|
}
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
74
internal/application/api/api.go
Normal file
74
internal/application/api/api.go
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log/slog"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||||
|
"github.com/maximotejeda/us_dop_bot/internal/application/command"
|
||||||
|
"github.com/maximotejeda/us_dop_bot/internal/application/message"
|
||||||
|
"github.com/maximotejeda/us_dop_bot/internal/application/query"
|
||||||
|
"github.com/maximotejeda/us_dop_bot/internal/ports"
|
||||||
|
)
|
||||||
|
|
||||||
|
type api struct {
|
||||||
|
bot *tgbotapi.BotAPI
|
||||||
|
command ports.Tgb
|
||||||
|
message ports.Tgb
|
||||||
|
query ports.Tgb
|
||||||
|
dolar ports.DolarService
|
||||||
|
user ports.UserService
|
||||||
|
log *slog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewApi(bot *tgbotapi.BotAPI) *api {
|
||||||
|
log := slog.Default()
|
||||||
|
log = log.With("location", "root")
|
||||||
|
return &api{bot: bot, log: log}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *api) Run(update *tgbotapi.Update, dolar ports.DolarService, user ports.UserService) {
|
||||||
|
// check for user in db
|
||||||
|
_, err := user.Get(update.SentFrom().ID)
|
||||||
|
if err != nil {
|
||||||
|
|
||||||
|
if strings.Contains(err.Error(), "sql: no rows in result set") {
|
||||||
|
a.log.Info("user not in database ")
|
||||||
|
a.log.Info("bot not restricted adding user to DB")
|
||||||
|
user.Create(update.SentFrom())
|
||||||
|
a.log.Info("Adding query permision to user over bot")
|
||||||
|
user.AddBot(update.SentFrom().ID, a.bot.Self.UserName)
|
||||||
|
}
|
||||||
|
a.log.Error("getting user", "error", err)
|
||||||
|
a.log.Info("bot not restricted adding user to DB")
|
||||||
|
}
|
||||||
|
bots, err := user.GetBots(update.SentFrom().ID)
|
||||||
|
if err != nil {
|
||||||
|
a.log.Error("getting bots", "error", err)
|
||||||
|
}
|
||||||
|
if !slices.Contains(bots, a.bot.Self.UserName) {
|
||||||
|
a.log.Info("bot not found in db for user", "bot", a.bot.Self.UserName)
|
||||||
|
a.log.Info("adding bots", "user", update.SentFrom().UserName, "bot", a.bot.Self.UserName)
|
||||||
|
_, err := user.AddBot(update.SentFrom().ID, a.bot.Self.UserName)
|
||||||
|
if err != nil {
|
||||||
|
a.log.Error("adding bots", "user", update.SentFrom().UserName, "bot", a.bot.Self.UserName, "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
msg := update.Message
|
||||||
|
if msg != nil { // message is not nil can be a command or a text message
|
||||||
|
if msg.IsCommand() {
|
||||||
|
a.command = command.NewCommand(a.bot, update, dolar, user)
|
||||||
|
a.command.Handler()
|
||||||
|
// is a command
|
||||||
|
} else if msg.Text != "" {
|
||||||
|
// is a text message
|
||||||
|
a.message = message.NewMessage(a.bot, update, dolar, user)
|
||||||
|
a.message.Handler()
|
||||||
|
}
|
||||||
|
} else if update.CallbackQuery != nil {
|
||||||
|
// is a cal back query
|
||||||
|
a.query = query.NewQuery(a.bot, update, dolar, user)
|
||||||
|
a.query.Handler()
|
||||||
|
}
|
||||||
|
}
|
||||||
109
internal/application/broadcaster/broadcaster.go
Normal file
109
internal/application/broadcaster/broadcaster.go
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
package broadcaster
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||||
|
"github.com/maximotejeda/us_dop_bot/internal/application/domain"
|
||||||
|
"github.com/maximotejeda/us_dop_bot/internal/application/helpers"
|
||||||
|
"github.com/maximotejeda/us_dop_bot/internal/ports"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Broadcaster struct {
|
||||||
|
ctx context.Context
|
||||||
|
log *slog.Logger
|
||||||
|
user ports.UserService
|
||||||
|
dolar ports.DolarService
|
||||||
|
data []byte
|
||||||
|
botName string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Message struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
Data change `json:"data"`
|
||||||
|
Error error `json:"error"`
|
||||||
|
}
|
||||||
|
type change struct {
|
||||||
|
Before *domain.History `json:"before"`
|
||||||
|
After *domain.History `json:"after"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBroadCast(ctx context.Context, user ports.UserService, dolar ports.DolarService, data []byte) *Broadcaster {
|
||||||
|
log := slog.Default()
|
||||||
|
log = log.With("place", "broadcast")
|
||||||
|
return &Broadcaster{
|
||||||
|
ctx: ctx,
|
||||||
|
user: user,
|
||||||
|
dolar: dolar,
|
||||||
|
data: data,
|
||||||
|
log: log,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Broadcaster) SendList() []tgbotapi.MessageConfig {
|
||||||
|
// convert data to map
|
||||||
|
m := Message{}
|
||||||
|
listMsg := []tgbotapi.MessageConfig{}
|
||||||
|
err := json.Unmarshal(b.data, &m)
|
||||||
|
if err != nil {
|
||||||
|
b.log.Error("unmarshaling data", "error", err)
|
||||||
|
}
|
||||||
|
if strings.Contains(m.Message, "change") && m.Error == nil {
|
||||||
|
|
||||||
|
userList, err := b.dolar.GetSubscribedUsers(m.Data.After.Institution.Name)
|
||||||
|
if err != nil {
|
||||||
|
b.log.Error("querying DB data", "error", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
cancelBTN := map[string]string{}
|
||||||
|
cancelBTN["Eliminar ❌"] = "cancelar=true"
|
||||||
|
keyboard := helpers.CreateKeyboard(cancelBTN)
|
||||||
|
b.log.Info("printing change", "user list", userList, "name", m.Data.After.Institution.Name, "data", m.Data.After)
|
||||||
|
compraCHG := comparer(m.Data.Before.Compra, m.Data.After.Compra)
|
||||||
|
ventaCHG := comparer(m.Data.Before.Venta, m.Data.After.Venta)
|
||||||
|
loc, _ := time.LoadLocation("America/Santo_Domingo")
|
||||||
|
text := fmt.Sprintf("Cambio Registrado:\n\nInstitucion: %s\n\t Compra: %.2f %s %.2f\n\t Venta: %.2f %s %.2f\n\n\t %s", m.Data.After.Institution.Name, m.Data.Before.Compra, compraCHG, m.Data.After.Compra, m.Data.Before.Venta, ventaCHG, m.Data.After.Venta, time.Unix(m.Data.After.Parsed, 0).In(loc).Format(time.DateTime))
|
||||||
|
|
||||||
|
for _, userID := range userList {
|
||||||
|
msg := tgbotapi.NewMessage(userID, text)
|
||||||
|
msg.ReplyMarkup = keyboard
|
||||||
|
listMsg = append(listMsg, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return listMsg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b Broadcaster) SendAllUsers(ctx context.Context, log *slog.Logger, data []byte, botname string) []tgbotapi.MessageConfig {
|
||||||
|
userList, err := b.user.GetAllBotsUsers(botname)
|
||||||
|
b.log.Info("broadcast", "user list", userList)
|
||||||
|
msgs := []tgbotapi.MessageConfig{}
|
||||||
|
if err != nil {
|
||||||
|
return msgs
|
||||||
|
}
|
||||||
|
cancelBTN := map[string]string{}
|
||||||
|
cancelBTN["Eliminar ❌"] = "cancelar=true"
|
||||||
|
keyboard := helpers.CreateKeyboard(cancelBTN)
|
||||||
|
for _, user := range userList {
|
||||||
|
msg := tgbotapi.NewMessage(user.TguID, "")
|
||||||
|
msg.Text = string(data)
|
||||||
|
msg.ReplyMarkup = keyboard
|
||||||
|
msgs = append(msgs, msg)
|
||||||
|
}
|
||||||
|
return msgs
|
||||||
|
}
|
||||||
|
|
||||||
|
func comparer(before, after float64) string {
|
||||||
|
if before > after {
|
||||||
|
return "⬇️"
|
||||||
|
} else if before < after {
|
||||||
|
return "⬆️"
|
||||||
|
} else {
|
||||||
|
return "🟰"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
220
internal/application/command/command.go
Normal file
220
internal/application/command/command.go
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log/slog"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||||
|
"github.com/maximotejeda/us_dop_bot/internal/application/helpers"
|
||||||
|
"github.com/maximotejeda/us_dop_bot/internal/ports"
|
||||||
|
)
|
||||||
|
|
||||||
|
var commandPool *sync.Pool
|
||||||
|
|
||||||
|
type Command struct {
|
||||||
|
bot *tgbotapi.BotAPI
|
||||||
|
update *tgbotapi.Update
|
||||||
|
msg *tgbotapi.MessageConfig
|
||||||
|
log *slog.Logger
|
||||||
|
dolar ports.DolarService
|
||||||
|
user ports.UserService
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCommand
|
||||||
|
// Factory for Command Handler
|
||||||
|
func NewCommand(bot *tgbotapi.BotAPI, update *tgbotapi.Update, dolar ports.DolarService, user ports.UserService) *Command {
|
||||||
|
if commandPool == nil {
|
||||||
|
commandPool = &sync.Pool{
|
||||||
|
New: func() any { return &Command{} },
|
||||||
|
}
|
||||||
|
for i := 0; i < 20; i++ {
|
||||||
|
commandPool.Put(commandPool.New())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log := slog.Default()
|
||||||
|
log = log.With("function", "command", "chat", update.Message.Chat.ID, "userid", update.Message.From.ID, "username", update.Message.From.UserName)
|
||||||
|
commands := commandPool.Get().(*Command)
|
||||||
|
commands.update = update
|
||||||
|
commands.bot = bot
|
||||||
|
commands.log = log
|
||||||
|
commands.dolar = dolar
|
||||||
|
commands.user = user
|
||||||
|
return commands
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty
|
||||||
|
// Returns pointer to command pool
|
||||||
|
func (c *Command) Empty() {
|
||||||
|
c.update = nil
|
||||||
|
c.msg = nil
|
||||||
|
c.log = nil
|
||||||
|
c.dolar = nil
|
||||||
|
commandPool.Put(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send
|
||||||
|
// Process command handlers
|
||||||
|
func (c *Command) Send() {
|
||||||
|
defer c.Empty()
|
||||||
|
c.bot.Send(*c.msg)
|
||||||
|
del := tgbotapi.NewDeleteMessage(c.update.Message.From.ID, c.update.Message.MessageID)
|
||||||
|
c.bot.Send(del)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler
|
||||||
|
// Manage command message for chat
|
||||||
|
func (c *Command) Handler() {
|
||||||
|
msg := tgbotapi.NewMessage(c.update.Message.Chat.ID, "command")
|
||||||
|
c.msg = &msg
|
||||||
|
command := c.update.Message.Command()
|
||||||
|
|
||||||
|
switch strings.ToLower(command) {
|
||||||
|
case "list", "lista":
|
||||||
|
msg.Text = "list todo"
|
||||||
|
case "listabancos":
|
||||||
|
|
||||||
|
bancos, err := c.lista("bancos")
|
||||||
|
if err != nil {
|
||||||
|
c.log.Error("query bancos", "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
msg = bancos
|
||||||
|
case "listacajas":
|
||||||
|
cajas, err := c.lista("cajas")
|
||||||
|
if err != nil {
|
||||||
|
c.log.Error("query cajas", "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
msg = cajas
|
||||||
|
|
||||||
|
case "listagentes":
|
||||||
|
agentes, err := c.lista("agentes")
|
||||||
|
if err != nil {
|
||||||
|
c.log.Error("query bancos", "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
msg = agentes
|
||||||
|
case "consulta":
|
||||||
|
instSubs, err := c.dolar.GetSubscribedInsts(c.update.Message.From.ID)
|
||||||
|
if err != nil {
|
||||||
|
c.log.Error("command-consulta", "err", err)
|
||||||
|
}
|
||||||
|
if len(instSubs) > 0 {
|
||||||
|
btnSTR := map[string]string{}
|
||||||
|
for _, inst := range instSubs {
|
||||||
|
switch inst {
|
||||||
|
case "asociacion popular de ahorros y prestamos":
|
||||||
|
btnSTR[inst] = "consultar=true&name=apap"
|
||||||
|
case "asociacion cibao de ahorros y prestamos":
|
||||||
|
btnSTR[inst] = "consultar=true&name=acap"
|
||||||
|
case "asociacion la nacional de ahorros y prestamos":
|
||||||
|
btnSTR[inst] = "consultar=true&name=anap"
|
||||||
|
case "asociacion peravia de ahorros y prestamos":
|
||||||
|
btnSTR[inst] = "consultar=true&name=aperap"
|
||||||
|
default:
|
||||||
|
btnSTR[inst] = "consultar=true&name=" + inst
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
btnSTR["cancelar ❌"] = "cancelar=true"
|
||||||
|
keyboard := helpers.CreateKeyboard(btnSTR)
|
||||||
|
msg.ReplyMarkup = keyboard
|
||||||
|
msg.Text = "Suscripciones actuales 💰\nPuedes hacer click para Ver cambios en los precios de las suscripcion\n o presionar cancelar"
|
||||||
|
} else {
|
||||||
|
msg.Text = "No existen subscripciones actualmente"
|
||||||
|
}
|
||||||
|
case "status", "info":
|
||||||
|
instSubbed, err := c.dolar.GetSubscribedInsts(c.update.Message.From.ID)
|
||||||
|
if err != nil {
|
||||||
|
c.log.Error("command-status", "err", err)
|
||||||
|
}
|
||||||
|
if len(instSubbed) > 0 {
|
||||||
|
btnSTR := map[string]string{}
|
||||||
|
for _, inst := range instSubbed {
|
||||||
|
btnSTR[inst] = "unsubs=true&name=" + inst
|
||||||
|
}
|
||||||
|
btnSTR["cancelar ❌"] = "cancelar=true"
|
||||||
|
keyboard := helpers.CreateKeyboard(btnSTR)
|
||||||
|
msg.ReplyMarkup = keyboard
|
||||||
|
msg.Text = "Suscripciones actuales 💰\n Puedes hacer click en una para eliminar su subscripcion\nPresionar cancelar para omitir."
|
||||||
|
} else {
|
||||||
|
msg.Text = "No existen subscripciones actualmente"
|
||||||
|
}
|
||||||
|
case "reset":
|
||||||
|
reset := map[string]string{"Reset": "reset=true", "cancelar ❌": "cancelar=true"}
|
||||||
|
keyboard := helpers.CreateKeyboard(reset)
|
||||||
|
msg.ReplyMarkup = keyboard
|
||||||
|
msg.Text = "Esta Seguro DE Eliminar todas las suscripciones del usuario."
|
||||||
|
case "help", "start", "ayuda", "h":
|
||||||
|
|
||||||
|
help := `
|
||||||
|
Asistente de cambio US <-> DOP
|
||||||
|
🇺🇸 ↔️ 🇩🇴
|
||||||
|
Tracker del precio del dolar para RD.
|
||||||
|
|
||||||
|
Funciona suscribiendo instituciones 💸.
|
||||||
|
- Tracker precio del dolar ❇️.
|
||||||
|
- Notificacion mensaje automatico 📈.
|
||||||
|
- Precio actual ⌚.
|
||||||
|
- Historico de precios 📅.
|
||||||
|
|
||||||
|
Comandos Conocidos por el bot:
|
||||||
|
|
||||||
|
/help: Muestra este mensaje ❓
|
||||||
|
/listabancos: Muestra bancos 🏦
|
||||||
|
/listacajas: Muestra asociaciones
|
||||||
|
/listagentes: Muestra agentes 📊
|
||||||
|
/consulta: Consulta entidad suscrita 🛎️
|
||||||
|
/reset: Borra tada suscripcion 🧹
|
||||||
|
/status: Estado del usuario 📋
|
||||||
|
`
|
||||||
|
|
||||||
|
msg.Text = help
|
||||||
|
default:
|
||||||
|
msg.Text = "Commando desconocido intenta con\n/help: to get bot info."
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Send()
|
||||||
|
|
||||||
|
}
|
||||||
|
func (c *Command) lista(instSTR string) (msg tgbotapi.MessageConfig, err error) {
|
||||||
|
var instList []string
|
||||||
|
|
||||||
|
msg = tgbotapi.MessageConfig{}
|
||||||
|
msg.ChatID = c.update.Message.Chat.ID
|
||||||
|
|
||||||
|
instList, err = c.dolar.GetInstByType(instSTR)
|
||||||
|
if err != nil {
|
||||||
|
c.log.Error("[inst-list-query]", "error", err)
|
||||||
|
return msg, err
|
||||||
|
}
|
||||||
|
instMap := map[string]string{}
|
||||||
|
subscribed, err := c.dolar.GetSubscribedInsts(c.update.Message.From.ID)
|
||||||
|
if err != nil {
|
||||||
|
c.log.Error("getting subscription", "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, i := range instList {
|
||||||
|
if slices.Contains(subscribed, i) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
instMap[i] = "subs=true&name=" + i
|
||||||
|
}
|
||||||
|
instMap["cancelar ❌"] = "cancelar=true"
|
||||||
|
keyboard := helpers.CreateKeyboard(instMap)
|
||||||
|
switch instSTR {
|
||||||
|
case "bancos":
|
||||||
|
msg.Text = "Differentes Bancos disponibles para track el precio del cambio ejemplos:\n\n\tBanco Popular\n\n\tBanreservas\n\n"
|
||||||
|
case "agentes":
|
||||||
|
msg.Text = "Differentes Agentes disponibles para track el precio del cambio ejemplos:\n\n\trm\n\n\tgirosol\n\n"
|
||||||
|
case "cajas":
|
||||||
|
msg.Text = "Differentes cajas disponibles para track el precio del cambio ejemplos\n\n\tasociacion popular\n\n\tasociacion cibao\n\n"
|
||||||
|
default:
|
||||||
|
msg.Text = "lista de instuiticiones no reconocidad Opciones disponibles:\n\n\tbancos\n\n\tcajas\n\n\tagentes"
|
||||||
|
}
|
||||||
|
msg.ReplyMarkup = keyboard
|
||||||
|
return msg, nil
|
||||||
|
}
|
||||||
17
internal/application/domain/history.go
Normal file
17
internal/application/domain/history.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package domain
|
||||||
|
|
||||||
|
type History struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Institution Institution `json:"institution"`
|
||||||
|
Compra float64 `json:"compra,omitempty"`
|
||||||
|
Venta float64 `json:"venta,omitempty"`
|
||||||
|
Parser string `json:"parser,omitempty"`
|
||||||
|
Parsed int64 `json:"parsed,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Institution struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
ShortName string `json:"short_name"`
|
||||||
|
Created int64 `json:"created"`
|
||||||
|
}
|
||||||
13
internal/application/domain/user.go
Normal file
13
internal/application/domain/user.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package domain
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
ID int64
|
||||||
|
TguID int64
|
||||||
|
Username string `json:"username"`
|
||||||
|
FirstName string `json:"first_name"`
|
||||||
|
LastName string `json:"last_name"`
|
||||||
|
Subs []string
|
||||||
|
Created int64
|
||||||
|
Edited int64
|
||||||
|
Deleted int64
|
||||||
|
}
|
||||||
215
internal/application/message/message.go
Normal file
215
internal/application/message/message.go
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
package message
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||||
|
"github.com/maximotejeda/us_dop_bot/internal/application/domain"
|
||||||
|
"github.com/maximotejeda/us_dop_bot/internal/application/static"
|
||||||
|
"github.com/maximotejeda/us_dop_bot/internal/ports"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ChatPool *sync.Pool
|
||||||
|
|
||||||
|
type Message struct {
|
||||||
|
bot *tgbotapi.BotAPI
|
||||||
|
update *tgbotapi.Update
|
||||||
|
msg *tgbotapi.MessageConfig
|
||||||
|
log *slog.Logger
|
||||||
|
dolar ports.DolarService
|
||||||
|
user ports.UserService
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMessage
|
||||||
|
// Factory for message handler
|
||||||
|
func NewMessage(bot *tgbotapi.BotAPI, update *tgbotapi.Update, dolar ports.DolarService, user ports.UserService) *Message {
|
||||||
|
if ChatPool == nil {
|
||||||
|
ChatPool = &sync.Pool{
|
||||||
|
New: func() any { return &Message{} },
|
||||||
|
}
|
||||||
|
for i := 0; i < 20; i++ {
|
||||||
|
ChatPool.Put(ChatPool.New())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log := slog.Default()
|
||||||
|
log = log.With("function", "message", "chat", update.Message.Chat.ID, "userid", update.Message.From.ID, "username", update.Message.From.UserName)
|
||||||
|
message := ChatPool.Get().(*Message)
|
||||||
|
message.update = update
|
||||||
|
message.bot = bot
|
||||||
|
message.log = log
|
||||||
|
message.dolar = dolar
|
||||||
|
message.user = user
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty
|
||||||
|
// Returns pointer to pool
|
||||||
|
func (m *Message) Empty() {
|
||||||
|
m.update = nil
|
||||||
|
m.msg = nil
|
||||||
|
m.log = nil
|
||||||
|
m.dolar = nil
|
||||||
|
m.user = nil
|
||||||
|
ChatPool.Put(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send
|
||||||
|
// Process message sending to bot
|
||||||
|
func (m *Message) Send() {
|
||||||
|
defer m.Empty()
|
||||||
|
m.bot.Send(m.msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler
|
||||||
|
// Manage features for messages
|
||||||
|
func (m *Message) Handler() {
|
||||||
|
msg := tgbotapi.NewMessage(m.update.Message.Chat.ID, "")
|
||||||
|
m.msg = &msg
|
||||||
|
msgtext := static.RemoveAccent(m.update.Message.Text)
|
||||||
|
re := regexp.MustCompile(`(?P<operacion>([cC]omprar?\s?(me)?|[vV]en(de)?(r)?(ta)?\s?(me)?))\s(?P<cantidad>[0-9.]{1,8})\s(?P<moneda>(dolar(e)?(s)?|peso(s)?|dollars?))\s?(?P<institucion>(.*))?`)
|
||||||
|
match := re.FindStringSubmatch(msgtext)
|
||||||
|
if len(match) != 0 {
|
||||||
|
operacion := match[1]
|
||||||
|
operacion = strings.ToLower(operacion)
|
||||||
|
cantidadStr := match[8]
|
||||||
|
cantidad, err := strconv.ParseFloat(cantidadStr, 64)
|
||||||
|
if err != nil {
|
||||||
|
m.log.Error("converting to float cantidad", "error", err)
|
||||||
|
m.msg.Text = "cantidad no reconocidad " + cantidadStr
|
||||||
|
m.Send()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
moneda := match[9]
|
||||||
|
moneda = strings.ToLower(moneda)
|
||||||
|
institucion := match[14]
|
||||||
|
institucion = strings.ToLower(institucion)
|
||||||
|
switch {
|
||||||
|
case strings.Contains(operacion, "compra"):
|
||||||
|
txt := m.Compra(cantidad, moneda, institucion)
|
||||||
|
m.msg.Text = txt
|
||||||
|
case strings.Contains(operacion, "vend"):
|
||||||
|
txt := m.Venta(cantidad, moneda, institucion)
|
||||||
|
m.msg.Text = txt
|
||||||
|
|
||||||
|
default:
|
||||||
|
m.msg.Text = "operacion no reconocida"
|
||||||
|
}
|
||||||
|
|
||||||
|
m.Send()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Message) Compra(cantidad float64, moneda, institucion string) string {
|
||||||
|
txt := ""
|
||||||
|
inst := static.NewInstList()
|
||||||
|
list := []*domain.History{}
|
||||||
|
if institucion == "" {
|
||||||
|
brd, bp, bhd, bcd := m.getPrincipalBank()
|
||||||
|
list = []*domain.History{brd, bp, bhd, bcd}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
txtList := inst.GetName(institucion)
|
||||||
|
if len(txtList) <= 0 {
|
||||||
|
txt = "no institution with name " + institucion
|
||||||
|
return txt
|
||||||
|
} else {
|
||||||
|
for _, v := range txtList {
|
||||||
|
i, err := m.dolar.GetLatest(v.Name)
|
||||||
|
if err != nil {
|
||||||
|
m.log.Error("getting latest", "inst", v, "error", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
list = append(list, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if strings.Contains(moneda, "peso") {
|
||||||
|
txt = fmt.Sprintf("Comprando dolares \nRD.$ %.2f pesos:\n", cantidad)
|
||||||
|
for _, it := range list {
|
||||||
|
compra := cantidad / it.Venta
|
||||||
|
txt = txt + fmt.Sprintf(" %s \t->\t USD$. %.2f\n", inst.GetAbbrev(it.Institution.Name), compra)
|
||||||
|
}
|
||||||
|
} else if strings.Contains(moneda, "dol") {
|
||||||
|
txt = fmt.Sprintf("Comprando %.2f dolares:\n", cantidad)
|
||||||
|
for _, it := range list {
|
||||||
|
compra := cantidad * it.Venta
|
||||||
|
txt = txt + fmt.Sprintf(" %s \t->\t RD$. %.2f\n", inst.GetAbbrev(it.Institution.Name), compra)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return txt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Message) Venta(cantidad float64, moneda, institucion string) string {
|
||||||
|
txt := ""
|
||||||
|
list := []*domain.History{}
|
||||||
|
|
||||||
|
inst := static.NewInstList()
|
||||||
|
if institucion == "" {
|
||||||
|
brd, bp, bhd, bcd := m.getPrincipalBank()
|
||||||
|
list = []*domain.History{brd, bp, bhd, bcd}
|
||||||
|
} else {
|
||||||
|
txtList := inst.GetName(institucion)
|
||||||
|
if len(txtList) <= 0 {
|
||||||
|
txt = "no institution with name " + institucion
|
||||||
|
return txt
|
||||||
|
} else {
|
||||||
|
for _, v := range txtList {
|
||||||
|
i, err := m.dolar.GetLatest(v.Name)
|
||||||
|
if err != nil {
|
||||||
|
m.log.Error("getting latest", "inst", v, "error", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
list = append(list, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if len(list) <= 0 {
|
||||||
|
return "no institutions found with name " + institucion
|
||||||
|
}
|
||||||
|
if strings.Contains(moneda, "peso") {
|
||||||
|
txt = fmt.Sprintf("Vendiendo equivalente a RD.$ %.2f pesos:\n", cantidad)
|
||||||
|
for _, it := range list {
|
||||||
|
venta := cantidad / it.Compra
|
||||||
|
txt = txt + fmt.Sprintf(" %s \t->\t USD$. %.2f\n", inst.GetAbbrev(it.Institution.Name), venta)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if strings.Contains(moneda, "dol") {
|
||||||
|
txt = fmt.Sprintf("Vendiendo USD.$ %.2f dolares\n", cantidad)
|
||||||
|
for _, it := range list {
|
||||||
|
venta := cantidad * it.Compra
|
||||||
|
txt = txt + fmt.Sprintf(" %s \t->\t RD$. %.2f\n", inst.GetAbbrev(it.Institution.Name), venta)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return txt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Message) getPrincipalBank() (brd, bp, bhd, bcd *domain.History) {
|
||||||
|
// principales bancos
|
||||||
|
// bhd reservas popular central
|
||||||
|
brd, err := m.dolar.GetLatest("banreservas")
|
||||||
|
if err != nil {
|
||||||
|
m.log.Error("query latest for banreservas", "error", err)
|
||||||
|
}
|
||||||
|
bp, err = m.dolar.GetLatest("banco popular")
|
||||||
|
if err != nil {
|
||||||
|
m.log.Error("query latest for banco popular")
|
||||||
|
}
|
||||||
|
bhd, err = m.dolar.GetLatest("banco hipotecario dominicano")
|
||||||
|
if err != nil {
|
||||||
|
m.log.Error("query latest for banreservas")
|
||||||
|
}
|
||||||
|
bcd, err = m.dolar.GetLatest("banco central dominicano")
|
||||||
|
if err != nil {
|
||||||
|
m.log.Error("query latest for banreservas")
|
||||||
|
}
|
||||||
|
return brd, bp, bhd, bcd
|
||||||
|
}
|
||||||
195
internal/application/query/query.go
Normal file
195
internal/application/query/query.go
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
package query
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||||
|
"github.com/maximotejeda/us_dop_bot/internal/application/domain"
|
||||||
|
"github.com/maximotejeda/us_dop_bot/internal/application/helpers"
|
||||||
|
"github.com/maximotejeda/us_dop_bot/internal/ports"
|
||||||
|
)
|
||||||
|
|
||||||
|
var chatPool *sync.Pool
|
||||||
|
|
||||||
|
type Query struct {
|
||||||
|
bot *tgbotapi.BotAPI
|
||||||
|
update *tgbotapi.Update
|
||||||
|
msg *tgbotapi.MessageConfig
|
||||||
|
log *slog.Logger
|
||||||
|
dolar ports.DolarService
|
||||||
|
user ports.UserService
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewQuery
|
||||||
|
// Factory for query handlers
|
||||||
|
func NewQuery(bot *tgbotapi.BotAPI, update *tgbotapi.Update, dolar ports.DolarService, user ports.UserService) *Query {
|
||||||
|
if chatPool == nil {
|
||||||
|
chatPool = &sync.Pool{
|
||||||
|
New: func() any { return &Query{} },
|
||||||
|
}
|
||||||
|
for i := 0; i < 20; i++ {
|
||||||
|
chatPool.Put(chatPool.New())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log := slog.Default()
|
||||||
|
log = log.With("function", "query", "chat", update.CallbackQuery.From.ID, "userid", update.CallbackQuery.From.ID, "username", update.CallbackQuery.From.UserName)
|
||||||
|
query := chatPool.Get().(*Query)
|
||||||
|
query.update = update
|
||||||
|
query.bot = bot
|
||||||
|
query.log = log
|
||||||
|
query.dolar = dolar
|
||||||
|
query.user = user
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty
|
||||||
|
// Returns pointer to pool
|
||||||
|
func (q *Query) Empty() {
|
||||||
|
q.update = nil
|
||||||
|
q.msg = nil
|
||||||
|
q.log = nil
|
||||||
|
chatPool.Put(q)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send
|
||||||
|
// Process Query message
|
||||||
|
func (q *Query) Send() {
|
||||||
|
defer q.Empty()
|
||||||
|
q.bot.Send(q.msg)
|
||||||
|
// Delete previous message
|
||||||
|
del := tgbotapi.NewDeleteMessage(q.update.CallbackQuery.From.ID, q.update.CallbackQuery.Message.MessageID)
|
||||||
|
q.bot.Send(del)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler
|
||||||
|
// Manage query message
|
||||||
|
func (q *Query) Handler() {
|
||||||
|
msg := tgbotapi.NewMessage(q.update.CallbackQuery.Message.Chat.ID, "")
|
||||||
|
q.msg = &msg
|
||||||
|
tUser := q.update.CallbackQuery.From
|
||||||
|
|
||||||
|
data := q.update.CallbackQuery.Data
|
||||||
|
|
||||||
|
dataList := strings.Split(data, "&")
|
||||||
|
|
||||||
|
dataMap := map[string]string{}
|
||||||
|
for _, val := range dataList {
|
||||||
|
subData := strings.Split(val, "=")
|
||||||
|
dataMap[subData[0]] = subData[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case dataMap["subs"] != "":
|
||||||
|
_, err := q.dolar.Subscribe(q.update.CallbackQuery.From.ID, dataMap["name"])
|
||||||
|
if err != nil {
|
||||||
|
q.log.Error("subs-query", "error", err.Error(), "user", q.update.CallbackQuery.From)
|
||||||
|
}
|
||||||
|
case dataMap["unsubs"] != "":
|
||||||
|
_, err := q.dolar.Unsubscribe(q.update.CallbackQuery.From.ID, dataMap["name"])
|
||||||
|
if err != nil {
|
||||||
|
q.log.Error("subs-query", "error", err.Error(), "user", q.update.CallbackQuery.From)
|
||||||
|
}
|
||||||
|
|
||||||
|
case dataMap["reset"] != "":
|
||||||
|
subscriptions, _ := q.dolar.GetSubscribedInsts(q.update.CallbackQuery.From.ID)
|
||||||
|
if len(subscriptions) > 0 {
|
||||||
|
for _, inst := range subscriptions {
|
||||||
|
q.dolar.Unsubscribe(q.update.CallbackQuery.From.ID, inst)
|
||||||
|
q.log.Info("unsubscribing", "institution", inst)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case dataMap["consultar"] != "":
|
||||||
|
name := "&name=" + dataMap["name"]
|
||||||
|
queryMap := map[string]string{
|
||||||
|
"Actual": "query=true&time=0&unit=now" + name,
|
||||||
|
//"30 Minutos": "query=true&time=30&unit=minute" + name,
|
||||||
|
"1 Hora": "query=true&time=1&unit=hour" + name,
|
||||||
|
"6 Horas": "query=true&time=6&unit=hour" + name,
|
||||||
|
"12 Horas": "query=true&time=12&unit=hour" + name,
|
||||||
|
"1 Dia": "query=true&time=24&unit=hour" + name,
|
||||||
|
"1 Semana": "query=true&time=168&unit=hour" + name,
|
||||||
|
"2 Semanas": "query=true&time=336&unit=hour" + name,
|
||||||
|
"1 Mes": "query=true&time=672&unit=hour" + name,
|
||||||
|
}
|
||||||
|
keyboard := helpers.CreateKeyboard(queryMap)
|
||||||
|
msg = tgbotapi.MessageConfig{}
|
||||||
|
msg.ChatID = tUser.ID
|
||||||
|
msg.ReplyMarkup = keyboard
|
||||||
|
msg.Text = fmt.Sprintf("Intervalos de tiempo disponibles para consulta en %s el cambio del dolar desde hace:", dataMap["name"])
|
||||||
|
case dataMap["query"] != "":
|
||||||
|
var timeUnit time.Duration
|
||||||
|
timeAmntSTR := dataMap["time"]
|
||||||
|
timeUnitSTR := dataMap["unit"]
|
||||||
|
name := dataMap["name"]
|
||||||
|
timeAmnt, err := strconv.Atoi(timeAmntSTR)
|
||||||
|
switch name {
|
||||||
|
case "apap":
|
||||||
|
name = "asociacion popular de ahorros y prestamos"
|
||||||
|
case "acap":
|
||||||
|
name = "asociacion cibao de ahorros y prestamos"
|
||||||
|
case "anap":
|
||||||
|
name = "asociacion la nacional de ahorros y prestamos"
|
||||||
|
case "aperap":
|
||||||
|
name = "asociacion peravia de ahorros y prestamos"
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
q.log.Error("[query-query] converting amount of time to int", "error", err)
|
||||||
|
q.Send()
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
switch timeUnitSTR {
|
||||||
|
case "hour":
|
||||||
|
timeUnit = time.Hour * time.Duration(timeAmnt)
|
||||||
|
instList, err := q.dolar.GetSince(name, int64(timeUnit.Minutes()))
|
||||||
|
//q.log.Info("hour provided", "hour", timeUnit, "int hours", int64(timeUnit.Minutes()))
|
||||||
|
if err != nil {
|
||||||
|
q.log.Error("[GETLIST] querying the inst database hours", "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
queryMap := map[string]string{"clear": "cancel=true"}
|
||||||
|
keyboard := helpers.CreateKeyboard(queryMap)
|
||||||
|
msg = tgbotapi.MessageConfig{}
|
||||||
|
msg.ChatID = tUser.ID
|
||||||
|
msg.ReplyMarkup = keyboard
|
||||||
|
|
||||||
|
msg.Text = instMessage(instList)
|
||||||
|
case "now":
|
||||||
|
instRes, err := q.dolar.GetLatest(name)
|
||||||
|
if err != nil {
|
||||||
|
q.log.Error("queriing the inst database now", "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
queryMap := map[string]string{"clear": "cancel=true"}
|
||||||
|
keyboard := helpers.CreateKeyboard(queryMap)
|
||||||
|
msg = tgbotapi.MessageConfig{}
|
||||||
|
msg.ChatID = tUser.ID
|
||||||
|
|
||||||
|
msg.ReplyMarkup = keyboard
|
||||||
|
msg.Text = fmt.Sprintf("%s\nCompra: %.2f\nVenta: %.2f", instRes.Institution.Name, instRes.Compra, instRes.Venta)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
q.Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
func instMessage(insts []*domain.History) string {
|
||||||
|
if len(insts) <= 0 {
|
||||||
|
return "Sin cambios registrados en este intervalo\nPrueba a ampliar el rango del tiempo deseado."
|
||||||
|
}
|
||||||
|
name := insts[0].Institution.Name
|
||||||
|
resultText := fmt.Sprintf("%s\n\n", name)
|
||||||
|
|
||||||
|
for _, i := range insts {
|
||||||
|
date := time.Unix(i.Parsed, 0)
|
||||||
|
loc, _ := time.LoadLocation("America/Santo_Domingo")
|
||||||
|
resultText = resultText + fmt.Sprintf(" %s\n Compra: %.2f Venta: %.2f\n", date.In(loc).Format(time.DateTime), i.Compra, i.Venta)
|
||||||
|
}
|
||||||
|
return resultText
|
||||||
|
}
|
||||||
114
internal/application/static/static.go
Normal file
114
internal/application/static/static.go
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
package static
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
|
"golang.org/x/text/runes"
|
||||||
|
"golang.org/x/text/transform"
|
||||||
|
"golang.org/x/text/unicode/norm"
|
||||||
|
)
|
||||||
|
|
||||||
|
var institutionList *instList
|
||||||
|
|
||||||
|
type Institution struct {
|
||||||
|
ID int64
|
||||||
|
Name string
|
||||||
|
ShortName string
|
||||||
|
}
|
||||||
|
type instList struct {
|
||||||
|
inst []*Institution
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewInstList() *instList {
|
||||||
|
return &instList{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i instList) GetName(name string) []*Institution {
|
||||||
|
inititate()
|
||||||
|
// strip banco dominicano asociacion de ahorros y prestamos
|
||||||
|
replacer := strings.NewReplacer(
|
||||||
|
"banco", "",
|
||||||
|
"dominicano", "",
|
||||||
|
"asociacion ", "",
|
||||||
|
"ahorros", "",
|
||||||
|
"prestamos", "",
|
||||||
|
" de ", "",
|
||||||
|
" y ", "",
|
||||||
|
" ", " ",
|
||||||
|
)
|
||||||
|
name = replacer.Replace(name)
|
||||||
|
list := []*Institution{}
|
||||||
|
if len(name) <= 1 {
|
||||||
|
return []*Institution{}
|
||||||
|
}
|
||||||
|
for _, inst := range institutionList.inst {
|
||||||
|
if strings.Contains(inst.Name, name) || strings.Contains(inst.ShortName, name) {
|
||||||
|
list = append(list, inst)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
func (i instList) GetAbbrev(name string) string {
|
||||||
|
inititate()
|
||||||
|
for _, i := range institutionList.inst {
|
||||||
|
if i.Name == name {
|
||||||
|
return i.ShortName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
func inititate() {
|
||||||
|
if institutionList == nil || len(institutionList.inst) <= 0 {
|
||||||
|
institutionList = &instList{
|
||||||
|
inst: []*Institution{
|
||||||
|
{ID: 1, Name: "banco central dominicano", ShortName: "bcd"},
|
||||||
|
{ID: 2, Name: "banco popular", ShortName: "bpd"},
|
||||||
|
{ID: 3, Name: "banco hipotecario dominicano", ShortName: "bhd"},
|
||||||
|
{ID: 4, Name: "banreservas", ShortName: "brd"},
|
||||||
|
{ID: 5, Name: "asociacion peravia de ahorros y prestamos", ShortName: "apeap"},
|
||||||
|
{ID: 6, Name: "asociacion popular de ahorros y prestamos", ShortName: "apap"},
|
||||||
|
{ID: 7, Name: "asociacion cibao de ahorros y prestamos", ShortName: "acap"},
|
||||||
|
{ID: 8, Name: "asociacion la nacional de ahorros y prestamos", ShortName: "alnap"},
|
||||||
|
{ID: 9, Name: "banco promerica", ShortName: "bpr"},
|
||||||
|
{ID: 10, Name: "banco bdi", ShortName: "bbd"},
|
||||||
|
{ID: 11, Name: "banco caribe", ShortName: "bca"},
|
||||||
|
{ID: 12, Name: "banco santa cruz", ShortName: "bsc"},
|
||||||
|
{ID: 13, Name: "banco vimenca", ShortName: "bvi"},
|
||||||
|
{ID: 14, Name: "scotiabank cambio online", ShortName: "scline"},
|
||||||
|
{ID: 15, Name: "scotiabank", ShortName: "scotiabank"},
|
||||||
|
{ID: 16, Name: "bonanza banco", ShortName: "bba"},
|
||||||
|
{ID: 17, Name: "banco atlantico", ShortName: "bat"},
|
||||||
|
{ID: 18, Name: "banco lopez de haro", ShortName: "blh"},
|
||||||
|
{ID: 19, Name: "banco ademi", ShortName: "bad"},
|
||||||
|
{ID: 20, Name: "banco lafise", ShortName: "bla"},
|
||||||
|
{ID: 21, Name: "banesco", ShortName: "banesco"},
|
||||||
|
{ID: 22, Name: "banco activo dominicana", ShortName: "bacd"},
|
||||||
|
{ID: 23, Name: "girosol", ShortName: "girosol"},
|
||||||
|
{ID: 24, Name: "moneycorps", ShortName: "moneycorps"},
|
||||||
|
{ID: 25, Name: "imbert y balbuena", ShortName: "imb"},
|
||||||
|
{ID: 26, Name: "rm", ShortName: "rm"},
|
||||||
|
{ID: 27, Name: "motor credito", ShortName: "mcr"},
|
||||||
|
{ID: 28, Name: "cambio extranjero", ShortName: "cex"},
|
||||||
|
{ID: 29, Name: "capla", ShortName: "capla"},
|
||||||
|
{ID: 30, Name: "taveras", ShortName: "taveras"},
|
||||||
|
{ID: 31, Name: "gamelin", ShortName: "gamelin"},
|
||||||
|
{ID: 32, Name: "sct", ShortName: "sct"},
|
||||||
|
{ID: 33, Name: "panora exchange", ShortName: "inf"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveAccent
|
||||||
|
// helps normalize names in db
|
||||||
|
// https://stackoverflow.com/questions/24588295/go-removing-accents-from-strings
|
||||||
|
func RemoveAccent(str string) string {
|
||||||
|
if str == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
t := transform.Chain(norm.NFD, runes.Remove(runes.In(unicode.Mn)), norm.NFC)
|
||||||
|
s, _, _ := transform.String(t, str)
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
13
internal/ports/dolar.go
Normal file
13
internal/ports/dolar.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package ports
|
||||||
|
|
||||||
|
import "github.com/maximotejeda/us_dop_bot/internal/application/domain"
|
||||||
|
|
||||||
|
type DolarService interface {
|
||||||
|
GetLatest(name string) (*domain.History, error)
|
||||||
|
GetSince(name string, duration int64) ([]*domain.History, error)
|
||||||
|
GetInstByType(name string) ([]string, error)
|
||||||
|
Subscribe(int64, string) (bool, error)
|
||||||
|
Unsubscribe(int64, string) (bool, error)
|
||||||
|
GetSubscribedUsers(string) ([]int64, error)
|
||||||
|
GetSubscribedInsts(int64) ([]string, error)
|
||||||
|
}
|
||||||
7
internal/ports/tgb.go
Normal file
7
internal/ports/tgb.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package ports
|
||||||
|
|
||||||
|
type Tgb interface {
|
||||||
|
Send()
|
||||||
|
Empty()
|
||||||
|
Handler()
|
||||||
|
}
|
||||||
17
internal/ports/user.go
Normal file
17
internal/ports/user.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package ports
|
||||||
|
|
||||||
|
import (
|
||||||
|
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||||
|
"github.com/maximotejeda/us_dop_bot/internal/application/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserService interface {
|
||||||
|
Get(int64) (*domain.User, error)
|
||||||
|
Edit(*tgbotapi.User) (bool, error)
|
||||||
|
Delete(int64) (bool, error)
|
||||||
|
Create(*tgbotapi.User) (bool, error)
|
||||||
|
AddBot(int64, string) (bool, error)
|
||||||
|
GetBots(int64) ([]string, error)
|
||||||
|
DeleteBot(int64, string) (bool, error)
|
||||||
|
GetAllBotsUsers(string) ([]*domain.User, error)
|
||||||
|
}
|
||||||
@ -19,18 +19,15 @@ spec:
|
|||||||
- name: us-dop-bot
|
- name: us-dop-bot
|
||||||
image: localhost:32000/us-dop-bot:latest
|
image: localhost:32000/us-dop-bot:latest
|
||||||
env:
|
env:
|
||||||
- name: DBURINST
|
- name: DBURI
|
||||||
value: $DBURINST
|
value: $DBURI
|
||||||
- name: DBURIUSER
|
|
||||||
value: $DBURIUSER
|
|
||||||
- name: NATSURI
|
- name: NATSURI
|
||||||
value: "nats://nats-svc:4222"
|
value: "nats://nats-svc:4222"
|
||||||
- name: TOKEN
|
- name: TOKEN
|
||||||
value: "$TOKEN"
|
value: "$PRODTOKEN"
|
||||||
volumeMounts:
|
- name: DOLLAR_SERVICE_URL
|
||||||
- name: database
|
value: "dolar-grpc-svc:80"
|
||||||
mountPath: /app/dolardb
|
- name: TGBUSER_SERVICE_URL
|
||||||
volumes:
|
value: "tgbuser-grpc-svc:80"
|
||||||
- name: database
|
- name: ENV
|
||||||
persistentVolumeClaim:
|
value: "production"
|
||||||
claimName: bank-crawler-pvc
|
|
||||||
|
|||||||
@ -1,13 +0,0 @@
|
|||||||
package models
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Institucion struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Compra float64 `json:"compra"`
|
|
||||||
Venta float64 `json:"venta"`
|
|
||||||
Parser string `json:"parser"`
|
|
||||||
Parsed time.Time `json:"parsed"`
|
|
||||||
}
|
|
||||||
152
query/query.go
152
query/query.go
@ -1,152 +0,0 @@
|
|||||||
package query
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log/slog"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
tg "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
|
||||||
"github.com/maximotejeda/us_dop_bot/db"
|
|
||||||
edb "github.com/maximotejeda/us_dop_bot/edb"
|
|
||||||
"github.com/maximotejeda/us_dop_bot/helpers"
|
|
||||||
"github.com/maximotejeda/us_dop_bot/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
// QueryHandler
|
|
||||||
// Manage queries to execute user commands
|
|
||||||
func QueryHandler(ctx context.Context, dbx *db.DB, inst *edb.DB, log *slog.Logger, query *tg.CallbackQuery) (msg *tg.MessageConfig) {
|
|
||||||
tUser := query.From
|
|
||||||
user := db.NewUser(dbx, log)
|
|
||||||
_, err := user.Get(tUser.ID)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("callback", "error", err)
|
|
||||||
}
|
|
||||||
data := query.Data
|
|
||||||
|
|
||||||
dataList := strings.Split(data, "&")
|
|
||||||
|
|
||||||
dataMap := map[string]string{}
|
|
||||||
for _, val := range dataList {
|
|
||||||
subData := strings.Split(val, "=")
|
|
||||||
dataMap[subData[0]] = subData[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case dataMap["subs"] != "":
|
|
||||||
err := user.Subscribe(dataMap["name"])
|
|
||||||
if err != nil {
|
|
||||||
log.Error("subs-query", "error", err.Error(), "user", user)
|
|
||||||
}
|
|
||||||
case dataMap["unsubs"] != "":
|
|
||||||
user.Get(tUser.ID)
|
|
||||||
err := user.Unsubscribe(dataMap["name"])
|
|
||||||
if err != nil {
|
|
||||||
log.Error("unsubs-query", "error", err.Error())
|
|
||||||
}
|
|
||||||
case dataMap["reset"] != "":
|
|
||||||
err := user.Reset()
|
|
||||||
if err != nil {
|
|
||||||
log.Error("[query reset] ", "error", err)
|
|
||||||
}
|
|
||||||
case dataMap["consultar"] != "":
|
|
||||||
name := "&name=" + dataMap["name"]
|
|
||||||
queryMap := map[string]string{
|
|
||||||
"Actual": "query=true&time=0&unit=now" + name,
|
|
||||||
"30 Minutos": "query=true&time=30&unit=minute" + name,
|
|
||||||
"1 Hora": "query=true&time=1&unit=hour" + name,
|
|
||||||
"6 Horas": "query=true&time=6&unit=hour" + name,
|
|
||||||
"12 Horas": "query=true&time=12&unit=hour" + name,
|
|
||||||
"1 Dia": "query=true&time=24&unit=hour" + name,
|
|
||||||
"1 Semana": "query=true&time=168&unit=hour" + name,
|
|
||||||
"2 Semanas": "query=true&time=336&unit=hour" + name,
|
|
||||||
"1 Mes": "query=true&time=672&unit=hour" + name,
|
|
||||||
}
|
|
||||||
keyboard := helpers.CreateKeyboard(queryMap)
|
|
||||||
msg = &tg.MessageConfig{}
|
|
||||||
msg.ChatID = tUser.ID
|
|
||||||
msg.ReplyMarkup = keyboard
|
|
||||||
msg.Text = fmt.Sprintf("Intervalos de tiempo disponibles para consulta en %s el cambio del dolar desde hace:", dataMap["name"])
|
|
||||||
case dataMap["query"] != "":
|
|
||||||
var timeUnit time.Duration
|
|
||||||
timeAmntSTR := dataMap["time"]
|
|
||||||
timeUnitSTR := dataMap["unit"]
|
|
||||||
name := dataMap["name"]
|
|
||||||
timeAmnt, err := strconv.Atoi(timeAmntSTR)
|
|
||||||
switch name {
|
|
||||||
case "apap":
|
|
||||||
name = "asociacion popular de ahorros y prestamos"
|
|
||||||
case "acap":
|
|
||||||
name = "asociacion cibao de ahorros y prestamos"
|
|
||||||
case "anap":
|
|
||||||
name = "asociacion la nacional de ahorros y prestamos"
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
log.Error("[query-query] converting amount of time to int", "error", err)
|
|
||||||
return msg
|
|
||||||
}
|
|
||||||
switch timeUnitSTR {
|
|
||||||
case "minute":
|
|
||||||
timeUnit = time.Minute * time.Duration(timeAmnt)
|
|
||||||
instList, err := inst.GetChangeSince(name, timeUnit)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("[GETLIST] querying the inst database minutes", "error", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
queryMap := map[string]string{"clear": "cancel=true"}
|
|
||||||
keyboard := helpers.CreateKeyboard(queryMap)
|
|
||||||
msg = &tg.MessageConfig{}
|
|
||||||
msg.ChatID = tUser.ID
|
|
||||||
msg.ReplyMarkup = keyboard
|
|
||||||
|
|
||||||
msg.Text = instMessage(instList)
|
|
||||||
|
|
||||||
case "hour":
|
|
||||||
timeUnit = time.Hour * time.Duration(timeAmnt)
|
|
||||||
instList, err := inst.GetChangeSince(name, timeUnit)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("[GETLIST] querying the inst database hours", "error", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
queryMap := map[string]string{"clear": "cancel=true"}
|
|
||||||
keyboard := helpers.CreateKeyboard(queryMap)
|
|
||||||
msg = &tg.MessageConfig{}
|
|
||||||
msg.ChatID = tUser.ID
|
|
||||||
msg.ReplyMarkup = keyboard
|
|
||||||
|
|
||||||
msg.Text = instMessage(instList)
|
|
||||||
case "now":
|
|
||||||
instRes, err := inst.GetLastPrice(name)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("queriing the inst database now", "error", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
queryMap := map[string]string{"clear": "cancel=true"}
|
|
||||||
keyboard := helpers.CreateKeyboard(queryMap)
|
|
||||||
msg = &tg.MessageConfig{}
|
|
||||||
msg.ChatID = tUser.ID
|
|
||||||
|
|
||||||
msg.ReplyMarkup = keyboard
|
|
||||||
msg.Text = fmt.Sprintf("%s\nCompra: %.2f\nVenta: %.2f", instRes.Name, instRes.Compra, instRes.Venta)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
//log.Info("", "time unit", timeUnit, "timeAmount", timeAmnt)
|
|
||||||
|
|
||||||
}
|
|
||||||
return msg
|
|
||||||
}
|
|
||||||
|
|
||||||
func instMessage(insts []*models.Institucion) string {
|
|
||||||
if len(insts) <= 0 {
|
|
||||||
return "Sin cambios registrados en este intervalo\nPrueba a ampliar el rango del tiempo deseado."
|
|
||||||
}
|
|
||||||
name := insts[0].Name
|
|
||||||
resultText := fmt.Sprintf("%s\n\n", name)
|
|
||||||
for _, i := range insts {
|
|
||||||
resultText = resultText + fmt.Sprintf(" %s\n Compra: %.2f Venta: %.2f\n", i.Parsed.Format(time.DateTime), i.Compra, i.Venta)
|
|
||||||
}
|
|
||||||
return resultText
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user