This commit is contained in:
user-penguin 2024-11-12 02:32:06 +07:00
commit 077437d33c
18 changed files with 432 additions and 0 deletions

2
.env.example Normal file
View File

@ -0,0 +1,2 @@
BOT_TOKEN=PLEASE_ADD_YOU_TOKEN_TO_DOT_ENV_FILE
POSTGRES_URL_CONNECT=postgresql://user:password@hos:port/db?sslmode=disable

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
.env
/Dockerfile
/docker-compose.yml
__debug*

11
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,11 @@
{
"configurations": [
{
"name": "Launch smm-tg",
"type": "go",
"request": "launch",
"mode": "debug",
"program": "${workspaceFolder}/cmd/smm_tg"
}
]
}

53
Makefile Normal file
View File

@ -0,0 +1,53 @@
LOCAL_BIN := $(CURDIR)/bin
PATH := $(LOCAL_BIN):$(PATH)
GO_IMAGE_VER := 1.23.3
ifeq (local-migrations-create,$(firstword $(MAKECMDGOALS)))
migrationName := $(wordlist 2,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS))
$(eval $(migrationName):;@:)
endif
local-migrate-up:
@goose -allow-missing -dir ./migrations postgres "user=crab dbname=smm-core host=localhost port=5432 sslmode=disable" up
local-migrations-create:
@goose -dir ./migrations postgres "user=crab dbname=smm-core host=localhost port=5432 sslmode=disable" create "${migrationName}" sql
local-init-arm: local-init-common
@if [ ! -f Dockerfile ]; then \
echo "Creating Dockerfile file..."; \
echo "FROM golang:1.23.3-alpine3.19 as builder" >> Dockerfile; \
echo "WORKDIR /app" >> Dockerfile; \
echo "COPY . ." >> Dockerfile; \
echo "ENV GOOS=linux" >> Dockerfile; \
echo "ENV GOARCH=arm" >> Dockerfile; \
echo "RUN go build -o smm_tg cmd/smm_tg/main.go" >> Dockerfile; \
echo "FROM alpine:3.19" >> Dockerfile; \
echo "COPY --from=builder /app/smm_tg /usr/local/bin/smm_tg" >> Dockerfile; \
echo "RUN chmod +x /usr/local/bin/smm_tg" >> Dockerfile; \
echo "CMD [\"smm_tg\"]" >> Dockerfile; \
echo "Dockerfile file created"; \
else \
echo "Dockerfile file already exists"; \
fi
local-init-amd64: local-init-common
local-init-common:
@if [ ! -f .env ]; then \
echo "Creating .env file..."; \
echo "BOT_TOKEN=" >> .env; \
echo "POSTGRES_URL_CONNECT=" >> .env; \
echo ".env file created"; \
else \
echo ".env file already exists"; \
fi
@if [ ! -f docker-compose.yml ]; then \
echo "Creating docker-compose.yml file..."; \
cp build/ci/docker-compose.yml .; \
echo "docker-compose.yml file created"; \
else \
echo "docker-compose.yml file already exists"; \
fi

22
README.md Normal file
View File

@ -0,0 +1,22 @@
# Save My Money Telegram Bot
телеграм бот сервиса-помощника, создающегося для контроля за расходами и накоплениями
```yaml
- кто мы?
- работяги
- чего мы хотим?
- меньше тратить денег на хуйню!
```
# Для пользователей
1. Найти бота __@samymoney_bot__
2. Начать контролировать денежные потоки и дыхание вселенной
# Для девелоперов
1. Выполнить
```bash
make local-init
```
2. Создать себе тестового бота и положить его токен в __.env__ под ключ __BOT_TOKEN__
3. Писать только хороший код
4. Плохой код не писать

79
build/ci/.drone.yml Normal file
View File

