Compare commits
10 Commits
e6258f9de2
...
ceb402a65d
| Author | SHA1 | Date | |
|---|---|---|---|
| ceb402a65d | |||
| 41b4cee5c4 | |||
| eb3055e7aa | |||
| 93da47c688 | |||
| 72c2bd05fa | |||
| 74f881ac9c | |||
| 13232710bb | |||
| ceb057ebea | |||
| 90097a5a5b | |||
| 62ffd4c09b |
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 }}."
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,2 +1,5 @@
|
|||||||
.env
|
.env
|
||||||
k8s/deployment.yml
|
k8s/deployment.yml
|
||||||
|
bin/
|
||||||
|
dolardb/
|
||||||
|
|
||||||
|
|||||||
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/.
|
||||||
|
|||||||
148
cmd/bot/main.go
Normal file
148
cmd/bot/main.go
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"runtime"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||||
|
"github.com/maximotejeda/us_dop_bot/config"
|
||||||
|
"github.com/maximotejeda/us_dop_bot/internal/adapters/dolar"
|
||||||
|
"github.com/maximotejeda/us_dop_bot/internal/adapters/user"
|
||||||
|
"github.com/maximotejeda/us_dop_bot/internal/application/api"
|
||||||
|
"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"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
maxWorkers = runtime.GOMAXPROCS(0)
|
||||||
|
sem = semaphore.NewWeighted(int64(maxWorkers) * 2)
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
log := slog.New(slog.NewJSONHandler(os.Stderr, nil))
|
||||||
|
log = log.With("location", "main")
|
||||||
|
nc, _ := nats.Connect(config.GetNatsURI())
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
bot, err := tgbotapi.NewBotAPI(config.GetToken())
|
||||||
|
if err != nil {
|
||||||
|
log.Error("token not found", "error", err)
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
botName := bot.Self.UserName
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
// bot user update channel
|
||||||
|
updtChan := bot.GetUpdatesChan(u)
|
||||||
|
// subs chann
|
||||||
|
changeChan := make(chan *nats.Msg, 64)
|
||||||
|
broadcastChan := make(chan *nats.Msg, 64)
|
||||||
|
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 {
|
||||||
|
log.Error("subscribing", "error", err.Error())
|
||||||
|
}
|
||||||
|
defer sub.Drain()
|
||||||
|
defer info.Drain()
|
||||||
|
defer nc.Close()
|
||||||
|
|
||||||
|
// exit channel
|
||||||
|
sign := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sign, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
defer close(sign)
|
||||||
|
app := api.NewApi(bot)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case update := <-updtChan:
|
||||||
|
if err = sem.Acquire(ctx, 1); err != nil {
|
||||||
|
bot.Send(tgbotapi.NewMessage(update.FromChat().ID, "error adquiring update"))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
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()
|
||||||
|
|
||||||
|
for _, msg := range userList {
|
||||||
|
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:
|
||||||
|
log.Error("killing app due to syscall ")
|
||||||
|
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
|
||||||
|
}
|
||||||
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.
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
|
||||||
|
}
|
||||||
26
internal/application/helpers/helpers.go
Normal file
26
internal/application/helpers/helpers.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package helpers
|
||||||
|
|
||||||
|
import tgb "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||||
|
|
||||||
|
// CreateKeyboard
|
||||||
|
// create keybowrds of two rows of any map[string]string input
|
||||||
|
func CreateKeyboard(data map[string]string) tgb.InlineKeyboardMarkup {
|
||||||
|
// hardcoded models
|
||||||
|
keyboard := tgb.NewInlineKeyboardMarkup()
|
||||||
|
// subbuttons := []tgbot.InlineKeyboardButton{}
|
||||||
|
rows := tgb.NewInlineKeyboardRow()
|
||||||
|
counter := 0
|
||||||
|
for key, val := range data {
|
||||||
|
|
||||||
|
if counter != 0 && counter%3 == 0 {
|
||||||
|
keyboard.InlineKeyboard = append(keyboard.InlineKeyboard, rows)
|
||||||
|
rows = tgb.NewInlineKeyboardRow()
|
||||||
|
}
|
||||||
|
rows = append(rows, tgb.NewInlineKeyboardButtonData(key, val))
|
||||||
|
if counter >= len(data)-1 {
|
||||||
|
keyboard.InlineKeyboard = append(keyboard.InlineKeyboard, rows)
|
||||||
|
}
|
||||||
|
counter++
|
||||||
|
}
|
||||||
|
return keyboard
|
||||||
|
}
|
||||||
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)
|
||||||
|
}
|
||||||
33
k8s/deployment.yml.template
Normal file
33
k8s/deployment.yml.template
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: us-dop-bot
|
||||||
|
labels:
|
||||||
|
app: us-dop-bot
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: us-dop-bot
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: us-dop-bot
|
||||||
|
name: us-dop-bot
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: us-dop-bot
|
||||||
|
image: localhost:32000/us-dop-bot:latest
|
||||||
|
env:
|
||||||
|
- name: DBURI
|
||||||
|
value: $DBURI
|
||||||
|
- name: NATSURI
|
||||||
|
value: "nats://nats-svc:4222"
|
||||||
|
- name: TOKEN
|
||||||
|
value: "$PRODTOKEN"
|
||||||
|
- name: DOLLAR_SERVICE_URL
|
||||||
|
value: "dolar-grpc-svc:80"
|
||||||
|
- name: TGBUSER_SERVICE_URL
|
||||||
|
value: "tgbuser-grpc-svc:80"
|
||||||
|
- name: ENV
|
||||||
|
value: "production"
|
||||||
Loading…
x
Reference in New Issue
Block a user