Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Store subscribed users in json file based database #14

Merged
merged 3 commits into from
May 14, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 21 additions & 10 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ package cmd

import (
"os"
"path/filepath"

"github.com/codescalers/statusbot/internal"
"github.com/codescalers/statusbot/internal/bot"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
Expand All @@ -20,26 +21,35 @@ var rootCmd = &cobra.Command{

token, err := cmd.Flags().GetString("bot-token")
if err != nil || token == "" {
log.Error().Err(err).Msg("error in token")
return
log.Fatal().Err(err).Msg("error in token")
}

time, err := cmd.Flags().GetString("time")
if err != nil || time == "" {
log.Error().Err(err).Msg("error in time")
return
log.Fatal().Err(err).Msg("error in time")
}

timezone, err := cmd.Flags().GetString("timezone")
if err != nil || timezone == "" {
log.Error().Err(err).Msg("error in timezone")
return
log.Fatal().Err(err).Msg("error in timezone")
}

bot, err := internal.NewBot(token, time, timezone)
db, err := cmd.Flags().GetString("database")
if err != nil {
log.Error().Err(err).Msg("failed to create bot")
return
log.Fatal().Err(err).Msg("error in database")
}

if db == "" {
defaultPath, err := os.UserHomeDir()
if err != nil {
log.Fatal().Err(err).Send()
}
db = filepath.Join(defaultPath, ".statusbot")
}

bot, err := internal.NewBot(token, time, timezone, db)
if err != nil {
log.Fatal().Err(err).Msg("failed to create bot")
}

bot.Start()
Expand All @@ -57,4 +67,5 @@ func init() {
rootCmd.Flags().StringP("bot-token", "b", "", "Enter a valid telegram bot token")
rootCmd.Flags().StringP("time", "t", "17:00", "Enter a valid time")
rootCmd.Flags().StringP("timezone", "z", "Africa/Cairo", "Enter a valid timezone")
rootCmd.Flags().StringP("database", "d", "", "Enter path to store chats info")
}
26 changes: 20 additions & 6 deletions internal/bot.go → internal/bot/bot.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"time"
_ "time/tzdata"

database "github.com/codescalers/statusbot/internal/db"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
"github.com/rs/zerolog/log"
)
Expand All @@ -18,10 +19,11 @@ type Bot struct {
removeChan chan int64
time time.Time
location *time.Location
db database.DB
}

// NewBot creates new bot with a valid bot api and communication channels
func NewBot(token string, inputTime string, timezone string) (Bot, error) {
func NewBot(token, inputTime, timezone, dbPath string) (Bot, error) {
bot := Bot{}

botAPI, err := tgbotapi.NewBotAPI(token)
Expand All @@ -44,11 +46,17 @@ func NewBot(token string, inputTime string, timezone string) (Bot, error) {

log.Printf("notfications is set to %s", parsedTime)

db, err := database.NewDB(dbPath)
if err != nil {
return bot, err
}

bot.location = loc
bot.botAPI = *botAPI
bot.time = parsedTime
bot.addChan = make(chan int64)
bot.removeChan = make(chan int64)
bot.db = db

return bot, nil
}
Expand Down Expand Up @@ -88,7 +96,6 @@ func (bot Bot) Start() {
}

func (bot Bot) runBot() {
chatIDs := make(map[int64]bool)
weekends := []time.Weekday{time.Friday, time.Saturday}

// set ticker every day at 12:00 to update time with location in case of new changes in timezone.
Expand All @@ -98,10 +105,10 @@ func (bot Bot) runBot() {
for {
select {
case chatID := <-bot.addChan:
chatIDs[chatID] = true
bot.db.Update(chatID, database.ChatInfo{ChatID: chatID})

case chatID := <-bot.removeChan:
delete(chatIDs, chatID)
bot.db.Delete(chatID)

case <-updateTicker.C:
// parse the time with location again to make sure the timezone is always up to date
Expand All @@ -114,16 +121,23 @@ func (bot Bot) runBot() {
updateTicker.Reset(24 * time.Hour)

case <-reminderTicker.C:
chats := bot.db.List()

// skip weekends
if !slices.Contains(weekends, bot.time.Weekday()) {
for chatID := range chatIDs {
bot.sendReminder(chatID)
for _, chat := range chats {
bot.sendReminder(chat.ChatID)
}
}

bot.time = bot.time.AddDate(0, 0, 1)
reminderTicker.Reset(24 * time.Hour)
log.Printf("next notfications is set to %s", bot.time)
}

if err := bot.db.Save(); err != nil {
log.Fatal().Err(err).Msg("failed to save updates to db")
}
}
}

Expand Down
75 changes: 75 additions & 0 deletions internal/db/db.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package db

import (
"encoding/json"
"os"
"path/filepath"
)

type ChatInfo struct {
ChatID int64
}

type DB struct {
path string
chatsIDs map[int64]ChatInfo
}

// NewDB open and load db if exist, or create new one if not exist
func NewDB(path string) (DB, error) {
if err := os.MkdirAll(path, 0777); err != nil {
return DB{}, err
}

chatsIDs := make(map[int64]ChatInfo)
path = filepath.Join(path, "db.json")

data, err := os.ReadFile(path)
if os.IsNotExist(err) || len(data) == 0 {
return DB{
path: path,
chatsIDs: chatsIDs,
}, nil
}
if err != nil {
return DB{}, err
}

if err := json.Unmarshal(data, &chatsIDs); err != nil {
return DB{}, err
}

return DB{
path: path,
chatsIDs: chatsIDs,
}, nil
}

func (db *DB) Save() error {
file, err := json.MarshalIndent(db.chatsIDs, "", " ")
if err != nil {
return err
}

return os.WriteFile(db.path, file, 0777)
}

func (db *DB) Get(key int64) ChatInfo {
return db.chatsIDs[key]
}

func (db *DB) Update(key int64, value ChatInfo) {
db.chatsIDs[key] = value
}

func (db *DB) Delete(key int64) {
delete(db.chatsIDs, key)
}

func (db *DB) List() []ChatInfo {
chatsInfo := make([]ChatInfo, 0, len(db.chatsIDs))
for _, val := range db.chatsIDs {
chatsInfo = append(chatsInfo, val)
}
return chatsInfo
}
Loading