@ -0,0 +1,79 @@
kind: pipeline
type: docker
name: default
steps:
- name: build
pull: if-not-exists
image: golang:1.23
environment:
TELEGRAM_TOKEN:
from_secret: bot_token
POSTGRES_URL_CONNECT:
from_secret: postgres_url_connect
settings:
envs: [ TELEGRAM_TOKEN,POSTGRES_URL_CONNECT ]
commands:
- CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o smm_tg cmd/smm_tg/main.go
- touch .env
- echo TELEGRAM_TOKEN=$${TELEGRAM_TOKEN} >> .env
- echo POSTGRES_URL_CONNECT=$${POSTGRES_URL_CONNECT} >> .env
- name: test
pull: if-not-exists
image: golang:1.23
commands:
- go test ./...
- name: copy to server
pull: if-not-exists
image: appleboy/drone-scp
settings:
host:
from_secret: ssh_ip
username:
from_secret: ssh_user
key:
from_secret: ssh_key
port:
from_secret: ssh_port
target: /home/crab/deploys/butler_bot
source:
- .env
- build/ci/Dockerfile
- deploy/docker-compose.yml
- migrations
- smm_tg
rm: true
- name: deploy
pull: if-not-exists
image: appleboy/drone-ssh
environment:
DB_PASS:
from_secret: db_pass
settings:
host:
from_secret: ssh_ip
username:
from_secret: ssh_user
key:
from_secret: ssh_key
port:
from_secret: ssh_port
envs: [ DB_PASS ]
script:
- cd /home/crab/deploys/smm_tg
- cp /etc/ssl/certs/ca-certificates.crt .
- mv build/ci/Dockerfile .
- mv deploy/docker-compose.yml .
- rm -r build deploy
- export PATH=$PATH:/usr/local/go/bin:/$HOME/go/bin
- goose -allow-missing -dir ./migrations postgres "user=crab password=$${DB_PASS} dbname=smm_tg host=localhost port=5432 sslmode=disable" up
- docker-compose up -d --force-recreate
trigger:
branch:
- master
event:
- push

4
build/ci/Dockerfile Normal file
View File

@ -0,0 +1,4 @@
FROM golang:1.23.3-alpine3.19
COPY smm_tg /usr/local/bin/smm_tg
RUN chmod +x /usr/local/bin/smm_tg
CMD ["smm_tg"]

View File

@ -0,0 +1,15 @@
version: '3.8'
services:
smm_tg:
build:
context: .
dockerfile: Dockerfile
ports:
- "8185:8090"
networks:
- common-network
networks:
common-network:
external: true

40
cmd/smm_tg/main.go Normal file
View File

@ -0,0 +1,40 @@
package main
import (
"context"
"os"
"git.3crabs.ru/save_my_money/smm_tg/internal/modules/messenger/telegram"
"git.3crabs.ru/save_my_money/smm_tg/internal/services/bot"
"git.3crabs.ru/save_my_money/smm_tg/internal/services/bot/bot_ping"
"git.3crabs.ru/save_my_money/smm_tg/internal/services/listener"
"github.com/joho/godotenv"
)
func main() {
// try get sysenv
telegarmToken := os.Getenv("BOT_TOKEN")
if len(telegarmToken) == 0 {
if err := godotenv.Load("../../.env"); err != nil {
panic(err)
}
telegarmToken = os.Getenv("BOT_TOKEN")
}
if len(telegarmToken) == 0 {
panic("empty bot token")
}
messengerTelegram, err := telegram.NewMessengerTelegram(telegarmToken)
if err != nil {
panic(err)
}
listenerService := listener.ListenerService{
Messenger: messengerTelegram,
Bots: []bot.IBot{
bot_ping.NewBotPing(messengerTelegram),
},
}
ctx := context.Background()
listenerService.Run(ctx)
}

15
go.mod Normal file
View File

@ -0,0 +1,15 @@
module git.3crabs.ru/save_my_money/smm_tg
go 1.23.3
require (
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
github.com/joho/godotenv v1.5.1
github.com/lib/pq v1.10.9
github.com/samber/lo v1.47.0
)
require (
go.uber.org/mock v0.5.0 // indirect
golang.org/x/text v0.16.0 // indirect
)

12
go.sum Normal file
View File

@ -0,0 +1,12 @@
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/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc=
github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=

View File

@ -0,0 +1,6 @@
package entities
type User struct {
ChatID string `sql:"chat_id"`
UserID string `sql:"user_id"`
}

View File

