From 6c33d1a4107878bfacdf7d069502b1ce78ebb1c2 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 10 Oct 2025 10:10:15 +0000 Subject: [PATCH 1/4] feat: Implement multi-user timetable tracking and notifications Refactors the application to support multiple users for timetable logging. - User accounts added via a Discord command are now stored in `accounts.json`. - The main scheduling loop now fetches timetables for all registered users, in addition to the default user defined in the `.env` file. - Notifications for registered users are now sent as direct messages, providing personalized updates. The default user continues to use the existing webhook. - User-specific data files (e.g., `timetable_filled_.json`) are now used to keep each user's data separate. - Error handling has been improved to prevent the application from crashing if an operation fails for a single user. --- Bot/Timetable.go | 106 +++++++++++++++++------------- Bot/Untis.go | 49 ++++++-------- Botrun/BotStart.go | 50 +++++++++++++++ main.go | 156 ++++++++++++++++++++++++++++----------------- 4 files changed, 226 insertions(+), 135 deletions(-) diff --git a/Bot/Timetable.go b/Bot/Timetable.go index 74afefc..898cca4 100644 --- a/Bot/Timetable.go +++ b/Bot/Timetable.go @@ -88,67 +88,73 @@ func ReadLoginResultFromFile(path string) (Loginresult, error) { return result, err } -func Timetable(cookies []*http.Cookie) { - loginResult, err := ReadLoginResultFromFile("login.json") - if err != nil { - log.Fatal(err) +func Timetable(cookies []*http.Cookie, userID string) { + loginFile := "login.json" + if userID != "" { + loginFile = "login_" + userID + ".json" } + + loginResult, err := ReadLoginResultFromFile(loginFile) + if err != nil { + log.Printf("Could not read login result for user %s: %v", userID, err) + return + } + today := time.Now().Format("20060102") g := getTimetable{"2023-05-06 15:44:22.215292", "getTimetable", params{today, today, loginResult.PersonID, loginResult.PersonType}, "2.0"} - TimetablesJson, err := json.Marshal(g) + timetablesJson, err := json.Marshal(g) if err != nil { - log.Fatalf("Error marshaling login data: %v", err) + log.Printf("Error marshaling timetable request: %v", err) return } - timetable := bytes.NewReader(TimetablesJson) + timetable := bytes.NewReader(timetablesJson) - prompt, err := http.NewRequest("GET", Url, timetable) + req, err := http.NewRequest("GET", Url, timetable) if err != nil { - log.Fatalf("Error creatingrequest: %v", err) + log.Printf("Error creating timetable request: %v", err) return } - //log.Println("prompt without extra header or cookie ", prompt) - //log.Println("Cookie: ", cookies) - - prompt.Header.Set("Content-Type", "application/json") - prompt.Header.Set("User-Agent", "Webuntis Test") - + req.Header.Set("Content-Type", "application/json") for _, cookie := range cookies { - //if cookie.Name == "JSESSIONID" { - prompt.AddCookie(cookie) - //log.Printf("Added JSESSIONID cookie: %s=%s", cookie.Name, cookie.Value) - //} + req.AddCookie(cookie) } - //log.Println("Request JSON:", string(TeachersJson)) - out, err := http.DefaultClient.Do(prompt) + + resp, err := http.DefaultClient.Do(req) if err != nil { - log.Printf("Error during request: %v", err) + log.Printf("Error fetching timetable: %v", err) return } - defer out.Body.Close() - //log.Println(out.Status) - response, err := io.ReadAll(out.Body) + defer resp.Body.Close() + + response, err := io.ReadAll(resp.Body) if err != nil { - log.Printf("Error reading response body: %v", err) + log.Printf("Error reading timetable response: %v", err) return } - //responseString := string(response) - //log.Println("Repsonse ", responseString) + var Response TimetableResponse - err = json.Unmarshal(response, &Response) - if err != nil { - log.Fatalf("Error unmarshaling response: %v", err) + if err := json.Unmarshal(response, &Response); err != nil { + log.Printf("Error unmarshaling timetable response: %v", err) + return } + data, err := json.MarshalIndent(Response.Result, "", " ") if err != nil { - log.Fatal(err) + log.Printf("Error marshaling timetable result: %v", err) + return } - err = os.WriteFile("timetable.json", data, 0644) - if err != nil { - log.Fatal(err) + + timetableFile := "timetable.json" + if userID != "" { + timetableFile = "timetable_" + userID + ".json" } - log.Println("Updated Timetable") - setTimetable() + if err := os.WriteFile(timetableFile, data, 0644); err != nil { + log.Printf("Error writing timetable file: %v", err) + return + } + + log.Printf("Updated timetable for user %s", userID) + setTimetable(userID) } func LoadIDMap(path string) (map[int]string, error) { @@ -189,14 +195,18 @@ func formatDate(date int) string { day := s[6:8] return fmt.Sprintf("%s-%s-%s", day, month, year) } -func setTimetable() { +func setTimetable(userID string) { subjects, _ := LoadIDMap("subjects.json") rooms, _ := LoadIDMap("rooms.json") classes, _ := LoadIDMap("classes.json") - timetable, _ := LoadTimetable("timetable.json") + + timetableFile := "timetable.json" + if userID != "" { + timetableFile = "timetable_" + userID + ".json" + } + timetable, _ := LoadTimetable(timetableFile) var namedTimetable []NamedTimetableEntry - for _, lesson := range timetable { var klNames, suNames, roNames []string for _, kl := range lesson.Kl { @@ -224,11 +234,17 @@ func setTimetable() { data, err := json.MarshalIndent(namedTimetable, "", " ") if err != nil { - log.Fatal(err) + log.Printf("Error marshaling named timetable: %v", err) + return } - err = os.WriteFile("timetableFilled.json", data, 0644) - if err != nil { - log.Fatal(err) + + timetableFilledFile := "timetableFilled.json" + if userID != "" { + timetableFilledFile = "timetableFilled_" + userID + ".json" } - log.Println("Filled Timetable") + if err := os.WriteFile(timetableFilledFile, data, 0644); err != nil { + log.Printf("Error writing timetableFilled file: %v", err) + return + } + log.Printf("Filled timetable for user %s", userID) } diff --git a/Bot/Untis.go b/Bot/Untis.go index 260afae..7b3faf9 100644 --- a/Bot/Untis.go +++ b/Bot/Untis.go @@ -35,70 +35,59 @@ var Url = "https://thalia.webuntis.com/WebUntis/jsonrpc.do?school=Mons_Tabor" //var Password = os.Getenv("UNTIS_PASSWORD") //var USERS = os.Getenv("UNTIS_USER") -func Main(user, password string) { +func Main(user, password, userID string) { godotenv.Load("../.env") - cookies, err := Auth(user, password) + cookies, err := Auth(user, password, userID) if err != nil { - log.Fatal(err) + log.Printf("Authentication failed for user %s: %v", user, err) return } Rooms(cookies) - Classes(cookies) - Subjects(cookies) - - Timetable(cookies) - - //getTeachers sends empty response + Timetable(cookies, userID) Teachers(cookies) } -func Auth(user, password string) ([]*http.Cookie, error) { +func Auth(user, password, userID string) ([]*http.Cookie, error) { l := Login{"2023-05-06 15:44:22.215292", "authenticate", Params{user, password, "WebUntis Test"}, "2.0"} loginJSON, err := json.Marshal(l) if err != nil { - log.Fatalf("Error marshaling login data: %v", err) return nil, err } login := bytes.NewReader(loginJSON) - //log.Println("Run") LoginOut, err := http.Post(Url, "application/json", login) if err != nil { - log.Printf("Error during authentication: %v", err) return nil, err } defer LoginOut.Body.Close() - // Parse cookies from response cookies := LoginOut.Cookies() - //log.Println("Received cookies:", cookies) - //log.Println("Set-Cookie headers:", LoginOut.Header["Set-Cookie"]) - //loginRespBody, _ := io.ReadAll(LoginOut.Body) - //log.Println("Login response body:", string(loginRespBody)) + log.Printf("Login successful for user: %s", user) - log.Println("Login successful") - - //log.Println("Login successful") response, err := io.ReadAll(LoginOut.Body) if err != nil { - log.Fatalf("Error reading response body: %v", err) + return nil, err } + var Response LoginResponse - err = json.Unmarshal(response, &Response) - if err != nil { - log.Fatalf("Error unmarshaling response: %v", err) + if err := json.Unmarshal(response, &Response); err != nil { + return nil, err } + data, err := json.MarshalIndent(Response.Result, "", " ") if err != nil { - log.Fatal(err) + return nil, err } - err = os.WriteFile("login.json", data, 0644) - if err != nil { - log.Fatal(err) + + loginFile := "login.json" + if userID != "" { + loginFile = "login_" + userID + ".json" + } + if err := os.WriteFile(loginFile, data, 0644); err != nil { + return nil, err } return cookies, nil - } diff --git a/Botrun/BotStart.go b/Botrun/BotStart.go index 63a5c6d..a580dd3 100644 --- a/Botrun/BotStart.go +++ b/Botrun/BotStart.go @@ -248,6 +248,35 @@ func Start() { dg.Close() } +// DecryptedAccount holds user information with a decrypted password. +type DecryptedAccount struct { + UserID string + Username string + DecryptedPassword string +} + +// LoadAndDecryptAccounts loads all accounts from the JSON file and decrypts their passwords. +func LoadAndDecryptAccounts() ([]DecryptedAccount, error) { + accounts := loadAllAccounts() + decryptedAccounts := make([]DecryptedAccount, 0, len(accounts)) + + for _, acc := range accounts { + decPwd, err := decrypt(acc.Password) + if err != nil { + // Log the error but continue with other accounts + fmt.Printf("Error decrypting password for user %s: %v\n", acc.UserID, err) + continue + } + decryptedAccounts = append(decryptedAccounts, DecryptedAccount{ + UserID: acc.UserID, + Username: acc.Username, + DecryptedPassword: decPwd, + }) + } + + return decryptedAccounts, nil +} + // Expose this for main.go to trigger notifications func NotifyAllUsers() { if DiscordSession != nil { @@ -255,6 +284,27 @@ func NotifyAllUsers() { } } +// SendDM sends a direct message to a user. +func SendDM(userID, message string) { + if DiscordSession == nil { + fmt.Println("Discord session not initialized, cannot send DM.") + return + } + // Avoid sending messages to the "default" user placeholder + if userID == "default" { + return + } + channel, err := DiscordSession.UserChannelCreate(userID) + if err != nil { + fmt.Printf("Error creating DM channel for %s: %v\n", userID, err) + return + } + _, err = DiscordSession.ChannelMessageSend(channel.ID, message) + if err != nil { + fmt.Printf("Error sending DM to %s: %v\n", userID, err) + } +} + func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) { // Ignore bot's own messages if m.Author.ID == s.State.User.ID { diff --git a/main.go b/main.go index 3751813..921ea3f 100644 --- a/main.go +++ b/main.go @@ -80,45 +80,74 @@ func isScheduledTime(now time.Time) bool { return false } func scheduleTimetableUpdate() { - var prevData []byte - //declare user and pass - godotenv.Load(".env") - var password = os.Getenv("UNTIS_PASSWORD") - var user = os.Getenv("UNTIS_USER") - Untis.Main(user, password) - // Initial read of the file - data, err := os.ReadFile("timetableFilled.json") - if err == nil { - prevData = data + prevData := make(map[string][]byte) + + // Function to update all users + updateAllUsers := func() { + // Load all registered users + accounts, err := BotStart.LoadAndDecryptAccounts() + if err != nil { + log.Printf("Error loading accounts: %v", err) + } + + // Add the default user from .env + defaultUser := os.Getenv("UNTIS_USER") + defaultPassword := os.Getenv("UNTIS_PASSWORD") + if defaultUser != "" && defaultPassword != "" { + accounts = append(accounts, BotStart.DecryptedAccount{ + UserID: "default", + Username: defaultUser, + DecryptedPassword: defaultPassword, + }) + } + + // Process each user + for _, acc := range accounts { + // Fetch the latest timetable + Untis.Main(acc.Username, acc.DecryptedPassword, acc.UserID) + + // Check for changes + timetableFile := fmt.Sprintf("timetableFilled_%s.json", acc.UserID) + if acc.UserID == "default" { + timetableFile = "timetableFilled.json" + } + data, err := os.ReadFile(timetableFile) + if err != nil { + log.Printf("Error reading timetable for user %s: %v", acc.Username, err) + continue + } + + if prev, ok := prevData[acc.UserID]; ok && !bytes.Equal(data, prev) { + log.Printf("Timetable has changed for user %s.", acc.Username) + BotStart.SendDM(acc.UserID, "Your timetable has changed!") + } + prevData[acc.UserID] = data + } } - // Ticker for checking timetable changes every hour - hourTicker := time.NewTicker(1 * time.Minute) + // Initial update + updateAllUsers() + + // Ticker for periodic updates + hourTicker := time.NewTicker(1 * time.Minute) // For testing, 1 minute. Change to 1 * time.Hour for production. go func() { for range hourTicker.C { - Untis.Main(user, password) - data, err := os.ReadFile("timetableFilled.json") - if err != nil { - log.Printf("Error reading timetable: %v", err) - } else if !bytes.Equal(data, prevData) { - log.Println("Timetable file has changed.") - sendUpdateDiscordWebhook() - prevData = data - } - // Trigger bot notifications for all users - BotStart.NotifyAllUsers() + updateAllUsers() } }() - // Ticker for checking scheduled times every minute + // Ticker for scheduled lesson notifications startMinuteTicker(func() { now := time.Now() if isScheduledTime(now) { - log.Println("Scheduled time reached, updating and running Run()") - Untis.Main(user, password) - log.Println("Updated now running Run()") - Run() - log.Println("Finished running Run") + log.Println("Scheduled time reached, running notifications...") + accounts, _ := BotStart.LoadAndDecryptAccounts() + // Also notify the default user + accounts = append(accounts, BotStart.DecryptedAccount{UserID: "default"}) + for _, acc := range accounts { + Run(acc.UserID) + } + log.Println("Finished running notifications.") } }) } @@ -136,42 +165,49 @@ func startMinuteTicker(f func()) { }() } -/* -#Main Run Function -*/ -func Run() { - log.Println("Sending next Lesson") - codeByStartTime, err := MapTimeToCode("timetableFilled.json") - if err != nil { - log.Println(err) +func Run(userID string) { + timetableFile := "timetableFilled.json" + if userID != "default" { + timetableFile = fmt.Sprintf("timetableFilled_%s.json", userID) + } + log.Printf("Sending next lesson for user %s", userID) + + // Check if the timetable file exists + if _, err := os.Stat(timetableFile); os.IsNotExist(err) { + log.Printf("Timetable file %s not found for user %s.", timetableFile, userID) + return } - roomByStartTime, err := MapTimeToRoom("timetableFilled.json") - if err != nil { - log.Fatal(err) - } - subjectByStartTime, err := MapTimeToSubject("timetableFilled.json") - if err != nil { - log.Fatal(err) - } + codeByStartTime, _ := MapTimeToCode(timetableFile) + roomByStartTime, _ := MapTimeToRoom(timetableFile) + subjectByStartTime, _ := MapTimeToSubject(timetableFile) + now := time.Now().Format("15:04") - nextTime, room, found := NextRoomForTime(roomByStartTime, now) - if found { - log.Println("found Room") - Subject, found := NextSubjectForTime(subjectByStartTime, now) - if found { - log.Println("Found Subject") - Status, found := NextCodeForTime(codeByStartTime, now) - if found { - log.Println("Found Status") - fmt.Printf("Next time: %s, Room: %s\n", nextTime, room) - sendDiscordWebhook(Subject, room, nextTime, Status) - } else { - sendDiscordWebhook(Subject, room, nextTime, "") - } - } + nextTime, room, foundRoom := NextRoomForTime(roomByStartTime, now) + if !foundRoom { + return // No upcoming lessons } + subject, foundSubject := NextSubjectForTime(subjectByStartTime, now) + if !foundSubject { + return // Should not happen if room was found + } + + status, _ := NextCodeForTime(codeByStartTime, now) + + var message string + if status != "" { + message = fmt.Sprintf("Next lesson: **%s** in room **%s** at **%s**. Status: **%s**", subject, room, nextTime, status) + } else { + message = fmt.Sprintf("Next lesson: **%s** in room **%s** at **%s**.", subject, room, nextTime) + } + + if userID == "default" { + // The default user still sends to the webhook + sendDiscordWebhook(subject, room, nextTime, status) + } else { + BotStart.SendDM(userID, message) + } } /* From 2c0c1b289ba4312fcd113d71cd12fc4056d12167 Mon Sep 17 00:00:00 2001 From: Jannover5000r Date: Fri, 10 Oct 2025 12:53:43 +0200 Subject: [PATCH 2/4] changed readme and gitignore to updated features -added deleteaccount command with alias removeaccount --- .gitignore | 3 +- Botrun/BotStart.go | 73 ++++++++++++++++++++++++++++++++++++++++++++++ README.md | 11 +++++-- 3 files changed, 83 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 153d98a..71732ee 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ teachers.json login.json timetable*.json timetableFilled*.json -accounts.json \ No newline at end of file +accounts.json +login*.json diff --git a/Botrun/BotStart.go b/Botrun/BotStart.go index a580dd3..145450c 100644 --- a/Botrun/BotStart.go +++ b/Botrun/BotStart.go @@ -305,6 +305,56 @@ func SendDM(userID, message string) { } } +// Delete account and associated files for a given userID +func deleteAccount(userID string) error { + var accounts []Account + + // Load existing accounts + data, err := os.ReadFile(accountsFile) + if err != nil { + if os.IsNotExist(err) { + return nil // No accounts file means nothing to delete + } + return err + } + + if len(data) > 0 { + if err := json.Unmarshal(data, &accounts); err != nil { + return err + } + } + + // Filter out the account with matching userID + newAccounts := accounts[:0] + found := false + for _, acc := range accounts { + if acc.UserID != userID { + newAccounts = append(newAccounts, acc) + } else { + found = true + } + } + + if !found { + return errors.New("account not found") + } + + // Save remaining accounts back to file + newData, err := json.MarshalIndent(newAccounts, "", " ") + if err != nil { + return err + } + if err := os.WriteFile(accountsFile, newData, 0644); err != nil { + return err + } + + // Delete associated timetable files + os.Remove(getTimetableFile(userID)) + os.Remove(getTimetableFilledFile(userID)) + + return nil +} + func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) { // Ignore bot's own messages if m.Author.ID == s.State.User.ID { @@ -329,6 +379,29 @@ func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) { return } + // Handle "!deleteaccount" and "!removeaccount" commands + if m.GuildID != "" && (m.Content == "!deleteaccount" || m.Content == "!removeaccount") { + // Delete the command message for privacy + _ = s.ChannelMessageDelete(m.ChannelID, m.ID) + + err := deleteAccount(m.Author.ID) + if err != nil { + // Create DM channel to notify of error + channel, err := s.UserChannelCreate(m.Author.ID) + if err == nil { + s.ChannelMessageSend(channel.ID, "Failed to delete your account: "+err.Error()) + } + return + } + + // Notify success via DM + channel, err := s.UserChannelCreate(m.Author.ID) + if err == nil { + s.ChannelMessageSend(channel.ID, "Your account has been successfully deleted.") + } + return + } + // Handle DMs for the two-step process if m.GuildID == "" { stateMutex.Lock() diff --git a/README.md b/README.md index dd8bfcd..398bcef 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,13 @@ # Installation -### This bot is self hosted and will run correctly when doing "go run . " in the root folder of the project. Before usage add the .env file with the Credentials as mentioned above. When you run the Programm for the first time, all important files will be created automatically and the bot is ready to go. The user added via the fields UNTIS_USER and UNTIS_PASSWORD in the .env will be the one where the Webhook is sourced from and the other users will be send a DM after adding their account with the command. +# This is a discord application intended for self hosting. You will need to add an application in the disord developer website for this bot mainly with the read messages and write messages privelege as well as send privat messages for the account handling. If you give it administrator permissions you dont have to worry further but this could present you with a security risk if my github account gets compromised and a malicious update to the script gets pushed which you than use since the bot got access to your whole server. -## In case your school is using a different timing for the Lessons than mine, you can change the times where you will be notified with the next room and Lesson for the day in **Line 71 in the main.go** file. -# I am neither a representative of Untis Untis Baden-Württemberg GmbH nor a Developer in their team. This project is based on their API and is not affiliated with them. \ No newline at end of file +### This bot is self hosted and will run correctly when doing "go run . " in the root folder of the project. **Before usage add the .env file with the Credentials as mentioned above.** When you run the Programm for the first time, all important files will be created automatically and the bot is ready to go. The user added via the fields UNTIS_USER and UNTIS_PASSWORD in the .env will be the one where the Webhook is sourced from and the other users will be send a DM after adding their account with the command. + +### For multi user support: using the command **!addaccount** in the discord server the bot is used in, it will than dm you with the sign up process prompting you to enter your untis Username and password (no email neededd). The Username is stored as plain text and the password is hashed on the device hosting the bot, so currently it is **not secure** since the host will have access to the hashed password and the hash used! Deleting an account is done by using **!removeaccount** which will delete the account, created with you discord account. + +## In case your school is using a different timing for the Lessons than mine, you can change the times where you will be notified with the next room and Lesson for the day in **Line 71 in the main.go** file. The bot may not send you a message if there is no lesson after the specified time so that you can leave empty lessons as is for the days. + +# I am neither a representative of Untis, Untis Baden-Württemberg GmbH nor a Developer in their team. This project is based on their API and my code and is not affiliated with them. \ No newline at end of file From f91674daa1f3fa0cb62c747d3523ec03c4a88cc4 Mon Sep 17 00:00:00 2001 From: Jannover5000r Date: Fri, 10 Oct 2025 13:00:13 +0200 Subject: [PATCH 3/4] new readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 398bcef..4b693ab 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ # This is a discord application intended for self hosting. You will need to add an application in the disord developer website for this bot mainly with the read messages and write messages privelege as well as send privat messages for the account handling. If you give it administrator permissions you dont have to worry further but this could present you with a security risk if my github account gets compromised and a malicious update to the script gets pushed which you than use since the bot got access to your whole server. -### This bot is self hosted and will run correctly when doing "go run . " in the root folder of the project. **Before usage add the .env file with the Credentials as mentioned above.** When you run the Programm for the first time, all important files will be created automatically and the bot is ready to go. The user added via the fields UNTIS_USER and UNTIS_PASSWORD in the .env will be the one where the Webhook is sourced from and the other users will be send a DM after adding their account with the command. +### This bot is self hosted and will run correctly when doing "go run . " in the root folder of the project. **Before usage add the .env file with the Credentials as mentioned above.** When you run the Program for the first time, all important files will be created automatically and the bot is ready to go. The user added via the fields UNTIS_USER and UNTIS_PASSWORD in the .env will be the one where the Webhook is sourced from and the other users will be send a DM after adding their account with the command. Add the Discord Webhook via the DISCORD_WEBHOOK_URL point in the .env and will send the messages for the timetable in there. ### For multi user support: using the command **!addaccount** in the discord server the bot is used in, it will than dm you with the sign up process prompting you to enter your untis Username and password (no email neededd). The Username is stored as plain text and the password is hashed on the device hosting the bot, so currently it is **not secure** since the host will have access to the hashed password and the hash used! Deleting an account is done by using **!removeaccount** which will delete the account, created with you discord account. From bc22180e103aedfe74c9a913b600f4dcf9e71306 Mon Sep 17 00:00:00 2001 From: Jannover5000r <90698896+Jannover5000r@users.noreply.github.com> Date: Sat, 11 Oct 2025 01:57:08 +0200 Subject: [PATCH 4/4] changes to gomod and gosum --- go.mod | 8 ++++---- go.sum | 9 ++++++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 8f32964..1d720a8 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module untislogger -go 1.24 +go 1.25.2 require ( github.com/bwmarrin/discordgo v0.29.0 @@ -8,7 +8,7 @@ require ( ) require ( - github.com/gorilla/websocket v1.4.2 // indirect - golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b // indirect - golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 // indirect + github.com/gorilla/websocket v1.5.3 // indirect + golang.org/x/crypto v0.43.0 // indirect + golang.org/x/sys v0.37.0 // indirect ) diff --git a/go.sum b/go.sum index 04715b6..3ce7d10 100644 --- a/go.sum +++ b/go.sum @@ -1,14 +1,17 @@ github.com/bwmarrin/discordgo v0.29.0 h1:FmWeXFaKUwrcL3Cx65c20bTRW+vOb6k8AnaP+EgjDno= github.com/bwmarrin/discordgo v0.29.0/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= -github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= +golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=