From 2c5d21df1162189e3c3396353ebf96e6f528f10b Mon Sep 17 00:00:00 2001 From: Yash Raj <68998269+SparkleYR@users.noreply.github.com> Date: Mon, 30 Jun 2025 15:41:51 +0530 Subject: [PATCH 1/8] Create birthday.go [SQL] --- core/sql/birthday.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 core/sql/birthday.go diff --git a/core/sql/birthday.go b/core/sql/birthday.go new file mode 100644 index 0000000..bd36a2c --- /dev/null +++ b/core/sql/birthday.go @@ -0,0 +1,27 @@ +package sql + +import ( + "log" +) +type Birthday struct { + JID string `gorm:"primary_key"` + Birthday string +} +func AddBirthday(jid, birthday string) { + b := &Birthday{JID: jid} + tx := SESSION.Begin() + tx.FirstOrCreate(b) + b.Birthday = birthday + if err := tx.Save(b).Error; err != nil { + log.Printf("Failed to save birthday: %v", err) + tx.Rollback() + return + } + tx.Commit() +} +func GetBirthdaysForDate(date string) []Birthday { + var birthdays []Birthday + SESSION.Where("birthday = ?", date).Find(&birthdays) + return birthdays +} +// assuming gorm's AutoMigrate should handle table creation From ef1068c4442a017383ec00d62d3386a2ceba71c1 Mon Sep 17 00:00:00 2001 From: Yash Raj <68998269+SparkleYR@users.noreply.github.com> Date: Mon, 30 Jun 2025 15:45:25 +0530 Subject: [PATCH 2/8] Create birthday.go [CORE] --- core/birthday.go | 130 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 core/birthday.go diff --git a/core/birthday.go b/core/birthday.go new file mode 100644 index 0000000..9f0e0f2 --- /dev/null +++ b/core/birthday.go @@ -0,0 +1,130 @@ +package core + +import ( + "fmt" + "strings" + + "github.com/lugvitc/steve/core/sql" + "github.com/lugvitc/steve/ext" + "github.com/lugvitc/steve/ext/context" + "github.com/lugvitc/steve/ext/handlers" + waLogger "github.com/lugvitc/steve/logger" + + "go.mau.fi/whatsmeow" +) + +func addNote(client *whatsmeow.Client, ctx *context.Context) error { + args := ctx.Message.Args() + if len(args) <= 1 { + return ext.EndGroups + } + msg := ctx.Message.Message + key := args[1] + var value string + if msg.Message.ExtendedTextMessage != nil && msg.Message.ExtendedTextMessage.ContextInfo != nil { + qmsg := msg.Message.ExtendedTextMessage.ContextInfo.QuotedMessage + switch { + case qmsg.Conversation != nil: + value = qmsg.GetConversation() + case qmsg.ExtendedTextMessage != nil: + value = qmsg.ExtendedTextMessage.GetText() + } + } else { + if len(args) == 2 { + return ext.EndGroups + } + value = strings.Join(args[2:], " ") + } + go sql.AddNote(strings.ToLower(key), value) + _, _ = reply(client, ctx.Message, fmt.Sprintf("Added Note ```%s```.", key)) + return ext.EndGroups +} + +func deleteNote(client *whatsmeow.Client, ctx *context.Context) error { + args := ctx.Message.Args() + if len(args) == 1 { + return ext.EndGroups + } + key := args[1] + if sql.DeleteNote(strings.ToLower(key)) { + _, _ = reply(client, ctx.Message, fmt.Sprintf("Successfully delete note '```%s```'.", key)) + } else { + _, _ = reply(client, ctx.Message, "Failed to delete that note!") + } + return ext.EndGroups +} + +func getNote(client *whatsmeow.Client, ctx *context.Context) error { + args := ctx.Message.Args() + if len(args) == 1 { + return ext.EndGroups + } + key := args[1] + value := sql.GetNote(strings.ToLower(key)).Value + if value == "" { + value = "```Note not found```" + } + _, _ = reply(client, ctx.Message, value) + return ext.EndGroups +} + +func getNoteHash(client *whatsmeow.Client, ctx *context.Context) error { + args := ctx.Message.Args() + if len(args) == 0 { + return nil + } + text := strings.ToLower(args[0]) + if !strings.HasPrefix(text, "#") { + return nil + } + text = text[1:] + value := sql.GetNote(text).Value + if value == "" { + return nil + } + _, _ = reply(client, ctx.Message, value) + return nil +} + +func listNotes(client *whatsmeow.Client, ctx *context.Context) error { + text := "*List of notes*:" + for _, note := range sql.GetNotes() { + text += fmt.Sprintf("\n- ```%s```", note.Name) + } + if text == "*List of notes*:" { + text = "No notes are present." + } + _, _ = reply(client, ctx.Message, text) + return ext.EndGroups +} + +func (*Module) LoadNote(dispatcher *ext.Dispatcher) { + ppLogger := LOGGER.Create("note") + defer ppLogger.Println("Loaded Note module") + dispatcher.AddHandler( + handlers.NewCommand("add", authorizedOnly(addNote), ppLogger.Create("add-cmd"). + ChangeLevel(waLogger.LevelInfo), + ).AddDescription("Add a note."), + ) + dispatcher.AddHandler( + handlers.NewCommand("clear", authorizedOnly(deleteNote), ppLogger.Create("clear-cmd"). + ChangeLevel(waLogger.LevelInfo), + ).AddDescription("Clear a note."), + ) + dispatcher.AddHandler( + handlers.NewCommand("get", getNote, ppLogger.Create("get-cmd"). + ChangeLevel(waLogger.LevelInfo), + ).AddDescription("Get a note from key."), + ) + dispatcher.AddHandler( + handlers.NewCommand("notes", listNotes, ppLogger.Create("notes"). + ChangeLevel(waLogger.LevelInfo), + ).AddDescription("Display keys of all saved notes."), + ) + dispatcher.AddHandlerToGroup( + handlers.NewMessage(getNoteHash, ppLogger.Create("get-hash"). + ChangeLevel(waLogger.LevelInfo), + ).AddDescription("Get a note using #key format."), + 1, + ) +} From 748b44237b657436a1cc911ccdcb68065fcf785a Mon Sep 17 00:00:00 2001 From: Yash Raj <68998269+SparkleYR@users.noreply.github.com> Date: Mon, 30 Jun 2025 15:51:05 +0530 Subject: [PATCH 3/8] assing the whatsmeow client variable to the core.Load function --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 2d19eb9..fbb78cb 100644 --- a/main.go +++ b/main.go @@ -45,7 +45,7 @@ func main() { core.LOGGER.Println("Created new disptacher") dispatcher.InitialiseProcessing(ctx, client) db := sql.LoadDB(core.LOGGER) - core.Load(dispatcher) + core.Load(dispatcher, client) if client.Store.ID == nil { // No ID stored, new login From 3b581cc1131ad63284108aa4fc91ed19db1324d8 Mon Sep 17 00:00:00 2001 From: Yash Raj <68998269+SparkleYR@users.noreply.github.com> Date: Mon, 30 Jun 2025 15:54:05 +0530 Subject: [PATCH 4/8] Update init.go --- core/init.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/core/init.go b/core/init.go index 41689f9..e56b74f 100644 --- a/core/init.go +++ b/core/init.go @@ -79,11 +79,18 @@ func authorizedOnlyMessages(callback handlers.Response) handlers.Response { } } -func Load(dispatcher *ext.Dispatcher) { +func Load(dispatcher *ext.Dispatcher, client *whatsmeow.Client) { defer LOGGER.Println("Loaded all modules") Type := reflect.TypeOf(&Module{}) Value := reflect.ValueOf(&Module{}) + for i := 0; i < Type.NumMethod(); i++ { - Type.Method(i).Func.Call([]reflect.Value{Value, reflect.ValueOf(dispatcher)}) + method := Type.Method(i) + numArgs := method.Type.NumIn() + if numArgs == 3 && method.Type.In(2) == reflect.TypeOf(client) { + method.Func.Call([]reflect.Value{Value, reflect.ValueOf(dispatcher), reflect.ValueOf(client)}) + } else if numArgs == 2 { + method.Func.Call([]reflect.Value{Value, reflect.ValueOf(dispatcher)}) + } } } From f07c24aa3039066a30cfe26e1447a64e708b9938 Mon Sep 17 00:00:00 2001 From: Yash Raj <68998269+SparkleYR@users.noreply.github.com> Date: Mon, 30 Jun 2025 16:57:30 +0530 Subject: [PATCH 5/8] Update birthday.go --- core/sql/birthday.go | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/core/sql/birthday.go b/core/sql/birthday.go index bd36a2c..0493b14 100644 --- a/core/sql/birthday.go +++ b/core/sql/birthday.go @@ -1,27 +1,29 @@ package sql -import ( - "log" -) type Birthday struct { - JID string `gorm:"primary_key"` - Birthday string -} -func AddBirthday(jid, birthday string) { - b := &Birthday{JID: jid} + UserID string `gorm:"primaryKey"` + Date string + ChatJID string +func SaveBirthday(userID, date, chatJID string) { + birthday := &Birthday{UserID: userID} tx := SESSION.Begin() - tx.FirstOrCreate(b) - b.Birthday = birthday - if err := tx.Save(b).Error; err != nil { - log.Printf("Failed to save birthday: %v", err) - tx.Rollback() - return - } + tx.FirstOrCreate(birthday) + birthday.Date = date + birthday.ChatJID = chatJID + tx.Save(birthday) tx.Commit() } -func GetBirthdaysForDate(date string) []Birthday { +func DeleteBirthday(userID string) bool { + birthday := &Birthday{UserID: userID} + return SESSION.Delete(birthday).RowsAffected != 0 +} +func GetTodaysBirthdays(todayDate string) []Birthday { + var birthdays []Birthday + SESSION.Where("date = ?", todayDate).Find(&birthdays) + return birthdays +} +func GetAllBirthdays() []Birthday { var birthdays []Birthday - SESSION.Where("birthday = ?", date).Find(&birthdays) + SESSION.Find(&birthdays) return birthdays } -// assuming gorm's AutoMigrate should handle table creation From d2de846133fb7e1da13d4bbd71e8d6a07aa16c5f Mon Sep 17 00:00:00 2001 From: Yash Raj <68998269+SparkleYR@users.noreply.github.com> Date: Mon, 30 Jun 2025 16:57:45 +0530 Subject: [PATCH 6/8] Update db.go --- core/sql/db.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/sql/db.go b/core/sql/db.go index 5552a73..6875509 100644 --- a/core/sql/db.go +++ b/core/sql/db.go @@ -30,7 +30,7 @@ func LoadDB(LOGGER *waLogger.Logger) *sqlOrig.DB { LOGGER.Println("Database connected") // Create tables if they don't exist - _ = SESSION.AutoMigrate(&Note{}, &Filter{}, &ChatSettings{}, &Message{}) + _ = SESSION.AutoMigrate(&Note{}, &Filter{}, &ChatSettings{}, &Message{},&Birthday{}) LOGGER.Println("Auto-migrated database schema") return dB } From 5421630966674b0c03e93f6290c5da4e6ef32cb1 Mon Sep 17 00:00:00 2001 From: Yash Raj <68998269+SparkleYR@users.noreply.github.com> Date: Mon, 30 Jun 2025 16:59:04 +0530 Subject: [PATCH 7/8] Update birthday.go --- core/birthday.go | 187 +++++++++++++++++++++++++---------------------- 1 file changed, 99 insertions(+), 88 deletions(-) diff --git a/core/birthday.go b/core/birthday.go index 9f0e0f2..db22313 100644 --- a/core/birthday.go +++ b/core/birthday.go @@ -1,130 +1,141 @@ package core import ( + "context" "fmt" + "regexp" "strings" + "time" "github.com/lugvitc/steve/core/sql" "github.com/lugvitc/steve/ext" "github.com/lugvitc/steve/ext/context" "github.com/lugvitc/steve/ext/handlers" - waLogger "github.com/lugvitc/steve/logger" - + "github.com/lugvitc/steve/logger" "go.mau.fi/whatsmeow" + "go.mau.fi/whatsmeow/proto/waE2E" + "go.mau.fi/whatsmeow/types" + "google.golang.org/protobuf/proto" ) - -func addNote(client *whatsmeow.Client, ctx *context.Context) error { +func saveBirthday(client *whatsmeow.Client, ctx *context.Context) error { args := ctx.Message.Args() - if len(args) <= 1 { + if len(args) < 2 { + _, _ = reply(client, ctx.Message, "Please provide a date. Usage: `.savebirthday DD/MM @user`") return ext.EndGroups } - msg := ctx.Message.Message - key := args[1] - var value string - if msg.Message.ExtendedTextMessage != nil && msg.Message.ExtendedTextMessage.ContextInfo != nil { - qmsg := msg.Message.ExtendedTextMessage.ContextInfo.QuotedMessage - switch { - case qmsg.Conversation != nil: - value = qmsg.GetConversation() - case qmsg.ExtendedTextMessage != nil: - value = qmsg.ExtendedTextMessage.GetText() - } - } else { - if len(args) == 2 { - return ext.EndGroups - } - value = strings.Join(args[2:], " ") + + dateStr := args[1] + match, _ := regexp.MatchString(`^(0[1-9]|[12][0-9]|3[01])/(0[1-9]|1[0-2])$`, dateStr) + if !match { + _, _ = reply(client, ctx.Message, "Invalid date format. Please use `DD/MM`.") + return ext.EndGroups } - go sql.AddNote(strings.ToLower(key), value) - _, _ = reply(client, ctx.Message, fmt.Sprintf("Added Note ```%s```.", key)) - return ext.EndGroups -} -func deleteNote(client *whatsmeow.Client, ctx *context.Context) error { - args := ctx.Message.Args() - if len(args) == 1 { + var mentionedJIDs []string + if ctx.Message.Message.Message.ExtendedTextMessage != nil { + mentionedJIDs = ctx.Message.Message.Message.ExtendedTextMessage.ContextInfo.GetMentionedJID() + } + + if len(mentionedJIDs) == 0 { + _, _ = reply(client, ctx.Message, "Please mention a user to save their birthday.") return ext.EndGroups } - key := args[1] - if sql.DeleteNote(strings.ToLower(key)) { - _, _ = reply(client, ctx.Message, fmt.Sprintf("Successfully delete note '```%s```'.", key)) - } else { - _, _ = reply(client, ctx.Message, "Failed to delete that note!") + + for _, jidStr := range mentionedJIDs { + sql.SaveBirthday(jidStr, dateStr, ctx.Message.Info.Chat.String()) } + + _, _ = reply(client, ctx.Message, fmt.Sprintf("Birthday saved for %d user(s) on `%s`.", len(mentionedJIDs), dateStr)) return ext.EndGroups } +func deleteBirthday(client *whatsmeow.Client, ctx *context.Context) error { + var mentionedJIDs []string + if ctx.Message.Message.Message.ExtendedTextMessage != nil { + mentionedJIDs = ctx.Message.Message.Message.ExtendedTextMessage.ContextInfo.GetMentionedJID() + } -func getNote(client *whatsmeow.Client, ctx *context.Context) error { - args := ctx.Message.Args() - if len(args) == 1 { + if len(mentionedJIDs) == 0 { + _, _ = reply(client, ctx.Message, "Please mention a user to delete their birthday.") return ext.EndGroups } - key := args[1] - value := sql.GetNote(strings.ToLower(key)).Value - if value == "" { - value = "```Note not found```" + + deletedCount := 0 + for _, jidStr := range mentionedJIDs { + if sql.DeleteBirthday(jidStr) { + deletedCount++ + } } - _, _ = reply(client, ctx.Message, value) + + _, _ = reply(client, ctx.Message, fmt.Sprintf("Successfully deleted %d birthday(s).", deletedCount)) return ext.EndGroups } - -func getNoteHash(client *whatsmeow.Client, ctx *context.Context) error { - args := ctx.Message.Args() - if len(args) == 0 { - return nil - } - text := strings.ToLower(args[0]) - if !strings.HasPrefix(text, "#") { - return nil +func listBirthdays(client *whatsmeow.Client, ctx *context.Context) error { + allBirthdays := sql.GetAllBirthdays() + if len(allBirthdays) == 0 { + _, _ = reply(client, ctx.Message, "No birthdays saved yet.") + return ext.EndGroups } - text = text[1:] - value := sql.GetNote(text).Value - if value == "" { - return nil + + var response strings.Builder + response.WriteString("*Saved Birthdays*:\n") + for _, b := range allBirthdays { + user := strings.Split(b.UserID, "@")[0] + response.WriteString(fmt.Sprintf("\n- `User %s`: %s", user, b.Date)) } - _, _ = reply(client, ctx.Message, value) - return nil + + _, _ = reply(client, ctx.Message, response.String()) + return ext.EndGroups } +func checkAndWishBirthdays(client *whatsmeow.Client, ppLogger *logger.Logger) { + time.Sleep(10 * time.Second) -func listNotes(client *whatsmeow.Client, ctx *context.Context) error { - text := "*List of notes*:" - for _, note := range sql.GetNotes() { - text += fmt.Sprintf("\n- ```%s```", note.Name) - } - if text == "*List of notes*:" { - text = "No notes are present." + for { + ppLogger.Println("Running daily birthday check...") + today := time.Now().Format("02/01") + birthdays := sql.GetTodaysBirthdays(today) + + if len(birthdays) > 0 { + for _, b := range birthdays { + ppLogger.Println(fmt.Sprintf("Wishing birthday to %s", b.UserID)) + chatJID, _ := types.ParseJID(b.ChatJID) + userJID, _ := types.ParseJID(b.UserID) + + wish := fmt.Sprintf("Happy Birthday @%s!", userJID.User) + _, err := client.SendMessage(context.Background(), chatJID, &waE2E.Message{ + ExtendedTextMessage: &waE2E.ExtendedTextMessage{ + Text: proto.String(wish), + ContextInfo: &waE2E.ContextInfo{ + MentionedJID: []string{userJID.String()}, + }, + }, + }) + if err != nil { + ppLogger.ChangeLevel(logger.LevelError).Println(fmt.Sprintf("Failed to send birthday wish to %s: %v", userJID, err)) + } + time.Sleep(2 * time.Second) + } + } else { + ppLogger.Println("No birthdays found for today.") + } + time.Sleep(24 * time.Hour) } - _, _ = reply(client, ctx.Message, text) - return ext.EndGroups } +func (*Module) LoadBirthday(dispatcher *ext.Dispatcher, client *whatsmeow.Client) { + ppLogger := LOGGER.Create("birthday") + defer ppLogger.Println("Loaded Birthday module") -func (*Module) LoadNote(dispatcher *ext.Dispatcher) { - ppLogger := LOGGER.Create("note") - defer ppLogger.Println("Loaded Note module") dispatcher.AddHandler( - handlers.NewCommand("add", authorizedOnly(addNote), ppLogger.Create("add-cmd"). - ChangeLevel(waLogger.LevelInfo), - ).AddDescription("Add a note."), + handlers.NewCommand("savebirthday", authorizedOnly(saveBirthday), ppLogger.Create("save-cmd")). + AddDescription("Saves a user's birthday. Usage: .savebirthday DD/MM @user"), ) dispatcher.AddHandler( - handlers.NewCommand("clear", authorizedOnly(deleteNote), ppLogger.Create("clear-cmd"). - ChangeLevel(waLogger.LevelInfo), - ).AddDescription("Clear a note."), + handlers.NewCommand("delbirthday", authorizedOnly(deleteBirthday), ppLogger.Create("del-cmd")). + AddDescription("Deletes a user's birthday. Usage: .delbirthday @user"), ) dispatcher.AddHandler( - handlers.NewCommand("get", getNote, ppLogger.Create("get-cmd"). - ChangeLevel(waLogger.LevelInfo), - ).AddDescription("Get a note from key."), - ) - dispatcher.AddHandler( - handlers.NewCommand("notes", listNotes, ppLogger.Create("notes"). - ChangeLevel(waLogger.LevelInfo), - ).AddDescription("Display keys of all saved notes."), - ) - dispatcher.AddHandlerToGroup( - handlers.NewMessage(getNoteHash, ppLogger.Create("get-hash"). - ChangeLevel(waLogger.LevelInfo), - ).AddDescription("Get a note using #key format."), - 1, + handlers.NewCommand("birthdays", listBirthdays, ppLogger.Create("list-cmd")). + AddDescription("Lists all saved birthdays."), ) + + go checkAndWishBirthdays(client, ppLogger) } From e3c0e440ed4965ca9f95bdc63d91157c6826394d Mon Sep 17 00:00:00 2001 From: Yash Raj <68998269+SparkleYR@users.noreply.github.com> Date: Mon, 30 Jun 2025 17:03:35 +0530 Subject: [PATCH 8/8] Update init.go --- core/init.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/core/init.go b/core/init.go index e56b74f..05bb73d 100644 --- a/core/init.go +++ b/core/init.go @@ -78,18 +78,15 @@ func authorizedOnlyMessages(callback handlers.Response) handlers.Response { return callback(client, ctx) } } - -func Load(dispatcher *ext.Dispatcher, client *whatsmeow.Client) { +func Load(dispatcher *ext.Dispatcher, client *whatsmeow.Client) { defer LOGGER.Println("Loaded all modules") Type := reflect.TypeOf(&Module{}) Value := reflect.ValueOf(&Module{}) - for i := 0; i < Type.NumMethod(); i++ { method := Type.Method(i) - numArgs := method.Type.NumIn() - if numArgs == 3 && method.Type.In(2) == reflect.TypeOf(client) { + if method.Type.NumIn() == 3 && method.Type.In(2).String() == "*whatsmeow.Client" { method.Func.Call([]reflect.Value{Value, reflect.ValueOf(dispatcher), reflect.ValueOf(client)}) - } else if numArgs == 2 { + } else if method.Type.NumIn() == 2 && method.Type.In(1).String() == "*ext.Dispatcher" { method.Func.Call([]reflect.Value{Value, reflect.ValueOf(dispatcher)}) } }