@ -0,0 +1,16 @@
package messenger
//go:generate mockgen -source=$GOFILE -destination=mocks/$GOFILE -package=mocks
import "context"
type Message struct {
ChatID string
UserID string
Text string
}
type IMessenger interface {
GetMessage(ctx context.Context) (*Message, error)
SendMessage(ctx context.Context, msg *Message) error
}

View File

@ -0,0 +1,64 @@
package telegram
import (
"context"
"fmt"
"log"
"strconv"
"git.3crabs.ru/save_my_money/smm_tg/internal/modules/messenger"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)
type messengerTelegram struct {
bot *tgbotapi.BotAPI
updates tgbotapi.UpdatesChannel
}
func NewMessengerTelegram(token string) (messenger.IMessenger, error) {
bot, err := tgbotapi.NewBotAPI(token)
if err != nil {
return nil, err
}
log.Printf("Authorized on account %s", bot.Self.UserName)
u := tgbotapi.NewUpdate(0)
u.Timeout = 60
updates := bot.GetUpdatesChan(u)
return &messengerTelegram{
bot: bot,
updates: updates,
}, nil
}
func (m *messengerTelegram) GetMessage(ctx context.Context) (*messenger.Message, error) {
tgMsg := <-m.updates
log.Printf("get grom tg: %+v", tgMsg)
chatID := int64(0)
if tgMsg.Message != nil && tgMsg.Message.Chat != nil {
chatID = tgMsg.Message.Chat.ID
}
if chatID == 0 {
return nil, fmt.Errorf("get chat id error %+v", tgMsg)
}
msg := &messenger.Message{
ChatID: fmt.Sprintf("%d", chatID),
UserID: tgMsg.Message.From.UserName,
Text: tgMsg.Message.Text,
}
log.Println("get:", msg)
return msg, nil
}
func (m *messengerTelegram) SendMessage(ctx context.Context, msg *messenger.Message) error {
chatID, err := strconv.ParseInt(msg.ChatID, 10, 64)
if err != nil {
return err
}
tgMsg := tgbotapi.NewMessage(chatID, msg.Text)
if _, err = m.bot.Send(tgMsg); err != nil {
return err
}
log.Println("send:", msg)
return nil
}

View File

@ -0,0 +1,35 @@
package bot_ping
import (
"context"
"strings"
"git.3crabs.ru/save_my_money/smm_tg/internal/modules/messenger"
"git.3crabs.ru/save_my_money/smm_tg/internal/services/bot"
)
type botPing struct {
messenger messenger.IMessenger
}
func NewBotPing(
messenger messenger.IMessenger,
) bot.IBot {
return &botPing{
messenger: messenger,
}
}
func (bot *botPing) Process(ctx context.Context, msg *messenger.Message) error {
if !strings.Contains(msg.Text, "/ping") {
return nil
}
bot.messenger.SendMessage(
ctx,
&messenger.Message{
ChatID: msg.ChatID,
Text: "pong",
},
)
return nil
}

View File

@ -0,0 +1,11 @@
package bot
import (
"context"
"git.3crabs.ru/save_my_money/smm_tg/internal/modules/messenger"
)
type IBot interface {
Process(ctx context.Context, msg *messenger.Message) error
}

View File

@ -0,0 +1,31 @@
package listener
import (
"context"
"log"
"git.3crabs.ru/save_my_money/smm_tg/internal/modules/messenger"
"git.3crabs.ru/save_my_money/smm_tg/internal/services/bot"
)
type ListenerService struct {
Messenger messenger.IMessenger
Bots []bot.IBot
}
func (s *ListenerService) Run(ctx context.Context) error {
for {
msg, err := s.Messenger.GetMessage(ctx)
if err != nil {
if err == context.Canceled {
return nil
}
return err
}
for _, b := range s.Bots {
if err := b.Process(ctx, msg); err != nil {
log.Println(err)
}
}
}
}

View File

@ -0,0 +1,12 @@
-- +goose Up
-- +goose StatementBegin
CREATE TABLE IF NOT EXISTS "user" (
id SERIAL PRIMARY KEY,
chat_id TEXT NOT NULL
);
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
DROP TABLE IF EXISTS "user";
-- +goose StatementEnd