From 0c2cf9118ad78e3ee853eac988addf527f5a5c73 Mon Sep 17 00:00:00 2001 From: far-galaxy Date: Thu, 3 Aug 2023 21:52:18 +0400 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=BE:=20=D0=BA=D1=80=D0=B0=D1=82=D0=BA=D0=B0=D1=8F=20?= =?UTF-8?q?=D1=81=D0=B2=D0=BE=D0=B4=D0=BA=D0=B0=20(=D1=87=D0=B0=D1=81?= =?UTF-8?q?=D1=82=D0=B8=D1=87=D0=BD=D0=BE)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.go | 2 + modules/database/database_struct.go | 10 +- modules/ssau_parser/database.go | 40 ++++- modules/ssau_parser/database_test.go | 6 +- modules/ssau_parser/parser.go | 5 + modules/tg/handlers.go | 28 ++- modules/tg/shedule.go | 248 +++++++++++++++++++++++++++ modules/tg/utils.go | 57 +++--- 8 files changed, 348 insertions(+), 48 deletions(-) create mode 100644 modules/tg/shedule.go diff --git a/main.go b/main.go index 4c166f5..a666f3f 100644 --- a/main.go +++ b/main.go @@ -5,10 +5,12 @@ import ( "os" "git.l9labs.ru/anufriev.g.a/l9_stud_bot/modules/database" + "git.l9labs.ru/anufriev.g.a/l9_stud_bot/modules/ssau_parser" "git.l9labs.ru/anufriev.g.a/l9_stud_bot/modules/tg" ) func main() { + ssau_parser.HeadURL = "http://127.0.0.1:5000/prod" tg.CheckEnv() //bot := new(tg.Bot) diff --git a/modules/database/database_struct.go b/modules/database/database_struct.go index 84f21b3..9afc432 100644 --- a/modules/database/database_struct.go +++ b/modules/database/database_struct.go @@ -2,6 +2,7 @@ package database import "time" +// Пользователь системы (задел под сайт) type User struct { L9Id int64 `xorm:"pk"` } @@ -18,6 +19,7 @@ const ( SelSeeStaff Position = "select_see_staff" // Выбирает преподавателя для автономной карточки ) +// Пользователь Telegram type TgUser struct { L9Id int64 `xorm:"pk"` TgId int64 @@ -25,27 +27,33 @@ type TgUser struct { PosTag Position } +// Подключённое к пользователю расписание type ShedulesInUser struct { UID int64 `xorm:"pk autoincr"` // Не забывать про автоинкремент!!! L9Id int64 - IsTeacher bool + IsGroup bool SheduleId int64 } +// Учебная группа type Group struct { GroupId int64 `xorm:"pk"` GroupName string // Полный номер группы SpecName string // Шифр и название специальности + LastUpd time.Time } +// Преподаватель type Teacher struct { TeacherId int64 `xorm:"pk"` FirstName string // Фамилия LastName string // Имя, отчество и прочие окончания ShortName string // Инициалы SpecName string // Место работы + LastUpd time.Time } +// Занятие type Lesson struct { LessonId int64 `xorm:"pk autoincr"` NumInShedule int diff --git a/modules/ssau_parser/database.go b/modules/ssau_parser/database.go index b411ec3..ebd7467 100644 --- a/modules/ssau_parser/database.go +++ b/modules/ssau_parser/database.go @@ -2,6 +2,7 @@ package ssau_parser import ( "strings" + "time" "git.l9labs.ru/anufriev.g.a/l9_stud_bot/modules/database" "xorm.io/xorm" @@ -10,10 +11,12 @@ import ( // Согласование недельного расписания с БД // Возвращает соответственно добавленные и удалённые занятия func UpdateSchedule(db *xorm.Engine, sh WeekShedule) ([]database.Lesson, []database.Lesson, error) { - if err := checkGroupOrTeacher(db, sh); err != nil { + if _, err := CheckGroupOrTeacher(db, sh); err != nil { return nil, nil, err } - + if len(sh.Uncovered) == 0 { + return nil, nil, nil + } first_new := sh.Uncovered[0] _, week := first_new.Begin.ISOWeek() var old []database.Lesson @@ -33,6 +36,28 @@ func UpdateSchedule(db *xorm.Engine, sh WeekShedule) ([]database.Lesson, []datab return nil, nil, err } } + // Обновляем время обновления + if len(add) > 0 || len(del) > 0 { + if sh.IsGroup { + gr := database.Group{GroupId: sh.SheduleId} + if _, err := db.Get(&gr); err != nil { + return nil, nil, err + } + gr.LastUpd = time.Now() + if _, err := db.Update(gr); err != nil { + return nil, nil, err + } + } else { + var t database.Teacher + if err := db.Find(&t, &database.Teacher{TeacherId: sh.SheduleId}); err != nil { + return nil, nil, err + } + t.LastUpd = time.Now() + if _, err := db.Update(t); err != nil { + return nil, nil, err + } + } + } return add, del, nil } @@ -57,12 +82,13 @@ func isTeacherExists(db *xorm.Engine, teacherId int64) (bool, error) { } // Проверка наличия группы или преподавателя в БД и добавление при необходимости +// Возвращает истину, если группы/преподавателя раньше не было // TODO: Добавить проверку изменений в полях данных -func checkGroupOrTeacher(db *xorm.Engine, sh WeekShedule) error { +func CheckGroupOrTeacher(db *xorm.Engine, sh WeekShedule) (bool, error) { if sh.IsGroup { exists, err := isGroupExists(db, sh.SheduleId) if err != nil { - return err + return false, err } if !exists { @@ -72,11 +98,12 @@ func checkGroupOrTeacher(db *xorm.Engine, sh WeekShedule) error { SpecName: sh.SpecName, } db.InsertOne(group) + return true, nil } } else { exists, err := isTeacherExists(db, sh.SheduleId) if err != nil { - return err + return false, err } if !exists { @@ -84,10 +111,11 @@ func checkGroupOrTeacher(db *xorm.Engine, sh WeekShedule) error { teacher.TeacherId = sh.SheduleId teacher.SpecName = sh.SpecName db.InsertOne(teacher) + return true, nil } } - return nil + return false, nil } func ParseTeacherName(fullName string) database.Teacher { diff --git a/modules/ssau_parser/database_test.go b/modules/ssau_parser/database_test.go index d93c4ce..e7edd90 100644 --- a/modules/ssau_parser/database_test.go +++ b/modules/ssau_parser/database_test.go @@ -41,10 +41,10 @@ func TestCheckGroupOrTeacher(t *testing.T) { } err := sh.DownloadById(false) handleError(err) - err = checkGroupOrTeacher(db, sh) + _, err = CheckGroupOrTeacher(db, sh) handleError(err) // Повторяем на предмет наличия - err = checkGroupOrTeacher(db, sh) + _, err = CheckGroupOrTeacher(db, sh) handleError(err) // Проверяем преподавателя @@ -55,7 +55,7 @@ func TestCheckGroupOrTeacher(t *testing.T) { } err = sh.DownloadById(false) handleError(err) - err = checkGroupOrTeacher(db, sh) + _, err = CheckGroupOrTeacher(db, sh) handleError(err) } diff --git a/modules/ssau_parser/parser.go b/modules/ssau_parser/parser.go index 60a2ff5..b234e73 100644 --- a/modules/ssau_parser/parser.go +++ b/modules/ssau_parser/parser.go @@ -63,6 +63,7 @@ func GetSheduleInfo(doc *goquery.Document, sh *WeekShedule) { var hourMap = map[int]int{8: 0, 9: 1, 11: 2, 13: 3, 15: 4, 17: 5, 18: 6, 20: 7} // Парсинг страницы с расписанием +// TODO: находить окна func (sh *WeekShedule) Parse(p Page, uncover bool) error { doc := p.Doc GetSheduleInfo(doc, sh) @@ -208,6 +209,10 @@ func ParseLesson(s *goquery.Selection, isGroup bool, sheduleId int64) []Lesson { } else { lesson.SubGroup = append(lesson.SubGroup, 0) } + } else if isGroup && len(groupId) > 1 { + for range groupId { + lesson.SubGroup = append(lesson.SubGroup, 0) + } } place := l.Find("div.schedule__place").First().Text() diff --git a/modules/tg/handlers.go b/modules/tg/handlers.go index 35485f4..43e2234 100644 --- a/modules/tg/handlers.go +++ b/modules/tg/handlers.go @@ -94,23 +94,21 @@ func (bot *Bot) Find(user *database.TgUser, query string) (tgbotapi.Message, err sheduleId = allTeachers[0].TeacherId isGroup = false } - shedule := database.ShedulesInUser{ - IsTeacher: !isGroup, + shedule := ssau_parser.WeekShedule{ + IsGroup: isGroup, SheduleId: sheduleId, } - msg := tgbotapi.NewMessage( - user.TgId, - fmt.Sprintf( - "Тут должно было быть расписание, но его пока не завезли\n%d", - shedule.SheduleId, - ), - ) - return bot.TG.Send(msg) - /* - err := bot.GetSummary([]database.ShedulesInUser{shedule}, false) + not_exists, _ := ssau_parser.CheckGroupOrTeacher(bot.DB, shedule) + if not_exists { + msg := tgbotapi.NewMessage(user.TgId, "Загружаю расписание...\nЭто займёт некоторое время") + bot.TG.Send(msg) + err := bot.LoadShedule(shedule) if err != nil { - return err - }*/ + bot.Debug.Println(err) + } + } + bot.GetSummary(user, []database.ShedulesInUser{Swap(shedule)}, false) + return tgbotapi.Message{}, nil } // Если получено несколько групп @@ -158,7 +156,7 @@ func (bot *Bot) GetShedule(user *database.TgUser, query *tgbotapi.CallbackQuery) } if !isAdd { shedule := database.ShedulesInUser{ - IsTeacher: !isGroup, + IsGroup: isGroup, SheduleId: groupId, } msg := tgbotapi.NewEditMessageText( diff --git a/modules/tg/shedule.go b/modules/tg/shedule.go new file mode 100644 index 0000000..5f2f652 --- /dev/null +++ b/modules/tg/shedule.go @@ -0,0 +1,248 @@ +package tg + +import ( + "fmt" + "log" + "strconv" + "strings" + "time" + + "git.l9labs.ru/anufriev.g.a/l9_stud_bot/modules/database" + "git.l9labs.ru/anufriev.g.a/l9_stud_bot/modules/ssau_parser" + tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" + "xorm.io/xorm" +) + +func (bot *Bot) GetPersonalSummary(user *database.TgUser, msg ...tgbotapi.Message) { + var shedules []database.ShedulesInUser + bot.DB.ID(user.L9Id).Find(&shedules) + + if len(shedules) == 0 { + bot.Etc(user) + return + } else { + err := bot.GetSummary(user, shedules, true, msg...) + if err != nil { + log.Fatal(err) + } + } +} + +// Получить краткую сводку +func (bot *Bot) GetSummary( + user *database.TgUser, + shedules []database.ShedulesInUser, + isPersonal bool, + editMsg ...tgbotapi.Message) error { + + now, _ := time.Parse("2006-01-02 15:04 -07", "2023-03-06 07:20 +04") //time.Now().Add(time.Hour * time.Duration(24) * (-1) * 30 * 4) + + lessons, err := bot.GetLessons(shedules, now) + if err != nil { + return err + } + if len(lessons) != 0 { + var firstPair, secondPair []database.Lesson + pairs := GroupPairs(lessons) + firstPair = pairs[0] + log.Println(firstPair, secondPair) + + str := "📝Краткая сводка:\n\n" + if pairs[0][0].Begin.Day() != now.Day() { + str += "❗️Сегодня пар нет\nБлижайшие занятия " + if firstPair[0].Begin.Sub(now).Hours() < 48 { + str += "завтра\n" + } else { + // TODO: добавить прописные названия месяцев + str += fmt.Sprintf("%s\n\n", firstPair[0].Begin.Format("02.01")) + } /* + day, err := bot.GetDayShedule(pairs) + if err != nil { + return err + } + str += day*/ + } else { + if firstPair[0].Begin.Before(now) { + str += "Сейчас:\n\n" + } else { + str += "Ближайшая пара сегодня:\n\n" + } + firstStr, err := PairToStr(firstPair, bot.DB) + if err != nil { + return err + } + str += firstStr + if len(pairs) > 1 { + secondPair = pairs[1] + if firstPair[0].Begin.Day() == secondPair[0].Begin.Day() { + str += "\nПосле неё:\n\n" + secondStr, err := PairToStr(secondPair, bot.DB) + if err != nil { + return err + } + str += secondStr + } else { + str += "\nБольше ничего сегодня нет" + } + } else { + str += "\nБольше ничего сегодня нет" + } + + } + /* + var shId int64 + if isPersonal { + shId = 0 + } else { + shId = shedules[0].SheduleId + } + + + markup := SummaryKeyboard( + // TODO: создать тип таких префиксов + "sh_near", + shId, + shedules[0].IsGroup, + 0, + )*/ + bot.EditOrSend(user.TgId, str, tgbotapi.NewInlineKeyboardMarkup(), editMsg...) + + } else { + msg := tgbotapi.NewMessage(user.TgId, "Ой! Пар не обнаружено ):") + bot.TG.Send(msg) + } + return nil +} + +// Получить список ближайших занятий (для краткой сводки или расписания на день) +func (bot *Bot) GetLessons(shedules []database.ShedulesInUser, now time.Time) ([]database.Lesson, error) { + + condition := CreateCondition(shedules) + + var lessons []database.Lesson + err := bot.DB. + Where("end > ?", now.Format("2006-01-02 15:04:05")). + And(condition). + OrderBy("begin"). + Limit(16). + Find(&lessons) + + return lessons, err +} + +// Загрузка расписания из ssau.ru/rasp +func (bot *Bot) LoadShedule(shedule ssau_parser.WeekShedule) error { + sh := ssau_parser.WeekShedule{ + SheduleId: shedule.SheduleId, + IsGroup: shedule.IsGroup, + } + // TODO: вынести количество недель в переменную, либо автоматически определять конец + for week := 1; week < 21; week++ { + sh.Week = week + err := sh.DownloadById(true) + if err != nil { + return err + } + _, _, err = ssau_parser.UpdateSchedule(bot.DB, sh) + if err != nil { + return err + } + } + + return nil +} + +// Создать условие группы/преподавателя +func CreateCondition(shedules []database.ShedulesInUser) string { + var groups []string + var teachers []string + + for _, sh := range shedules { + if !sh.IsGroup { + teachers = append(teachers, strconv.FormatInt(sh.SheduleId, 10)) + } else { + groups = append(groups, strconv.FormatInt(sh.SheduleId, 10)) + } + } + + var condition, teachers_str, groups_str string + if len(groups) > 0 { + groups_str = strings.Join(groups, ",") + condition = "groupId in (" + groups_str + ") " + } + if len(teachers) > 0 { + if len(condition) > 0 { + condition += " or " + } + teachers_str += strings.Join(teachers, ",") + condition += "teacherId in (" + teachers_str + ") " + } + return condition +} + +func GroupPairs(lessons []database.Lesson) [][]database.Lesson { + var shedule [][]database.Lesson + var pair []database.Lesson + + l_idx := 0 + + for l_idx < len(lessons) { + day := lessons[l_idx].Begin + for l_idx < len(lessons) && lessons[l_idx].Begin == day { + pair = append(pair, lessons[l_idx]) + l_idx++ + } + shedule = append(shedule, pair) + pair = []database.Lesson{} + } + return shedule +} + +func PairToStr(pair []database.Lesson, db *xorm.Engine) (string, error) { + var str string + beginStr := pair[0].Begin.Format("15:04") + endStr := pair[0].End.Format("15:04") + str = fmt.Sprintf("📆 %s - %s\n", beginStr, endStr) + + for i, sublesson := range pair { + var type_emoji string + switch sublesson.Type { + case "lect": + type_emoji = "📗" + case "pract": + type_emoji = "📕" + case "lab": + type_emoji = "📘" + case "other": + type_emoji = "📙" + case "mil": + type_emoji = "🗿" + default: + type_emoji = "📙" + } + str += fmt.Sprintf("%s%s\n", type_emoji, sublesson.Name) + if sublesson.Place != "" { + str += fmt.Sprintf("🧭 %s\n", sublesson.Place) + } + if sublesson.TeacherId != 0 { + var t database.Teacher + _, err := db.ID(sublesson.TeacherId).Get(&t) + if err != nil { + return "", err + } + str += fmt.Sprintf("👤 %s %s\n", t.FirstName, t.ShortName) + } + if sublesson.SubGroup != 0 { + str += fmt.Sprintf("👥 Подгруппа: %d\n", sublesson.SubGroup) + } + if sublesson.Comment != "" { + str += fmt.Sprintf("💬 %s\n", sublesson.Comment) + } + if i != len(pair)-1 { + str += "+\n" + } + } + + str += "------------------------------------------\n" + return str, nil +} diff --git a/modules/tg/utils.go b/modules/tg/utils.go index 92fe650..8d552bb 100644 --- a/modules/tg/utils.go +++ b/modules/tg/utils.go @@ -6,6 +6,7 @@ import ( "strings" "git.l9labs.ru/anufriev.g.a/l9_stud_bot/modules/database" + "git.l9labs.ru/anufriev.g.a/l9_stud_bot/modules/ssau_parser" tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" ) @@ -61,24 +62,24 @@ func GenerateKeyboard(array []tgbotapi.InlineKeyboardButton) tgbotapi.InlineKeyb return tgbotapi.InlineKeyboardMarkup{InlineKeyboard: markup} } -func SummaryKeyboard(clickedButton string, sheduleId int64, isTeacher bool, dt int) tgbotapi.InlineKeyboardMarkup { - tail := GenerateButtonTail(sheduleId, 0, isTeacher) +func SummaryKeyboard(clickedButton string, sheduleId int64, isGroup bool, dt int) tgbotapi.InlineKeyboardMarkup { + tail := GenerateButtonTail(sheduleId, 0, isGroup) near := []tgbotapi.InlineKeyboardButton{ - tgbotapi.NewInlineKeyboardButtonData("Краткая сводка", "near"+tail), + tgbotapi.NewInlineKeyboardButtonData("Краткая сводка", "sh_near"+tail), } day := []tgbotapi.InlineKeyboardButton{ - tgbotapi.NewInlineKeyboardButtonData("День", "day"+tail), + tgbotapi.NewInlineKeyboardButtonData("День", "sh_day"+tail), } week := []tgbotapi.InlineKeyboardButton{ - tgbotapi.NewInlineKeyboardButtonData("Неделя", "week"+tail), + tgbotapi.NewInlineKeyboardButtonData("Неделя", "sh_week"+tail), } - update := GenerateButtonTail(sheduleId, dt, isTeacher) + update := GenerateButtonTail(sheduleId, dt, isGroup) var arrows []tgbotapi.InlineKeyboardButton - if clickedButton == "day" || clickedButton == "week" { - prev_arrow := GenerateButtonTail(sheduleId, dt-1, isTeacher) - next_arrow := GenerateButtonTail(sheduleId, dt+1, isTeacher) + if clickedButton == "sh_day" || clickedButton == "sh_week" { + prev_arrow := GenerateButtonTail(sheduleId, dt-1, isGroup) + next_arrow := GenerateButtonTail(sheduleId, dt+1, isGroup) arrows = []tgbotapi.InlineKeyboardButton{ tgbotapi.NewInlineKeyboardButtonData("⏮", clickedButton+prev_arrow), tgbotapi.NewInlineKeyboardButtonData("🔄", clickedButton+update), @@ -95,11 +96,11 @@ func SummaryKeyboard(clickedButton string, sheduleId int64, isTeacher bool, dt i var markup [][]tgbotapi.InlineKeyboardButton switch clickedButton { - case "day": + case "sh_day": markup = [][]tgbotapi.InlineKeyboardButton{ arrows, near, week, } - case "week": + case "sh_week": markup = [][]tgbotapi.InlineKeyboardButton{ arrows, near, day, } @@ -114,11 +115,11 @@ func SummaryKeyboard(clickedButton string, sheduleId int64, isTeacher bool, dt i return tgbotapi.InlineKeyboardMarkup{InlineKeyboard: markup} } -func GenerateButtonTail(sheduleId int64, dt int, isTeacher bool) string { +func GenerateButtonTail(sheduleId int64, dt int, isGroup bool) string { var tail string if sheduleId == 0 { tail = fmt.Sprintf("_personal_%d_0", dt) - } else if isTeacher { + } else if !isGroup { tail = fmt.Sprintf("_teacher_%d_%d", dt, sheduleId) } else { tail = fmt.Sprintf("_group_%d_%d", dt, sheduleId) @@ -126,8 +127,7 @@ func GenerateButtonTail(sheduleId int64, dt int, isTeacher bool) string { return tail } -/* -func (bot *Bot) EditOrSend(str string, markup tgbotapi.InlineKeyboardMarkup, editMsg ...tgbotapi.Message) { +func (bot *Bot) EditOrSend(id int64, str string, markup tgbotapi.InlineKeyboardMarkup, editMsg ...tgbotapi.Message) { if len(editMsg) > 0 { msg := tgbotapi.NewEditMessageText( editMsg[0].Chat.ID, @@ -137,23 +137,26 @@ func (bot *Bot) EditOrSend(str string, markup tgbotapi.InlineKeyboardMarkup, edi msg.ReplyMarkup = &markup bot.TG.Request(msg) } else { - msg := tgbotapi.NewMessage(bot.TG_user.TgId, str) - msg.ReplyMarkup = markup - bot.TG.Send(msg) + msg := tgbotapi.NewMessage(id, str) + //msg.ReplyMarkup = &markup + _, err := bot.TG.Send(msg) + if err != nil { + bot.Debug.Println(err) + } } -}*/ +} func ParseQuery(data []string) ([]database.ShedulesInUser, int, error) { - isGroup := data[1] == "group" - sheduleId, err := strconv.ParseInt(data[3], 0, 64) + isGroup := data[2] == "group" + sheduleId, err := strconv.ParseInt(data[4], 0, 64) if err != nil { return nil, 0, err } shedule := database.ShedulesInUser{ - IsTeacher: !isGroup, + IsGroup: !isGroup, SheduleId: sheduleId, } - dt, err := strconv.ParseInt(data[2], 0, 0) + dt, err := strconv.ParseInt(data[3], 0, 0) if err != nil { return nil, 0, err } @@ -175,3 +178,11 @@ func (bot *Bot) DeleteMsg(query *tgbotapi.CallbackQuery) { delete := tgbotapi.NewDeleteMessage(query.From.ID, query.Message.MessageID) bot.TG.Request(delete) } + +// Меняем шило на мыло +func Swap(sh ssau_parser.WeekShedule) database.ShedulesInUser { + return database.ShedulesInUser{ + IsGroup: sh.IsGroup, + SheduleId: sh.SheduleId, + } +}