diff --git a/bot/bot.go b/bot/bot.go
index 01aa6e6..24575a0 100644
--- a/bot/bot.go
+++ b/bot/bot.go
@@ -4,10 +4,11 @@ import (
"log"
"strings"
- "github.com/csunibo/informabot/model"
- "github.com/csunibo/informabot/utils"
tgbotapi "github.com/musianisamuele/telegram-bot-api"
"golang.org/x/exp/slices"
+
+ "github.com/csunibo/informabot/model"
+ "github.com/csunibo/informabot/utils"
)
func StartInformaBot(token string, debug bool) {
@@ -42,19 +43,18 @@ func run(bot *tgbotapi.BotAPI) {
handleCommand(bot, &update)
} else {
// text message
- for i := 0; i < len(model.Autoreplies); i++ {
+ for i := 0; i < len(model.AutoReplies); i++ {
if strings.Contains(strings.ToLower(update.Message.Text),
- strings.ToLower(model.Autoreplies[i].Text)) {
+ strings.ToLower(model.AutoReplies[i].Text)) {
var msg tgbotapi.MessageConfig
if update.Message.IsTopicMessage {
msg = tgbotapi.NewThreadMessage(update.Message.Chat.ID,
- update.Message.MessageThreadID, model.Autoreplies[i].Reply)
+ update.Message.MessageThreadID, model.AutoReplies[i].Reply)
} else {
msg = tgbotapi.NewMessage(update.Message.Chat.ID,
- model.Autoreplies[i].Reply)
+ model.AutoReplies[i].Reply)
}
-
msg.ReplyToMessageID = update.Message.MessageID
utils.SendHTML(bot, msg)
}
diff --git a/commands/uni.go b/commands/uni.go
index 05d91b7..c298ad1 100644
--- a/commands/uni.go
+++ b/commands/uni.go
@@ -3,11 +3,12 @@ package commands
import (
"encoding/json"
"fmt"
- "io/ioutil"
"log"
- "net/http"
- "sort"
+ "strings"
"time"
+
+ "github.com/csunibo/unibo-go/timetable"
+ "golang.org/x/exp/slices"
)
const TIMEFORMAT = "2006-01-02T15:04:05"
@@ -47,32 +48,34 @@ func (t *LezioniTime) UnmarshalJSON(data []byte) error {
return nil
}
-// GetTimeTable returns the timetable for the Unibo url
-// returns empty string if there are no lessons.
-func GetTimeTable(url string) string {
- resp, err := http.Get(url)
+// 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) {
+
+ interval := &timetable.Interval{Start: day, End: day}
+ events, err := timetable.FetchTimetable(courseType, courseName, "", year, interval)
if err != nil {
- log.Printf("Error getting json when requesting orario lezioni: %s\n", err)
+ log.Printf("Error getting timetable: %s\n", err)
+ return "", err
}
- defer resp.Body.Close()
-
- result := []OrarioLezioni{}
- body, _ := ioutil.ReadAll(resp.Body)
- json.Unmarshal(body, &result)
- sort.Slice(result, func(i, j int) bool {
- return (*time.Time)(&result[i].StartTime).Before((time.Time)(result[j].StartTime))
+ // Sort the events by start time
+ slices.SortFunc(events, func(a, b timetable.Event) int {
+ return int(b.Start.Time.Sub(a.Start.Time).Nanoseconds())
})
- var message string = ""
- for _, lezione := range result {
- message += fmt.Sprintf(` π %s`, lezione.Teams, lezione.Title) +
- "\n" + lezione.Time + "\n"
- if len(lezione.Aule) > 0 {
- message += fmt.Sprintf(" π’ %s - %s\n", lezione.Aule[0].Edificio, lezione.Aule[0].Piano)
- message += fmt.Sprintf(" π %s\n", lezione.Aule[0].Indirizzo)
+ b := strings.Builder{}
+ for _, event := range events {
+ b.WriteString(fmt.Sprintf(` π %s`, event.Teams, event.Title))
+ b.WriteString("\n")
+ b.WriteString(event.Start.Format("15:04"))
+ b.WriteString("\n")
+ if len(event.Classrooms) > 0 {
+ // TODO(VaiTon): Re-implement this
+ //b.WriteString(fmt.Sprintf(" π’ %s - %s\n", event.Classrooms[0].Edificio, event.Aule[0].Piano))
+ //b.WriteString(fmt.Sprintf(" π %s\n", event.Classrooms[0].Indirizzo))
}
}
- return message
+ return b.String(), nil
}
diff --git a/commands/uni_test.go b/commands/uni_test.go
index 1b1d5f8..48f3133 100644
--- a/commands/uni_test.go
+++ b/commands/uni_test.go
@@ -47,7 +47,7 @@ func TestLezioniTime_Format(t *testing.T) {
}
func TestLezioniTime_UnmarshalJSON(t *testing.T) {
- var time LezioniTime
+ var lezioniTime LezioniTime
type args struct {
data []byte
}
@@ -59,7 +59,7 @@ func TestLezioniTime_UnmarshalJSON(t *testing.T) {
}{
{
name: "Not an error",
- tr: &time,
+ tr: &lezioniTime,
args: args{
data: []byte{34, 50, 48, 50, 51, 45, 48, 51, 45, 49, 51, 84, 49, 50, 58, 48, 48, 58, 48, 48, 34},
},
@@ -67,7 +67,7 @@ func TestLezioniTime_UnmarshalJSON(t *testing.T) {
},
{
name: "Error #1",
- tr: &time,
+ tr: &lezioniTime,
args: args{
data: []byte("A"),
},
@@ -75,7 +75,7 @@ func TestLezioniTime_UnmarshalJSON(t *testing.T) {
},
{
name: "Error #2",
- tr: &time,
+ tr: &lezioniTime,
args: args{
data: []byte{},
},
@@ -83,7 +83,7 @@ func TestLezioniTime_UnmarshalJSON(t *testing.T) {
},
{
name: "Error #2",
- tr: &time,
+ tr: &lezioniTime,
args: args{
data: []byte{34},
},
@@ -102,44 +102,72 @@ func TestLezioniTime_UnmarshalJSON(t *testing.T) {
func TestGetTimeTable(t *testing.T) {
type args struct {
- url string
+ courseType string
+ courseName string
+ year int
+ day time.Time
}
tests := []struct {
- name string
- args args
- want string
+ name string
+ args args
+ want string
+ error bool
}{
{
name: "Weekend",
args: args{
- url: "https://corsi.unibo.it/laurea/informatica/orario-lezioni/@@orario_reale_json?anno=1&start=2023-03-11&end=2023-03-11",
+ courseType: "laurea",
+ courseName: "informatica",
+ year: 1,
+ day: time.Date(2023, 3, 11, 0, 0, 0, 0, time.UTC),
},
want: "",
},
{
name: "Weekday",
args: args{
- url: "https://corsi.unibo.it/laurea/informatica/orario-lezioni/@@orario_reale_json?anno=1&start=2023-10-31&end=2023-10-31",
+ courseType: "laurea",
+ courseName: "informatica",
+ year: 1,
+ day: time.Date(2023, 10, 31, 0, 0, 0, 0, time.UTC),
},
want: `π`,
},
{
name: "Not a valid url",
args: args{
- url: "https://example.com",
+ courseType: "test",
+ courseName: "test",
},
- want: "",
+ want: "",
+ error: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- got := GetTimeTable(tt.args.url)
- got = strings.ReplaceAll(got, " ", "")
- want := strings.ReplaceAll(tt.want, " ", "")
- want = strings.ReplaceAll(want, "\t", "")
- if !strings.Contains(got, want) {
- t.Errorf("GetTimeTable() = %v, want %v", got, want)
+ got, err := GetTimeTable(tt.args.courseType, tt.args.courseName, tt.args.year, tt.args.day)
+ if err != nil && !tt.error {
+ t.Errorf("GetTimeTable() error = %v", err)
+ return
+ } else {
+ got = strings.ReplaceAll(got, " ", "")
+ want := strings.ReplaceAll(tt.want, " ", "")
+ want = strings.ReplaceAll(want, "\t", "")
+ if !strings.Contains(got, want) {
+ t.Errorf("GetTimeTable() = %v, want %v", got, want)
+ }
}
})
}
}
+func TestWeekend(t *testing.T) {
+
+ date := time.Date(2023, 3, 11, 0, 0, 0, 0, time.UTC)
+ result, err := GetTimeTable("laurea", "informatica", 1, date)
+ if err != nil {
+ t.Fatalf("Error while getting timetable: %s", err)
+ }
+ if result != "" {
+ t.Errorf("Expected empty string in weekend, got %s", result)
+ }
+}
diff --git a/go.mod b/go.mod
index c11cb8c..b2d28b9 100644
--- a/go.mod
+++ b/go.mod
@@ -3,11 +3,9 @@ module github.com/csunibo/informabot
go 1.18
require (
+ github.com/csunibo/unibo-go v0.0.6
github.com/mitchellh/mapstructure v1.5.0
github.com/musianisamuele/telegram-bot-api v0.0.4
-)
-
-require (
- golang.org/x/exp v0.0.0-20230809094429-853ea248256d
+ golang.org/x/exp v0.0.0-20230905200255-921286631fa9
golang.org/x/text v0.13.0
)
diff --git a/go.sum b/go.sum
index ce246ac..52a2f51 100644
--- a/go.sum
+++ b/go.sum
@@ -1,8 +1,10 @@
+github.com/csunibo/unibo-go v0.0.6 h1:7VDtXVTiwJwGveLyG7vP62aKMg/WpqSx2iJiGRaPlDU=
+github.com/csunibo/unibo-go v0.0.6/go.mod h1:h2+xnccHa7x48RNB6d07bpHQ01ozw4oihgDOlvVrJ9U=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/musianisamuele/telegram-bot-api v0.0.4 h1:kQoE4Ih/rnyf2i8iCOwcN+zflx+H1A7+PhZx9Kkqppk=
github.com/musianisamuele/telegram-bot-api v0.0.4/go.mod h1:f8epVo400dyxqaQpXt3la1mnhdQW25R1w3yqYT3GQc4=
-golang.org/x/exp v0.0.0-20230809094429-853ea248256d h1:wu5bD43Ana/nF1ZmaLr3lW/FQeJU8CcI+Ln7yWHViXE=
-golang.org/x/exp v0.0.0-20230809094429-853ea248256d/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
+golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
+golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
diff --git a/json/actions.json b/json/actions.json
index d0094c4..39fe28b 100644
--- a/json/actions.json
+++ b/json/actions.json
@@ -87,7 +87,11 @@
"type": "todayLectures",
"data": {
"description": "Orari lezioni di oggi (1\u00b0 anno)",
- "url": "https://corsi.unibo.it/laurea/informatica/orario-lezioni/@@orario_reale_json?anno=1",
+ "course": {
+ "year": 1,
+ "type": "laurea",
+ "name": "Informatica"
+ },
"title": "Lezioni di oggi (1\u00b0 anno):\n",
"fallbackText": "Non ci sono lezioni oggi. SMETTILA DI PRESSARMI"
}
@@ -96,7 +100,11 @@
"type": "todayLectures",
"data": {
"description": "Orari lezioni di oggi (2\u00b0 anno)",
- "url": "https://corsi.unibo.it/laurea/informatica/orario-lezioni/@@orario_reale_json?anno=2",
+ "course": {
+ "year": 2,
+ "type": "laurea",
+ "name": "Informatica"
+ },
"title": "Lezioni di oggi (2\u00b0 anno):\n",
"fallbackText": "Non ci sono lezioni oggi. SMETTILA DI PRESSARMI"
}
@@ -105,7 +113,11 @@
"type": "todayLectures",
"data": {
"description": "Orari lezioni di oggi (3\u00b0 anno)",
- "url": "https://corsi.unibo.it/laurea/informatica/orario-lezioni/@@orario_reale_json?anno=3",
+ "course": {
+ "year": 3,
+ "type": "laurea",
+ "name": "Informatica"
+ },
"title": "Lezioni di oggi:\n",
"fallbackText": "Non ci sono lezioni oggi. SMETTILA DI PRESSARMI"
}
@@ -122,7 +134,11 @@
"type": "tomorrowLectures",
"data": {
"description": "Orari lezioni di domani (1\u00b0 anno)",
- "url": "https://corsi.unibo.it/laurea/informatica/orario-lezioni/@@orario_reale_json?anno=1",
+ "course": {
+ "year": 1,
+ "type": "laurea",
+ "name": "Informatica"
+ },
"title": "Lezioni di domani (1\u00b0 anno):\n",
"fallbackText": "Non ci sono lezioni domani. SMETTILA DI PRESSARMI"
}
@@ -131,7 +147,11 @@
"type": "tomorrowLectures",
"data": {
"description": "Orari lezioni di domani (2\u00b0 anno)",
- "url": "https://corsi.unibo.it/laurea/informatica/orario-lezioni/@@orario_reale_json?anno=2",
+ "course": {
+ "year": 2,
+ "type": "laurea",
+ "name": "Informatica"
+ },
"title": "Lezioni di domani (2\u00b0 anno):\n",
"fallbackText": "Non ci sono lezioni domani. SMETTILA DI PRESSARMI"
}
@@ -140,7 +160,11 @@
"type": "tomorrowLectures",
"data": {
"description": "Orari lezioni di domani (3\u00b0 anno)",
- "url": "https://corsi.unibo.it/laurea/informatica/orario-lezioni/@@orario_reale_json?anno=3",
+ "course": {
+ "year": 3,
+ "type": "laurea",
+ "name": "Informatica"
+ },
"title": "Lezioni di domani (3\u00b0 anno):\n",
"fallbackText": "Non ci sono lezioni domani. SMETTILA DI PRESSARMI"
}
diff --git a/model/controller.go b/model/controller.go
index ae9300b..210dabd 100644
--- a/model/controller.go
+++ b/model/controller.go
@@ -7,17 +7,18 @@ import (
"strings"
"time"
- "github.com/csunibo/informabot/commands"
- "github.com/csunibo/informabot/utils"
tgbotapi "github.com/musianisamuele/telegram-bot-api"
"golang.org/x/exp/slices"
+
+ "github.com/csunibo/informabot/commands"
+ "github.com/csunibo/informabot/utils"
)
-func (data MessageData) HandleBotCommand(bot *tgbotapi.BotAPI, message *tgbotapi.Message) CommandResponse {
- return makeResponseWithText(data.Text)
+func (d MessageData) HandleBotCommand(bot *tgbotapi.BotAPI, message *tgbotapi.Message) CommandResponse {
+ return makeResponseWithText(d.Text)
}
-func (data HelpData) HandleBotCommand(bot *tgbotapi.BotAPI, message *tgbotapi.Message) CommandResponse {
+func (d HelpData) HandleBotCommand(bot *tgbotapi.BotAPI, message *tgbotapi.Message) CommandResponse {
answer := ""
for _, action := range Actions {
if description := action.Data.GetDescription(); description != "" {
@@ -71,13 +72,13 @@ func (data LookingForData) HandleBotCommand(bot *tgbotapi.BotAPI, message *tgbot
return makeResponseWithText(resultMsg)
}
-func (data NotLookingForData) HandleBotCommand(bot *tgbotapi.BotAPI, message *tgbotapi.Message) CommandResponse {
+func (d NotLookingForData) HandleBotCommand(bot *tgbotapi.BotAPI, message *tgbotapi.Message) CommandResponse {
if (message.Chat.Type != "group" && message.Chat.Type != "supergroup") || slices.Contains(Settings.LookingForBlackList, message.Chat.ID) {
log.Print("Error [NotLookingForData]: not a group or blacklisted")
- return makeResponseWithText(data.ChatError)
+ return makeResponseWithText(d.ChatError)
} else if _, ok := Groups[message.Chat.ID]; !ok {
log.Print("Info [NotLookingForData]: group empty, user not found")
- return makeResponseWithText(fmt.Sprintf(data.NotFoundError, message.Chat.Title))
+ return makeResponseWithText(fmt.Sprintf(d.NotFoundError, message.Chat.Title))
}
var chatId = message.Chat.ID
@@ -87,38 +88,38 @@ func (data NotLookingForData) HandleBotCommand(bot *tgbotapi.BotAPI, message *tg
var msg string
if idx := slices.Index(Groups[chatId], senderId); idx == -1 {
log.Print("Info [NotLookingForData]: user not found in group")
- msg = fmt.Sprintf(data.NotFoundError, chatTitle)
+ msg = fmt.Sprintf(d.NotFoundError, chatTitle)
} else {
Groups[chatId] = append(Groups[chatId][:idx], Groups[chatId][idx+1:]...)
SaveGroups()
- msg = fmt.Sprintf(data.Text, chatTitle)
+ msg = fmt.Sprintf(d.Text, chatTitle)
}
return makeResponseWithText(msg)
}
-func (data YearlyData) HandleBotCommand(bot *tgbotapi.BotAPI, message *tgbotapi.Message) CommandResponse {
+func (d YearlyData) HandleBotCommand(bot *tgbotapi.BotAPI, message *tgbotapi.Message) CommandResponse {
var chatTitle string = strings.ToLower(message.Chat.Title)
// check if string starts with "Yearly"
if strings.Contains(chatTitle, "primo") {
- return makeResponseWithNextCommand(data.Command + "1")
+ return makeResponseWithNextCommand(d.Command + "1")
} else if strings.Contains(chatTitle, "secondo") {
- return makeResponseWithNextCommand(data.Command + "2")
+ return makeResponseWithNextCommand(d.Command + "2")
} else if strings.Contains(chatTitle, "terzo") {
- return makeResponseWithNextCommand(data.Command + "3")
+ return makeResponseWithNextCommand(d.Command + "3")
} else {
- return makeResponseWithText(data.NoYear)
+ return makeResponseWithText(d.NoYear)
}
}
-func (data TodayLecturesData) HandleBotCommand(bot *tgbotapi.BotAPI, message *tgbotapi.Message) CommandResponse {
- var todayTime time.Time = time.Now()
- var todayString string = todayTime.Format("2006-01-02")
- url := data.Url + fmt.Sprintf("&start=%s&end=%s", todayString, todayString)
- log.Printf("URL: %s\n", url)
+func (data TodayLecturesData) HandleBotCommand(_ *tgbotapi.BotAPI, _ *tgbotapi.Message) CommandResponse {
- var response string = commands.GetTimeTable(url)
+ 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") // TODO(VaiTon): better error message
+ }
var msg string
if response != "" {
@@ -130,25 +131,26 @@ func (data TodayLecturesData) HandleBotCommand(bot *tgbotapi.BotAPI, message *tg
return makeResponseWithText(msg)
}
-func (data TomorrowLecturesData) HandleBotCommand(bot *tgbotapi.BotAPI, message *tgbotapi.Message) CommandResponse {
- var todayTime time.Time = time.Now()
- var tomorrowTime time.Time = todayTime.AddDate(0, 0, 1)
- var tomorrowString string = tomorrowTime.Format("2006-01-02")
- url := data.Url + fmt.Sprintf("&start=%s&end=%s", tomorrowString, tomorrowString)
+func (d TomorrowLecturesData) HandleBotCommand(*tgbotapi.BotAPI, *tgbotapi.Message) CommandResponse {
+ tomorrowTime := time.Now().AddDate(0, 0, 1)
- var response string = commands.GetTimeTable(url)
+ response, err := commands.GetTimeTable(d.Course.Type, d.Course.Name, d.Course.Year, tomorrowTime)
+ if err != nil {
+ log.Printf("Error [TomorrowLecturesData]: %s\n", err)
+ return makeResponseWithText("Bot internal Error, contact developers") // TODO: better error message
+ }
var msg string
if response != "" {
- msg = data.Title + response
+ msg = d.Title + response
} else {
- msg = data.FallbackText
+ msg = d.FallbackText
}
return makeResponseWithText(msg)
}
-func (data ListData) HandleBotCommand(bot *tgbotapi.BotAPI, message *tgbotapi.Message) CommandResponse {
+func (data ListData) HandleBotCommand(*tgbotapi.BotAPI, *tgbotapi.Message) CommandResponse {
resultText := data.Header
for _, item := range data.Items {
@@ -162,43 +164,55 @@ func (data ListData) HandleBotCommand(bot *tgbotapi.BotAPI, message *tgbotapi.Me
return makeResponseWithText(resultText)
}
-const BEGINNING_MONTH = time.September
+const BeginningMonth = time.September
func getCurrentAcademicYear() int {
-
now := time.Now()
year := now.Year()
- if now.Month() >= BEGINNING_MONTH {
+ if now.Month() >= BeginningMonth {
return year
} else {
return year - 1
}
}
-func (data CourseData) HandleBotCommand(bot *tgbotapi.BotAPI, message *tgbotapi.Message) CommandResponse {
- emails := strings.Join(data.Professors, "@unibo.it\n ") + "@unibo.it\n"
- ternary_assignment := func(condition bool, true_value string) string {
- if condition {
- return true_value
- } else {
- return ""
- }
- }
+func (data CourseData) HandleBotCommand(*tgbotapi.BotAPI, *tgbotapi.Message) CommandResponse {
currentAcademicYear := fmt.Sprint(getCurrentAcademicYear())
- msg := ternary_assignment(data.Name != "", fmt.Sprintf("%s\n", data.Name)) +
- ternary_assignment(data.Website != "", fmt.Sprintf("Sito\nOrario", currentAcademicYear, data.Website, currentAcademicYear, data.Website)+"\n") +
- ternary_assignment(data.Professors != nil, fmt.Sprintf("Professori:\n %s", emails)) +
- ternary_assignment(data.Name != "", fmt.Sprintf("π Risorse (istanza principale)\n", utils.ToKebabCase(data.Name))) +
- ternary_assignment(data.Name != "", fmt.Sprintf("π Risorse (istanza di riserva 1)\n", utils.ToKebabCase(data.Name))) +
- ternary_assignment(data.Name != "", fmt.Sprintf("π Risorse (istanza di riserva 2)\n", utils.ToKebabCase(data.Name))) +
- ternary_assignment(data.Name != "", fmt.Sprintf("π Repository GitHub delle risorse\n", utils.ToKebabCase(data.Name))) +
- ternary_assignment(data.Telegram != "", fmt.Sprintf("π₯ Gruppo Studenti\n", data.Telegram))
- return makeResponseWithText(msg)
+ var b strings.Builder
+
+ if data.Name != "" {
+ b.WriteString(fmt.Sprintf("%s\n", data.Name))
+ }
+
+ if data.Website != "" {
+ b.WriteString(fmt.Sprintf("Sito\n",
+ currentAcademicYear, data.Website))
+ b.WriteString(fmt.Sprintf("Orario\n",
+ currentAcademicYear, data.Website))
+ }
+
+ if data.Professors != nil {
+ emails := strings.Join(data.Professors, "@unibo.it\n ") + "@unibo.it\n"
+ b.WriteString(fmt.Sprintf("Professori:\n %s", emails))
+ }
+
+ if data.Name != "" {
+ b.WriteString(fmt.Sprintf("π Risorse (istanza principale)\n", utils.ToKebabCase(data.Name)))
+ b.WriteString(fmt.Sprintf("π Risorse (istanza di riserva 1)\n", utils.ToKebabCase(data.Name)))
+ b.WriteString(fmt.Sprintf("π Risorse (istanza di riserva 2)\n", utils.ToKebabCase(data.Name)))
+ b.WriteString(fmt.Sprintf("π Repository GitHub delle risorse\n", utils.ToKebabCase(data.Name)))
+ }
+
+ if data.Telegram != "" {
+ b.WriteString(fmt.Sprintf("π₯ Gruppo Studenti\n", data.Telegram))
+ }
+
+ return makeResponseWithText(b.String())
}
-func (data LuckData) HandleBotCommand(bot *tgbotapi.BotAPI, message *tgbotapi.Message) CommandResponse {
+func (data LuckData) HandleBotCommand(_ *tgbotapi.BotAPI, message *tgbotapi.Message) CommandResponse {
var emojis = []string{"π²", "π―", "π", "β½", "π³", "π°"}
var noLuckGroups = []int64{-1563447632} // NOTE: better way to handle this?
@@ -221,8 +235,7 @@ func (data LuckData) HandleBotCommand(bot *tgbotapi.BotAPI, message *tgbotapi.Me
return makeResponseWithText(msg)
}
-func (data InvalidData) HandleBotCommand(bot *tgbotapi.BotAPI, message *tgbotapi.Message) CommandResponse {
+func (data InvalidData) HandleBotCommand(*tgbotapi.BotAPI, *tgbotapi.Message) CommandResponse {
log.Printf("Probably a bug in the JSON action dictionary, got invalid data in command")
-
return makeResponseWithText("Bot internal Error, contact developers")
}
diff --git a/model/description.go b/model/description.go
index 9da37ec..67768f8 100644
--- a/model/description.go
+++ b/model/description.go
@@ -8,8 +8,8 @@ func (d HelpData) GetDescription() string {
return d.Description
}
-func (d LookingForData) GetDescription() string {
- return d.Description
+func (data LookingForData) GetDescription() string {
+ return data.Description
}
func (d NotLookingForData) GetDescription() string {
@@ -20,26 +20,26 @@ func (d YearlyData) GetDescription() string {
return d.Description
}
-func (d TodayLecturesData) GetDescription() string {
- return d.Description
+func (data TodayLecturesData) GetDescription() string {
+ return data.Description
}
func (d TomorrowLecturesData) GetDescription() string {
return d.Description
}
-func (d ListData) GetDescription() string {
- return d.Description
+func (data ListData) GetDescription() string {
+ return data.Description
}
-func (d CourseData) GetDescription() string {
- return d.Description
+func (data CourseData) GetDescription() string {
+ return data.Description
}
-func (d LuckData) GetDescription() string {
- return d.Description
+func (data LuckData) GetDescription() string {
+ return data.Description
}
-func (d InvalidData) GetDescription() string {
+func (data InvalidData) GetDescription() string {
return "This data is invalidly parsed, please report this bug to the developer."
}
diff --git a/model/globals.go b/model/globals.go
index 45ca9e3..db96d12 100644
--- a/model/globals.go
+++ b/model/globals.go
@@ -1,16 +1,18 @@
+// This file contains all the global variables of the bot, that are initialized
+// with the start of the bot.
+//
+// This file should be here because it had circular imports with the Model (bot
+// imported model, which imported bot in order to access the global variables,
+// especially for the settings)
+
package model
import (
"log"
)
-// This file contains all the global variables of the bot, that are initialized
-// with the start of the bot, this file should be here because it had circular
-// imports with the Model (bot imported model, which imported bot in order to access
-// the global variables (expecially for the settings))
-
var (
- Autoreplies []AutoReply
+ AutoReplies []AutoReply
Actions []Action
MemeList []Meme
Settings SettingsStruct
@@ -19,7 +21,7 @@ var (
func InitGlobals() {
var err error
- Autoreplies, err = ParseAutoReplies()
+ AutoReplies, err = ParseAutoReplies()
if err != nil {
log.Fatalf("Error reading autoreply.json file: %s", err.Error())
}
diff --git a/model/model.go b/model/model.go
index 195fb6e..057e2e2 100644
--- a/model/model.go
+++ b/model/model.go
@@ -1,4 +1,6 @@
-// In this file we define all the structs used to parse JSON files into Go structs
+// In this file we define all the structs used to parse JSON files into Go
+// structs
+
package model
import (
@@ -10,6 +12,8 @@ type DataInterface interface {
GetDescription() string
}
+// GetActionFromType returns an Action struct with the right DataInterface,
+// inferred from the commandType string
func GetActionFromType(name string, commandType string) Action {
var data DataInterface
switch commandType {
@@ -98,11 +102,17 @@ type YearlyData struct {
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"`
- Url string `json:"url"`
- Title string `json:"title"`
- FallbackText string `json:"fallbackText"`
+ Description string `json:"description"`
+ Course CourseId `json:"course"`
+ Title string `json:"title"`
+ FallbackText string `json:"fallbackText"`
}
type TomorrowLecturesData TodayLecturesData
diff --git a/model/parse.go b/model/parse.go
index ad48ebe..e808c54 100644
--- a/model/parse.go
+++ b/model/parse.go
@@ -2,58 +2,55 @@ package model
import (
"encoding/json"
- "io/ioutil"
- "log"
+ "errors"
+ "fmt"
"os"
+ "strings"
- "github.com/csunibo/informabot/utils"
"github.com/mitchellh/mapstructure"
"golang.org/x/exp/slices"
+
+ "github.com/csunibo/informabot/utils"
)
const groupsPath = "./json/groups.json"
func ParseAutoReplies() ([]AutoReply, error) {
- jsonFile, err := os.Open("./json/autoreply.json")
+ byteValue, err := os.ReadFile("./json/autoreply.json")
if err != nil {
- log.Println(err)
- return nil, err
+ return nil, fmt.Errorf("error reading autoreply.json file: %w", err)
}
- defer jsonFile.Close()
-
- byteValue, _ := ioutil.ReadAll(jsonFile)
- var autoreplies []AutoReply
- json.Unmarshal(byteValue, &autoreplies)
+ var autoReplies []AutoReply
+ err = json.Unmarshal(byteValue, &autoReplies)
+ if err != nil {
+ return nil, fmt.Errorf("error parsing autoreply.json file: %w", err)
+ }
- return autoreplies, nil
+ return autoReplies, nil
}
func ParseSettings() (SettingsStruct, error) {
- jsonFile, err := os.Open("./json/settings.json")
+ byteValue, err := os.ReadFile("./json/settings.json")
if err != nil {
- log.Println(err)
- return SettingsStruct{}, err
+ return SettingsStruct{}, fmt.Errorf("error reading settings.json file: %w", err)
}
- defer jsonFile.Close()
-
- byteValue, _ := ioutil.ReadAll(jsonFile)
var settings SettingsStruct
- json.Unmarshal(byteValue, &settings)
+ err = json.Unmarshal(byteValue, &settings)
+ if err != nil {
+ return SettingsStruct{}, fmt.Errorf("error parsing settings.json file: %w", err)
+ }
return settings, nil
}
func ParseActions() ([]Action, error) {
- jsonFile, err := os.Open("./json/actions.json")
+ byteValue, err := os.ReadFile("./json/actions.json")
if err != nil {
- log.Println(err)
- return nil, err
+ return nil, fmt.Errorf("error reading actions.json file: %w", err)
}
- defer jsonFile.Close()
- byteValue, _ := ioutil.ReadAll(jsonFile)
return ParseActionsBytes(byteValue)
}
@@ -61,7 +58,7 @@ func ParseActionsBytes(bytes []byte) ([]Action, error) {
var mapData map[string]interface{}
err := json.Unmarshal(bytes, &mapData)
if err != nil {
- log.Println(err)
+ return nil, err
}
var actions []Action
@@ -76,30 +73,23 @@ func ParseActionsBytes(bytes []byte) ([]Action, error) {
}
slices.SortFunc(actions, func(a, b Action) int {
- if a.Name < b.Name {
- return -1
- } else if a.Name > b.Name {
- return 1
- } else {
- return 0
- }
+ return strings.Compare(a.Name, b.Name)
})
return actions, nil
}
func ParseMemeList() ([]Meme, error) {
- jsonFile, err := os.Open("./json/memes.json")
+ byteValue, err := os.ReadFile("./json/memes.json")
if err != nil {
- log.Println(err)
- return nil, err
+ return nil, fmt.Errorf("error reading memes.json file: %w", err)
}
- defer jsonFile.Close()
-
- byteValue, _ := ioutil.ReadAll(jsonFile)
var mapData map[string]interface{}
- json.Unmarshal(byteValue, &mapData)
+ err = json.Unmarshal(byteValue, &mapData)
+ if err != nil {
+ return nil, fmt.Errorf("error parsing memes.json file: %w", err)
+ }
var memes []Meme
for key, value := range mapData {
@@ -114,20 +104,19 @@ func ParseMemeList() ([]Meme, error) {
}
func ParseOrCreateGroups() (GroupsStruct, error) {
- jsonFile, err := os.Open(groupsPath)
- if err != nil {
- jsonFile, err = os.Create(groupsPath)
- if err != nil {
- log.Println(err)
- return nil, err
- }
+ byteValue, err := os.ReadFile(groupsPath)
+ if errors.Is(err, os.ErrNotExist) {
+ _, _ = os.Create(groupsPath)
+ return make(GroupsStruct), nil
+ } else if err != nil {
+ return nil, fmt.Errorf("error reading groups.json file: %w", err)
}
- defer jsonFile.Close()
-
- byteValue, _ := ioutil.ReadAll(jsonFile)
var groups GroupsStruct
- json.Unmarshal(byteValue, &groups)
+ err = json.Unmarshal(byteValue, &groups)
+ if err != nil {
+ return nil, fmt.Errorf("error parsing groups.json file: %w", err)
+ }
if groups == nil {
groups = make(GroupsStruct)
diff --git a/model/parse_test.go b/model/parse_test.go
index f1f2792..155463b 100644
--- a/model/parse_test.go
+++ b/model/parse_test.go
@@ -17,7 +17,7 @@ func TestActions(t *testing.T) {
actions, err := ParseActionsBytes(bytes)
t.Log(actions)
if err != nil {
- t.Error(err)
+ t.Fatal(err)
}
if len(actions) != 1 {
diff --git a/model/responses.go b/model/responses.go
index 444aab9..fa7961c 100644
--- a/model/responses.go
+++ b/model/responses.go
@@ -1,14 +1,15 @@
-// This file contains the structure and functions used to pass information
+// Package model contains the structure and functions used to pass information
// between the computations of the bot and the driver code, in bot.go
package model
-// This struct is returned by the command handler, it contains information about the
-// command computation.
+// CommandResponse is returned by the command handler, it contains information
+// about the command computation.
type CommandResponse struct {
Text string
NextCommand string
}
+// makeResponse creates a CommandResponse with the given text and nextCommand
func makeResponse(text string, nextCommand string) CommandResponse {
return CommandResponse{
Text: text,
@@ -16,22 +17,27 @@ func makeResponse(text string, nextCommand string) CommandResponse {
}
}
+// makeResponseWithText creates a CommandResponse with the given text (and no nextCommand)
func makeResponseWithText(text string) CommandResponse {
return makeResponse(text, "")
}
+// makeResponseWithNextCommand creates a CommandResponse with the given nextCommand (and no text)
func makeResponseWithNextCommand(nextCommand string) CommandResponse {
return makeResponse("", nextCommand)
}
-func (respose CommandResponse) IsEmpty() bool {
- return respose.Text == "" && respose.NextCommand == ""
+// IsEmpty returns true if the CommandResponse has no text and no nextCommand
+func (r CommandResponse) IsEmpty() bool {
+ return r.Text == "" && r.NextCommand == ""
}
-func (response CommandResponse) HasText() bool {
- return response.Text != ""
+// HasText returns true if the CommandResponse has some text
+func (r CommandResponse) HasText() bool {
+ return r.Text != ""
}
-func (response CommandResponse) HasNextCommand() bool {
- return response.NextCommand != ""
+// HasNextCommand returns true if the CommandResponse has some nextCommand
+func (r CommandResponse) HasNextCommand() bool {
+ return r.NextCommand != ""
}
diff --git a/utils/util.go b/utils/util.go
index 5d469bb..3e8d300 100644
--- a/utils/util.go
+++ b/utils/util.go
@@ -17,13 +17,10 @@ func SendHTML(bot *tgbotapi.BotAPI, msg tgbotapi.MessageConfig) {
bot.Send(msg)
}
-/* convert a string into kebab case
- * useful for GitHub repository
- *
- * example:
- * string = "Logica per l'informatica"
- * converted_string = ToKebabCase(string); = "logica-per-informatica" (sic!)
- */
+/*
+ToKebabCase convert a string into kebab case. Useful for GitHub repository
+names.
+*/
func ToKebabCase(str string) string {
// normalize the string to NFD form
normalizedStr := norm.NFD.String(strings.ToLower(strings.TrimSpace(str)))
@@ -48,9 +45,15 @@ func WriteJSONFile(filename string, data interface{}) error {
if err != nil {
return err
}
- defer file.Close()
encoder := json.NewEncoder(file)
encoder.SetIndent("", " ")
- return encoder.Encode(data)
+
+ err = encoder.Encode(data)
+ if err != nil {
+ return err
+ }
+
+ err = file.Close()
+ return err
}
diff --git a/utils/util_test.go b/utils/util_test.go
index e6a38aa..09cfb8a 100644
--- a/utils/util_test.go
+++ b/utils/util_test.go
@@ -1,55 +1,53 @@
package utils
import (
+ "fmt"
"testing"
)
-func TestToKebabCase(t *testing.T) {
- // Test strings
- str1 := "Logica per l'informatica"
- str2 := "Informatica e Societa"
- str3 := "Sistemi Operativi"
-
- // Expected results
- exp1 := "logica-per-informatica"
- exp2 := "informatica-e-societa"
- exp3 := "sistemi-operativi"
-
- // Test
- res1 := ToKebabCase(str1)
- res2 := ToKebabCase(str2)
- res3 := ToKebabCase(str3)
-
- // Check results
-
- if res1 != exp1 {
- t.Errorf("Expected %s, got %s", exp1, res1)
- }
-
- if res2 != exp2 {
- t.Errorf("Expected %s, got %s", exp2, res2)
- }
-
- if res3 != exp3 {
- t.Errorf("Expected %s, got %s", exp3, res3)
- }
+func ExampleToKebabCase() {
+ fmt.Println(ToKebabCase("Hello World"))
+ // Output: hello-world
}
-func TestAccents(t *testing.T) {
- str := "Informatica e SocietΓ "
- exp := "informatica-e-societa"
-
- str2 := "Γ Γ¨ Γ¬ Γ² ΓΉ"
- exp2 := "a-e-i-o-u"
-
- res := ToKebabCase(str)
- res2 := ToKebabCase(str2)
+func TestToKebabCase(t *testing.T) {
- if res != exp {
- t.Errorf("Expected %s, got %s", exp, res)
+ tests := []struct {
+ name string
+ args string
+ want string
+ }{
+ {
+ "Standard",
+ "Logica per l'informatica",
+ "logica-per-informatica",
+ },
+ {
+ "Standard",
+ "Informatica e Societa",
+ "informatica-e-societa",
+ },
+ {
+ "Standard",
+ "Sistemi Operativi",
+ "sistemi-operativi",
+ },
+ {
+ "Accents",
+ "Informatica e SocietΓ ",
+ "informatica-e-societa",
+ },
+ {
+ "Accents",
+ "Γ Γ¨ Γ¬ Γ² ΓΉ",
+ "a-e-i-o-u",
+ },
}
-
- if res2 != exp2 {
- t.Errorf("Expected %s, got %s", exp2, res2)
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := ToKebabCase(tt.args); got != tt.want {
+ t.Errorf("ToKebabCase() = %v, want %v", got, tt.want)
+ }
+ })
}
}