diff --git a/bot/bot.go b/bot/bot.go
index 1dd891e..35fabc6 100644
--- a/bot/bot.go
+++ b/bot/bot.go
@@ -35,6 +35,18 @@ func run(bot *tgbotapi.BotAPI) {
for update := range updates {
if update.Message == nil {
+ callback := tgbotapi.NewCallback(update.CallbackQuery.ID, update.CallbackQuery.Data)
+ if _, err := bot.Request(callback); err != nil {
+ log.Printf("Error [bot.Request() for the callback]: %s\n", err)
+ continue
+ }
+
+ callback_text := update.CallbackQuery.Data
+
+ if strings.HasPrefix(callback_text, "lectures_") {
+ handleCallback(bot, &update, "lezioni", callback_text)
+ }
+
continue
} else if filterMessage(bot, update.Message) {
continue
@@ -229,6 +241,15 @@ func executeCommand(bot *tgbotapi.BotAPI, update *tgbotapi.Update, commandIndex
if newCommand.HasNextCommand() {
handleAction(bot, update, newCommand.NextCommand)
}
+
+ if newCommand.HasRows() {
+ msg := tgbotapi.NewMessage(update.Message.Chat.ID, update.Message.Text)
+ msg.ReplyMarkup = newCommand.Rows
+ if _, err := bot.Send(msg); err != nil {
+ msg = tgbotapi.NewMessage(update.Message.Chat.ID, "Error sending data")
+ bot.Send(msg)
+ }
+ }
}
}
@@ -247,6 +268,21 @@ func handleAction(bot *tgbotapi.BotAPI, update *tgbotapi.Update, commandName str
return false
}
+// Handle a callback searching a the good action
+func handleCallback(bot *tgbotapi.BotAPI, update *tgbotapi.Update, commandName string, callback_text string) bool {
+ idx := slices.IndexFunc(model.Actions, func(action model.Action) bool {
+ return action.Name == commandName
+ })
+
+ if idx != -1 {
+ model.Actions[idx].Data.HandleBotCallback(bot, update, callback_text)
+
+ return true
+ }
+
+ return false
+}
+
func filterMessage(bot *tgbotapi.BotAPI, message *tgbotapi.Message) bool {
if message.Dice != nil {
// msg := tgbotapi.NewMessage(message.Chat.ID, "Found a dice")
diff --git a/commands/uni.go b/commands/uni.go
index d860b67..98cc03b 100644
--- a/commands/uni.go
+++ b/commands/uni.go
@@ -50,10 +50,10 @@ func (t *LezioniTime) UnmarshalJSON(data []byte) error {
// GetTimeTable returns an HTML string containing the timetable for the given
// course on the given date. Returns an empty string if there are no lessons.
-func GetTimeTable(courseType, courseName string, year int, day time.Time) (string, error) {
+func GetTimeTable(courseType, courseName string, curriculum string, year int, day time.Time) (string, error) {
interval := &timetable.Interval{Start: day, End: day}
- events, err := timetable.FetchTimetable(courseType, courseName, "", year, interval)
+ events, err := timetable.FetchTimetable(courseType, courseName, curriculum, year, interval)
if err != nil {
log.Printf("Error getting timetable: %s\n", err)
return "", err
diff --git a/commands/uni_test.go b/commands/uni_test.go
index 48f3133..e0c2507 100644
--- a/commands/uni_test.go
+++ b/commands/uni_test.go
@@ -106,6 +106,7 @@ func TestGetTimeTable(t *testing.T) {
courseName string
year int
day time.Time
+ curriculum string
}
tests := []struct {
name string
@@ -145,7 +146,7 @@ func TestGetTimeTable(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- got, err := GetTimeTable(tt.args.courseType, tt.args.courseName, tt.args.year, tt.args.day)
+ got, err := GetTimeTable(tt.args.courseType, tt.args.courseName, tt.args.curriculum, tt.args.year, tt.args.day)
if err != nil && !tt.error {
t.Errorf("GetTimeTable() error = %v", err)
return
@@ -163,7 +164,7 @@ func TestGetTimeTable(t *testing.T) {
func TestWeekend(t *testing.T) {
date := time.Date(2023, 3, 11, 0, 0, 0, 0, time.UTC)
- result, err := GetTimeTable("laurea", "informatica", 1, date)
+ result, err := GetTimeTable("laurea", "informatica", "", 1, date)
if err != nil {
t.Fatalf("Error while getting timetable: %s", err)
}
diff --git a/json/actions.json b/json/actions.json
index d13becf..11e9b2b 100644
--- a/json/actions.json
+++ b/json/actions.json
@@ -91,98 +91,12 @@
"text": "Lauree Triennali\n\nInformatica\n1\ufe0f\u20e3 1\u00b0 anno (Telegram, Discord)\n2\ufe0f\u20e3 2\u00b0 anno (Telegram, Discord)\n3\ufe0f\u20e3 3\u00b0 anno (Telegram, Discord)\n3\ufe0f\u20e3 Ex-3\u00b0 anno (Telegram, Discord)\n\ud83d\uddff Subreddit\n\nIngegneria Informatica\n1\ufe0f\u20e32\ufe0f\u20e33\ufe0f\u20e3 Tutti gli anni(Whatsapp)\n4\ufe0f\u20e3 Ex-3\u00b0 anno(Telegram)\n\nIngegneria e Science informatiche (Cesena)\n1\ufe0f\u20e32\ufe0f\u20e33\ufe0f\u20e3 Tutti gli anni(Whatsapp)\n\nInformatica per il Management\n1\ufe0f\u20e32\ufe0f\u20e33\ufe0f\u20e3 Tutti gli anni(Telegram)\n\nLauree Magistrali\n\nInformatica\n1\ufe0f\u20e32\ufe0f\u20e3 Tutti gli anni(Telegram)\n\nArtificial Intelligence\n1\ufe0f\u20e3 1\u00b0 anno (Telegram)\n2\ufe0f\u20e3 2\u00b0 anno (Telegram)\n\nIngegneria e Science informatiche (Cesena)\n1\ufe0f\u20e32\ufe0f\u20e3 Tutti gli anni (Whatsapp, Telegram)\n\n\ud83d\udd22 Tutti i gruppi Unibo"
}
},
- "lezionioggi": {
- "type": "yearly",
+ "lezioni": {
+ "type": "buttonsLecture",
"data": {
- "description": "Orari lezioni di oggi (tuo anno)",
- "command": "lezionioggi",
- "noYear": "Questo non sembra essere un gruppo generale per nessun anno di corso. Per usare questo comando da qui, devi specificare /lezionioggi1, /lezionioggi2 o /lezionioggi3."
- }
- },
- "lezionioggi1": {
- "type": "todayLectures",
- "data": {
- "description": "Orari lezioni di oggi (1\u00b0 anno)",
- "course": {
- "year": 1,
- "type": "laurea",
- "name": "informatica"
- },
- "title": "Lezioni di oggi (1\u00b0 anno):\n",
- "fallbackText": "Non ci sono lezioni oggi. SMETTILA DI PRESSARMI"
- }
- },
- "lezionioggi2": {
- "type": "todayLectures",
- "data": {
- "description": "Orari lezioni di oggi (2\u00b0 anno)",
- "course": {
- "year": 2,
- "type": "laurea",
- "name": "informatica"
- },
- "title": "Lezioni di oggi (2\u00b0 anno):\n",
- "fallbackText": "Non ci sono lezioni oggi. SMETTILA DI PRESSARMI"
- }
- },
- "lezionioggi3": {
- "type": "todayLectures",
- "data": {
- "description": "Orari lezioni di oggi (3\u00b0 anno)",
- "course": {
- "year": 3,
- "type": "laurea",
- "name": "informatica"
- },
- "title": "Lezioni di oggi:\n",
- "fallbackText": "Non ci sono lezioni oggi. SMETTILA DI PRESSARMI"
- }
- },
- "lezionidomani": {
- "type": "yearly",
- "data": {
- "description": "Orari lezioni di domani (tuo anno)",
- "command": "lezionidomani",
- "noYear": "Questo non sembra essere un gruppo generale per nessun anno di corso. Per usare questo comando da qui, devi specificare /lezionidomani1, /lezionidomani2 o /lezionidomani3."
- }
- },
- "lezionidomani1": {
- "type": "tomorrowLectures",
- "data": {
- "description": "Orari lezioni di domani (1\u00b0 anno)",
- "course": {
- "year": 1,
- "type": "laurea",
- "name": "informatica"
- },
- "title": "Lezioni di domani (1\u00b0 anno):\n",
- "fallbackText": "Non ci sono lezioni domani. SMETTILA DI PRESSARMI"
- }
- },
- "lezionidomani2": {
- "type": "tomorrowLectures",
- "data": {
- "description": "Orari lezioni di domani (2\u00b0 anno)",
- "course": {
- "year": 2,
- "type": "laurea",
- "name": "informatica"
- },
- "title": "Lezioni di domani (2\u00b0 anno):\n",
- "fallbackText": "Non ci sono lezioni domani. SMETTILA DI PRESSARMI"
- }
- },
- "lezionidomani3": {
- "type": "tomorrowLectures",
- "data": {
- "description": "Orari lezioni di domani (3\u00b0 anno)",
- "course": {
- "year": 3,
- "type": "laurea",
- "name": "informatica"
- },
- "title": "Lezioni di domani (3\u00b0 anno):\n",
- "fallbackText": "Non ci sono lezioni domani. SMETTILA DI PRESSARMI"
+ "description": "Orari lezioni",
+ "title": " Lezioni di %s di (%d\u00b0 anno) di giorno %s",
+ "fallbackText": "Non ci sono lezioni in questo giorno. SMETTILA DI PRESSARMI"
}
},
"materiali": {
diff --git a/model/callback.go b/model/callback.go
new file mode 100644
index 0000000..ceeabaf
--- /dev/null
+++ b/model/callback.go
@@ -0,0 +1,123 @@
+package model
+
+import (
+ "fmt"
+ "log"
+ "regexp"
+ "strconv"
+ "strings"
+ "time"
+
+ tgbotapi "github.com/musianisamuele/telegram-bot-api"
+
+ "github.com/csunibo/informabot/commands"
+)
+
+func (_ MessageData) HandleBotCallback(_bot *tgbotapi.BotAPI, _udpate *tgbotapi.Update, _callback_text string) {
+ log.Printf("`HandleBotCallback` not defined for `MessageData`")
+}
+
+func (_ HelpData) HandleBotCallback(_bot *tgbotapi.BotAPI, _udpate *tgbotapi.Update, _callback_text string) {
+ log.Printf("`HandleBotCallback` not defined for `HelpData`")
+}
+
+func (_ IssueData) HandleBotCallback(_bot *tgbotapi.BotAPI, _udpate *tgbotapi.Update, _callback_text string) {
+ log.Printf("`HandleBotCallback` not defined for `IssueData`")
+}
+
+func (_ LookingForData) HandleBotCallback(_bot *tgbotapi.BotAPI, _udpate *tgbotapi.Update, _callback_text string) {
+ log.Printf("`HandleBotCallback` not defined for `LookingForData`")
+}
+
+func (_ NotLookingForData) HandleBotCallback(_bot *tgbotapi.BotAPI, _udpate *tgbotapi.Update, _callback_text string) {
+ log.Printf("`HandleBotCallback` not defined for `NotLookingForData`")
+}
+
+// Handle the callback for the lectures command (`/lezioni`)
+// Parse the `callback_text` to check which operation it must to do:
+// - If the string ends with "_today" or "_tomorrow" it returns the timetable
+// - If the string just contains a "_y_" it asks for today or tomorrow
+// - Otherwise prints the course year of what timetable the user wants to see
+func (data Lectures) HandleBotCallback(bot *tgbotapi.BotAPI, update *tgbotapi.Update, callback_text string) {
+ var chatId = int64(update.CallbackQuery.Message.Chat.ID)
+ var messageId = update.CallbackQuery.Message.MessageID
+
+ if strings.Contains(callback_text, "_day_") {
+ dayRegex, err := regexp.Compile(`_day_(\d+)`)
+ if err != nil {
+ log.Printf("Error [dayRegex]: %s\n", err)
+ return
+ }
+
+ unixTime, err := strconv.ParseInt(dayRegex.FindString(callback_text)[5:], 10, 64)
+ if err != nil {
+ log.Printf("Error [unixTime]: %s\n", err)
+ return
+ }
+
+ timeForLectures := time.Unix(unixTime, 0)
+
+ yearRegex, err := regexp.Compile(`_y_(\d)_`)
+ if err != nil {
+ log.Printf("Error [yearRegex]: %s\n", err)
+ return
+ }
+
+ year, err := strconv.Atoi(yearRegex.FindString(callback_text)[3:4])
+ if err != nil {
+ log.Printf("Error [convert to integer the year regex]: %s\n", err)
+ return
+ }
+
+ timetableKey := callback_text[len("lectures_"):strings.Index(callback_text, "_y_")]
+
+ timetable := Timetables[timetableKey]
+ response, err := commands.GetTimeTable(timetable.Type, timetable.Name, timetable.Curriculum, year, timeForLectures)
+ if err != nil {
+ log.Printf("Error [GetTimeTable]: %s\n", err)
+ }
+
+ if response == "" {
+ response = data.FallbackText
+ } else {
+ response = fmt.Sprintf(data.Title, timetable.Course, year, timeForLectures.Format("2006-01-02")) + "\n\n" + response
+ }
+
+ editConfig := tgbotapi.NewEditMessageText(chatId, messageId, response)
+ editConfig.ParseMode = tgbotapi.ModeHTML
+
+ _, err = bot.Send(editConfig)
+ if err != nil {
+ log.Printf("Error [bot.Send() for the NewEditMessageText]: %s\n", err)
+ }
+ } else if strings.Contains(callback_text, "_y_") {
+ rows := ChooseTimetableDay(callback_text)
+ keyboard := tgbotapi.NewInlineKeyboardMarkup(rows...)
+ editConfig := tgbotapi.NewEditMessageReplyMarkup(chatId, messageId, keyboard)
+ _, err := bot.Send(editConfig)
+ if err != nil {
+ log.Printf("Error [bot.Send() for the NewEditMessageReplyMarkup]: %s\n", err)
+ }
+ } else {
+ timetableName := strings.TrimPrefix(callback_text, "lectures_")
+ rows := GetLectureYears(callback_text, Timetables[timetableName].Course)
+ keyboard := tgbotapi.NewInlineKeyboardMarkup(rows...)
+ editConfig := tgbotapi.NewEditMessageReplyMarkup(chatId, messageId, keyboard)
+ _, err := bot.Send(editConfig)
+ if err != nil {
+ log.Printf("Error [bot.Send() for the NewEditMessageReplyMarkup]: %s\n", err)
+ }
+ }
+}
+
+func (_ ListData) HandleBotCallback(_bot *tgbotapi.BotAPI, _udpate *tgbotapi.Update, _callback_text string) {
+ log.Printf("`HandleBotCallback` not defined for `ListData`")
+}
+
+func (_ LuckData) HandleBotCallback(_bot *tgbotapi.BotAPI, _udpate *tgbotapi.Update, _callback_text string) {
+ log.Printf("`HandleBotCallback` not defined for `LuckData`")
+}
+
+func (_ InvalidData) HandleBotCallback(_bot *tgbotapi.BotAPI, _udpate *tgbotapi.Update, _callback_text string) {
+ log.Printf("`HandleBotCallback` not defined for `InvalidData`")
+}
diff --git a/model/controller.go b/model/controller.go
index d59d031..f9cb8fb 100644
--- a/model/controller.go
+++ b/model/controller.go
@@ -10,7 +10,6 @@ import (
tgbotapi "github.com/musianisamuele/telegram-bot-api"
"golang.org/x/exp/slices"
- "github.com/csunibo/informabot/commands"
"github.com/csunibo/informabot/utils"
)
@@ -144,59 +143,10 @@ func (data NotLookingForData) HandleBotCommand(_ *tgbotapi.BotAPI, message *tgbo
return makeResponseWithText(msg)
}
-func (data YearlyData) HandleBotCommand(_ *tgbotapi.BotAPI, message *tgbotapi.Message) CommandResponse {
- chatTitle := strings.ToLower(message.Chat.Title)
-
- // check if string contains the year number
- if strings.Contains(chatTitle, "primo") ||
- strings.Contains(chatTitle, "first") {
- return makeResponseWithNextCommand(data.Command + "1")
- } else if strings.Contains(chatTitle, "secondo") ||
- strings.Contains(chatTitle, "second") {
- return makeResponseWithNextCommand(data.Command + "2")
- } else if strings.Contains(chatTitle, "terzo") ||
- strings.Contains(chatTitle, "third") {
- return makeResponseWithNextCommand(data.Command + "3")
- } else {
- return makeResponseWithText(data.NoYear)
- }
-}
-
-func (data TodayLecturesData) HandleBotCommand(*tgbotapi.BotAPI, *tgbotapi.Message) CommandResponse {
-
- response, err := commands.GetTimeTable(data.Course.Type, data.Course.Name, data.Course.Year, time.Now())
- if err != nil {
- log.Printf("Error [TodayLecturesData]: %s\n", err)
- return makeResponseWithText("Bot internal Error, contact developers")
- }
-
- var msg string
- if response != "" {
- msg = data.Title + response
- } else {
- msg = data.FallbackText
- }
-
- return makeResponseWithText(msg)
-}
-
-func (data TomorrowLecturesData) HandleBotCommand(*tgbotapi.BotAPI, *tgbotapi.Message) CommandResponse {
- tomorrowTime := time.Now().AddDate(0, 0, 1)
-
- response, err := commands.GetTimeTable(data.Course.Type, data.Course.Name, data.Course.Year, tomorrowTime)
- if err != nil {
- log.Printf("Error [TomorrowLecturesData]: %s\n", err)
- return makeResponseWithText("Bot internal Error, contact developers")
- }
-
- var msg string
- if response != "" {
- msg = data.Title + response
- } else {
- msg = data.FallbackText
- }
-
- return makeResponseWithText(msg)
+func (data Lectures) HandleBotCommand(_ *tgbotapi.BotAPI, message *tgbotapi.Message) CommandResponse {
+ rows := GetTimetableCoursesRows(&Timetables)
+ keyboard := tgbotapi.NewInlineKeyboardMarkup(rows...)
+ return makeResponseWithInlineKeyboard(keyboard)
}
func (data ListData) HandleBotCommand(*tgbotapi.BotAPI, *tgbotapi.Message) CommandResponse {
diff --git a/model/description.go b/model/description.go
index c8e988f..1fc7548 100644
--- a/model/description.go
+++ b/model/description.go
@@ -20,15 +20,7 @@ func (d NotLookingForData) GetDescription() string {
return d.Description
}
-func (d YearlyData) GetDescription() string {
- return d.Description
-}
-
-func (d TodayLecturesData) GetDescription() string {
- return d.Description
-}
-
-func (d TomorrowLecturesData) GetDescription() string {
+func (d Lectures) GetDescription() string {
return d.Description
}
diff --git a/model/globals.go b/model/globals.go
index cb4502b..94fa601 100644
--- a/model/globals.go
+++ b/model/globals.go
@@ -19,6 +19,7 @@ var (
Settings SettingsStruct
Teachings map[string]Teaching
Groups GroupsStruct
+ Timetables map[string]Timetable
Mantainers []Mantainer
)
@@ -59,9 +60,13 @@ func InitGlobals() {
log.Fatalf("Error reading or creating groups.json file: %s", err.Error())
}
+ Timetables, err = ParseTimetables()
+ if err != nil {
+ log.Fatalf(err.Error())
+ }
+
Mantainers, err = ParseMantainers()
if err != nil {
log.Fatalf("Error parsing mantainers.json file: %s", err.Error())
}
-
}
diff --git a/model/model.go b/model/model.go
index e89c90e..4e98815 100644
--- a/model/model.go
+++ b/model/model.go
@@ -9,6 +9,7 @@ import (
type DataInterface interface {
HandleBotCommand(bot *tgbotapi.BotAPI, message *tgbotapi.Message) CommandResponse
+ HandleBotCallback(bot *tgbotapi.BotAPI, update *tgbotapi.Update, callback_text string)
GetDescription() string
}
@@ -27,12 +28,8 @@ func GetActionFromType(name string, commandType string) Action {
data = LookingForData{}
case "notLookingFor":
data = NotLookingForData{}
- case "yearly":
- data = YearlyData{}
- case "todayLectures":
- data = TodayLecturesData{}
- case "tomorrowLectures":
- data = TomorrowLecturesData{}
+ case "buttonsLecture":
+ data = Lectures{}
case "list":
data = ListData{}
case "luck":
@@ -107,6 +104,23 @@ type Degree struct {
Chat string `json:"chat"`
}
+// timetables.json
+
+type Curriculum struct {
+ Name string `json:"name"`
+ Callback string `json:"callback"`
+}
+
+// Recognized by a callback string
+type Timetable struct {
+ Course string `json:"course"` // Course title
+ Name string `json:"name"` // Course name
+ Type string `json:"type"` // Type (laurea|magistrale|2cycle)
+ Curriculum string `json:"curricula"` // Curriculum
+ Title string `json:"title"`
+ FallbackText string `json:"fallbackText"`
+}
+
// SECTION ACTION STRUCTS DATA
type MessageData struct {
Text string `json:"text"`
@@ -138,27 +152,12 @@ type NotLookingForData struct {
NotFoundError string `json:"notFoundError"`
}
-type YearlyData struct {
- Description string `json:"description"`
- Command string `json:"command"`
- NoYear string `json:"noYear"`
-}
-
-type CourseId struct {
- Type string `json:"type"`
- Name string `json:"name"`
- Year int `json:"year"`
-}
-
-type TodayLecturesData struct {
- Description string `json:"description"`
- Course CourseId `json:"course"`
- Title string `json:"title"`
- FallbackText string `json:"fallbackText"`
+type Lectures struct {
+ Description string `json:"description"`
+ Title string `json:"title"`
+ FallbackText string `json:"fallbackText"`
}
-type TomorrowLecturesData TodayLecturesData
-
type ListData struct {
Description string `json:"description"`
Header string `json:"header"`
diff --git a/model/parse.go b/model/parse.go
index 263d1d5..6b856cf 100644
--- a/model/parse.go
+++ b/model/parse.go
@@ -16,11 +16,12 @@ import (
)
const (
- jsonPath = "./json/"
- groupsFile = "groups.json"
- configSubpath = "config/"
- degreesFile = "degrees.json"
- teachingsFile = "teachings.json"
+ jsonPath = "./json/"
+ groupsFile = "groups.json"
+ configSubpath = "config/"
+ degreesFile = "degrees.json"
+ teachingsFile = "teachings.json"
+ timetablesFile = "timetables.json"
)
func ParseAutoReplies() (autoReplies []AutoReply, err error) {
@@ -207,6 +208,25 @@ func ParseOrCreateGroups() (GroupsStruct, error) {
func SaveGroups(groups GroupsStruct) error { return utils.WriteJSONFile(groupsFile, groups) }
+func ParseTimetables() (timetables map[string]Timetable, err error) {
+ filepath := filepath.Join(jsonPath, configSubpath, timetablesFile)
+ file, err := os.Open(filepath)
+ defer file.Close()
+ if err != nil {
+ return nil, fmt.Errorf("error reading %s file: %w", timetablesFile, err)
+ }
+
+ var mapData map[string]Timetable
+
+ err = json.NewDecoder(file).Decode(&mapData)
+ if err != nil {
+ return nil, fmt.Errorf("error parsing %s file: %w", filepath, err)
+ }
+
+ timetables = mapData
+ return
+}
+
func ParseMantainers() (mantainer []Mantainer, err error) {
file, err := os.ReadFile("./json/config/mantainers.json")
if errors.Is(err, os.ErrNotExist) {
diff --git a/model/responses.go b/model/responses.go
index fa7961c..da80be8 100644
--- a/model/responses.go
+++ b/model/responses.go
@@ -2,29 +2,39 @@
// between the computations of the bot and the driver code, in bot.go
package model
+import tgbotapi "github.com/musianisamuele/telegram-bot-api"
+
// CommandResponse is returned by the command handler, it contains information
// about the command computation.
type CommandResponse struct {
Text string
NextCommand string
+ Rows tgbotapi.InlineKeyboardMarkup
}
// makeResponse creates a CommandResponse with the given text and nextCommand
-func makeResponse(text string, nextCommand string) CommandResponse {
+func makeResponse(text string, nextCommand string, rows tgbotapi.InlineKeyboardMarkup) CommandResponse {
return CommandResponse{
Text: text,
NextCommand: nextCommand,
+ Rows: rows,
}
}
// makeResponseWithText creates a CommandResponse with the given text (and no nextCommand)
func makeResponseWithText(text string) CommandResponse {
- return makeResponse(text, "")
+ return makeResponse(text, "", tgbotapi.InlineKeyboardMarkup{})
}
// makeResponseWithNextCommand creates a CommandResponse with the given nextCommand (and no text)
func makeResponseWithNextCommand(nextCommand string) CommandResponse {
- return makeResponse("", nextCommand)
+ return makeResponse("", nextCommand, tgbotapi.InlineKeyboardMarkup{})
+}
+
+// makeResponseWithInlineKeyboard creates a CommandResponse with the given array
+// of elements for the keyboard array.
+func makeResponseWithInlineKeyboard(rows tgbotapi.InlineKeyboardMarkup) CommandResponse {
+ return makeResponse("", "", rows)
}
// IsEmpty returns true if the CommandResponse has no text and no nextCommand
@@ -41,3 +51,8 @@ func (r CommandResponse) HasText() bool {
func (r CommandResponse) HasNextCommand() bool {
return r.NextCommand != ""
}
+
+// HasButtonRows returns true if the CommandResponse has some rows
+func (r CommandResponse) HasRows() bool {
+ return len(r.Rows.InlineKeyboard) > 0
+}
diff --git a/model/responses_test.go b/model/responses_test.go
index 67a0294..d5ba23b 100644
--- a/model/responses_test.go
+++ b/model/responses_test.go
@@ -3,6 +3,8 @@ package model
import (
"reflect"
"testing"
+
+ tgbotapi "github.com/musianisamuele/telegram-bot-api"
)
func TestCommandResponse_IsEmpty(t *testing.T) {
@@ -160,7 +162,7 @@ func Test_makeResponse(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- if got := makeResponse(tt.args.text, tt.args.nextCommand); !reflect.DeepEqual(got, tt.want) {
+ if got := makeResponse(tt.args.text, tt.args.nextCommand, tgbotapi.InlineKeyboardMarkup{}); !reflect.DeepEqual(got, tt.want) {
t.Errorf("makeResponse() = %v, want %v", got, tt.want)
}
})
diff --git a/model/timetables.go b/model/timetables.go
new file mode 100644
index 0000000..ed980b5
--- /dev/null
+++ b/model/timetables.go
@@ -0,0 +1,67 @@
+package model
+
+import (
+ "fmt"
+ "strings"
+ "time"
+
+ tgbotapi "github.com/musianisamuele/telegram-bot-api"
+)
+
+type InlineKeyboardRows [][]tgbotapi.InlineKeyboardButton
+
+// Returns a group of button rows for a selected groups on `timetables`
+func GetTimetableCoursesRows(timetables *map[string]Timetable) InlineKeyboardRows {
+ rows := make([][]tgbotapi.InlineKeyboardButton, len(*timetables))
+
+ i := 0
+ for callback, timetable := range *timetables {
+ row := tgbotapi.NewInlineKeyboardRow(tgbotapi.NewInlineKeyboardButtonData(timetable.Course, fmt.Sprintf("lectures_%s", callback)))
+ rows[i] = row
+ i++
+ }
+
+ return rows
+}
+
+// Returns buttons which permits to choose the day for the timetable
+func ChooseTimetableDay(callback_text string) InlineKeyboardRows {
+ rows := make([][]tgbotapi.InlineKeyboardButton, 7)
+ var weekdays = [7]string{
+ "Domenica", "Lunedì", "Martedì", "Mercoledì", "Giovedì", "Venerdì", "Sabato",
+ }
+ var months = [12]string{
+ "Dicembre", "Gennaio", "Febbraio", "Marzo", "Aprile", "Maggio", "Giugno", "Luglio", "Agosto", "Settembre", "Ottobre", "Novembre",
+ }
+
+ dt := time.Now()
+
+ for day := 0; day < 7; day++ {
+ rows[day] = tgbotapi.NewInlineKeyboardRow(tgbotapi.NewInlineKeyboardButtonData(fmt.Sprintf("%s %d %s", weekdays[dt.Weekday()%7], dt.Day(), months[dt.Month()%12]), fmt.Sprintf("%s_day_%d", callback_text, dt.Unix())))
+ dt = dt.AddDate(0, 0, 1)
+ }
+
+ return rows
+}
+
+// Returns a group of buttons rows for the available years of a `course`
+func GetLectureYears(callback_text string, course string) InlineKeyboardRows {
+ yearsNro := 3
+ // Master degrees has a duration of only 2 years
+ if strings.HasPrefix(callback_text, "lectures_lm") {
+ yearsNro = 2
+ }
+ rows := make([][]tgbotapi.InlineKeyboardButton, yearsNro)
+
+ i := 1
+ for i <= yearsNro {
+ buttonText := fmt.Sprintf("%s: %d\u00b0 anno", course, i)
+ buttonCallback := fmt.Sprintf("%s_y_%d", callback_text, i)
+ row := tgbotapi.NewInlineKeyboardRow(tgbotapi.NewInlineKeyboardButtonData(buttonText, buttonCallback))
+ rows[i-1] = row
+
+ i++
+ }
+
+ return rows
+}
diff --git a/model/timetables_test.go b/model/timetables_test.go
new file mode 100644
index 0000000..d1e10c0
--- /dev/null
+++ b/model/timetables_test.go
@@ -0,0 +1,157 @@
+package model
+
+import (
+ "fmt"
+ "testing"
+ "time"
+
+ tgbotapi "github.com/musianisamuele/telegram-bot-api"
+)
+
+func TestGetTimetableCoursesRows(t *testing.T) {
+ timetables := make([]map[string]Timetable, 2)
+
+ timetables[0] = map[string]Timetable{
+ "l_informatica": {
+ Course: "Informatica",
+ Type: "laurea",
+ Name: "informatica",
+ },
+ }
+ timetables[1] = map[string]Timetable{
+ "lm_informatica_software_techniques": {
+ Course: "Informatica Magistrale - Tecniche del software",
+ Type: "magistrale",
+ Name: "informatica",
+ Curriculum: "A58-000",
+ },
+ }
+ wants := make([]InlineKeyboardRows, 2)
+ wants[0] = InlineKeyboardRows{tgbotapi.NewInlineKeyboardRow(tgbotapi.NewInlineKeyboardButtonData("Informatica", "lectures_l_informatica"))}
+ wants[1] = InlineKeyboardRows{tgbotapi.NewInlineKeyboardRow(tgbotapi.NewInlineKeyboardButtonData("Informatica Magistrale - Tecniche del software", "lectures_lm_informatica_software_techniques"))}
+
+ type args struct {
+ data []map[string]Timetable
+ }
+ tests := []struct {
+ name string
+ args args
+ want []InlineKeyboardRows
+ }{
+ {
+ name: "All the timetables from the map",
+ args: args{data: timetables},
+ want: wants,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ for idx, timetable := range tt.args.data {
+ var got InlineKeyboardRows = GetTimetableCoursesRows(&timetable)
+ if len(got) != len(tt.want[idx]) {
+ t.Errorf("GetTimetableCoursesRows() = %v, want %v", got, tt.want)
+ } else {
+ for i, v := range got {
+ for j, w := range v {
+ if w.Text != tt.want[idx][i][j].Text || *w.CallbackData != *tt.want[idx][i][j].CallbackData {
+ t.Errorf("GetTimetableCoursesRows() = %v, want %v", w, tt.want[idx][i][j])
+ }
+ }
+ }
+ }
+ }
+ })
+ }
+
+}
+
+func TestChooseTimetableDay(t *testing.T) {
+ dt := time.Now()
+ var weekdays = [7]string{
+ "Domenica", "Lunedì", "Martedì", "Mercoledì", "Giovedì", "Venerdì", "Sabato",
+ }
+ var months = [12]string{
+ "Dicembre", "Gennaio", "Febbraio", "Marzo", "Aprile", "Maggio", "Giugno", "Luglio", "Agosto", "Settembre", "Ottobre", "Novembre",
+ }
+
+ type args struct {
+ data string
+ }
+ tests := []struct {
+ name string
+ args args
+ want InlineKeyboardRows
+ }{
+ {
+ name: "Get lectures for the week",
+ args: args{data: "lectures_lm_informatica_software_techniques"},
+ want: make([][]tgbotapi.InlineKeyboardButton, 7),
+ },
+ }
+
+ for day := 0; day < 7; day++ {
+ tests[0].want[day] = tgbotapi.NewInlineKeyboardRow(tgbotapi.NewInlineKeyboardButtonData(fmt.Sprintf("%s %d %s", weekdays[dt.Weekday()%7], dt.Day(), months[dt.Month()%12]), fmt.Sprintf("%s_day_%d", "lectures_lm_informatica_software_techniques", dt.Unix())))
+ dt = dt.AddDate(0, 0, 1)
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ var got InlineKeyboardRows = ChooseTimetableDay(tt.args.data)
+ if len(got) != len(tt.want) {
+ t.Errorf("ChooseTimetableDay() = %v, want %v", got, tt.want)
+ } else {
+ for i, v := range got {
+ for j, w := range v {
+ if w.Text != tt.want[i][j].Text || *w.CallbackData != *tt.want[i][j].CallbackData {
+ t.Errorf("ChooseTimetableDay() = %v, want %v", w, tt.want[i][j])
+ }
+ }
+ }
+ }
+ })
+ }
+}
+
+func TestGetLectureYears(t *testing.T) {
+ type args struct {
+ data [2]string
+ }
+ tests := []struct {
+ name string
+ args args
+ want InlineKeyboardRows
+ }{
+ {
+ name: "Get rows for bachelor's degree",
+ args: args{data: [2]string{"lectures_l_informatica", "Informatica"}},
+ want: InlineKeyboardRows{
+ tgbotapi.NewInlineKeyboardRow(tgbotapi.NewInlineKeyboardButtonData("Informatica: 1\u00b0 anno", "lectures_l_informatica_y_1")),
+ tgbotapi.NewInlineKeyboardRow(tgbotapi.NewInlineKeyboardButtonData("Informatica: 2\u00b0 anno", "lectures_l_informatica_y_2")),
+ tgbotapi.NewInlineKeyboardRow(tgbotapi.NewInlineKeyboardButtonData("Informatica: 3\u00b0 anno", "lectures_l_informatica_y_3")),
+ },
+ },
+ {
+ name: "Get rows for master's degree",
+ args: args{data: [2]string{"lectures_lm_informatica_software_techniques", "Informatica Magistrale"}},
+ want: InlineKeyboardRows{
+ tgbotapi.NewInlineKeyboardRow(tgbotapi.NewInlineKeyboardButtonData("Informatica Magistrale: 1\u00b0 anno", "lectures_lm_informatica_software_techniques_y_1")),
+ tgbotapi.NewInlineKeyboardRow(tgbotapi.NewInlineKeyboardButtonData("Informatica Magistrale: 2\u00b0 anno", "lectures_lm_informatica_software_techniques_y_2")),
+ },
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ var got InlineKeyboardRows = GetLectureYears(tt.args.data[0], tt.args.data[1])
+ if len(got) != len(tt.want) {
+ t.Errorf("GetLectureYears() = %v, want %v", got, tt.want)
+ } else {
+ for i, v := range got {
+ for j, w := range v {
+ if w.Text != tt.want[i][j].Text || *w.CallbackData != *tt.want[i][j].CallbackData {
+ t.Errorf("GetLectureYears() = %v, want %v", w, tt.want[i][j])
+ }
+ }
+ }
+ }
+ })
+ }
+}