From 530646e13cf67879d0dab64de90ec3f3fd9489c4 Mon Sep 17 00:00:00 2001 From: far-galaxy Date: Tue, 8 Aug 2023 19:36:33 +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=BD=D0=B5=D0=B4=D0=B5=D0=BB=D1=8C=D0=BD?= =?UTF-8?q?=D0=BE=D0=B5=20=D1=80=D0=B0=D1=81=D0=BF=D0=B8=D1=81=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20(=D0=BD=D0=B5=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B5?= =?UTF-8?q?=D1=80=D0=B5=D0=BD=D0=BE=20=D0=B8=20=D0=BD=D0=B5=20=D0=B4=D0=BE?= =?UTF-8?q?=D0=BF=D0=B8=D0=BB=D0=B5=D0=BD=D0=BE=20=D0=BF=D0=BE=D0=BB=D0=BD?= =?UTF-8?q?=D0=BE=D1=81=D1=82=D1=8C=D1=8E,=20=D0=BF=D1=80=D0=BE=D1=81?= =?UTF-8?q?=D1=82=D0=BE=20=D1=8F=20=D1=83=D1=81=D1=82=D0=B0=D0=BB)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.go | 4 +- modules/database/database_struct.go | 12 + modules/database/mysql.go | 2 +- modules/ssau_parser/database.go | 2 +- modules/tg/handlers.go | 6 +- modules/tg/tg_test.go | 38 +++ modules/tg/utils.go | 8 +- modules/tg/week_shedule.go | 389 ++++++++++++++++++++++++++++ 8 files changed, 450 insertions(+), 11 deletions(-) create mode 100644 modules/tg/week_shedule.go diff --git a/main.go b/main.go index 6ec3c6c..204c22e 100644 --- a/main.go +++ b/main.go @@ -18,8 +18,6 @@ func main() { logs := database.OpenLogs() defer logs.CloseAll() //bot := new(tg.Bot) - // bot.Week = 5 - // bot.WkPath = os.Getenv("WK_PATH") // bot.Debug = log.New(io.MultiWriter(os.Stderr, database.CreateLog("messages")), "", log.LstdFlags) bot, err := tg.InitBot( logs, @@ -33,6 +31,8 @@ func main() { if err != nil { log.Fatal(err) } + bot.Week = 5 + bot.WkPath = os.Getenv("WK_PATH") now, _ := time.Parse("2006-01-02 15:04 -07", "2023-02-06 11:20 +04") for update := range *bot.Updates { _, err := bot.HandleUpdate(update, now) diff --git a/modules/database/database_struct.go b/modules/database/database_struct.go index 9afc432..8e1c14e 100644 --- a/modules/database/database_struct.go +++ b/modules/database/database_struct.go @@ -68,3 +68,15 @@ type Lesson struct { SubGroup int64 Hash string } + +// Файлы, залитые в Telegream +type File struct { + Id int64 `xorm:"pk autoincr"` + FileId string + TgId int64 + IsPersonal bool + IsGroup bool + SheduleId int64 + Week int + LastUpd time.Time +} diff --git a/modules/database/mysql.go b/modules/database/mysql.go index 0d99a4d..57f63ee 100644 --- a/modules/database/mysql.go +++ b/modules/database/mysql.go @@ -49,7 +49,7 @@ func Connect(db DB, logger *os.File) (*xorm.Engine, error) { engine.ShowSQL(true) engine.SetMapper(names.SameMapper{}) - err = engine.Sync(&User{}, &TgUser{}, &Group{}, &Lesson{}, &Teacher{}, &ShedulesInUser{}) + err = engine.Sync(&User{}, &TgUser{}, &Group{}, &Lesson{}, &Teacher{}, &ShedulesInUser{}, &File{}) if err != nil { return nil, err } diff --git a/modules/ssau_parser/database.go b/modules/ssau_parser/database.go index f606eb0..323473e 100644 --- a/modules/ssau_parser/database.go +++ b/modules/ssau_parser/database.go @@ -164,7 +164,7 @@ func ParseTeacherName(fullName string) database.Teacher { teacher := database.Teacher{ FirstName: name[0], LastName: strings.Join(name[1:], " "), - ShortName: strings.Join(short_name, ". ") + ".", + ShortName: strings.Join(short_name, ".") + ".", } return teacher } diff --git a/modules/tg/handlers.go b/modules/tg/handlers.go index b9011ee..38e37fa 100644 --- a/modules/tg/handlers.go +++ b/modules/tg/handlers.go @@ -216,9 +216,9 @@ func (bot *Bot) HandleSummary(user *database.TgUser, query *tgbotapi.CallbackQue switch data[1] { case "day": _, err = bot.GetDaySummary(now[0], user, shedule, dt, false, *query.Message) - /* case "week": - bot.GetWeekSummary(shedule, dt, false, *query.Message) - */ + case "week": + bot.GetWeekSummary(now[0], user, shedule[0], dt, false) + default: _, err = bot.GetSummary(now[0], user, shedule, false, *query.Message) } diff --git a/modules/tg/tg_test.go b/modules/tg/tg_test.go index 61b7de4..738efc3 100644 --- a/modules/tg/tg_test.go +++ b/modules/tg/tg_test.go @@ -217,3 +217,41 @@ func TestSummary(t *testing.T) { log.Fatal(err) }*/ } + +func TestGetWeekLessons(t *testing.T) { + ssau_parser.HeadURL = "http://127.0.0.1:5000/prod" + files := database.OpenLogs() + defer files.CloseAll() + bot := initTestBot(files) + bot.Week = 5 + bot.WkPath = "C:\\Program Files\\wkhtmltopdf\\bin\\wkhtmltoimage.exe" + user := database.TgUser{} + user.TgId, _ = strconv.ParseInt(os.Getenv("TELEGRAM_TEST_USER"), 0, 64) + shedules := []ssau_parser.WeekShedule{ + { + SheduleId: 100000000, + IsGroup: true, + Week: 1, + }, + { + SheduleId: 3, + IsGroup: false, + Week: 1, + }, + } + now, _ := time.Parse("2006-01-02 15:04 -07", times[2]) + for _, sh := range shedules { + err := sh.DownloadById(true) + if err != nil { + log.Fatal(err) + } + _, _, err = ssau_parser.UpdateSchedule(bot.DB, sh) + if err != nil { + log.Fatal(err) + } + err = bot.CreateWeekImg(now, &user, []database.ShedulesInUser{Swap(sh)}, 0, false) + if err != nil { + log.Fatal(err) + } + } +} diff --git a/modules/tg/utils.go b/modules/tg/utils.go index 44c8b8c..70c939b 100644 --- a/modules/tg/utils.go +++ b/modules/tg/utils.go @@ -78,9 +78,9 @@ func SummaryKeyboard(clickedButton string, sheduleId int64, isGroup bool, dt int day := []tgbotapi.InlineKeyboardButton{ tgbotapi.NewInlineKeyboardButtonData("День", "sh_day"+tail), } - /*week := []tgbotapi.InlineKeyboardButton{ + week := []tgbotapi.InlineKeyboardButton{ tgbotapi.NewInlineKeyboardButtonData("Неделя", "sh_week"+tail), - }*/ + } update := GenerateButtonTail(sheduleId, dt, isGroup) var arrows []tgbotapi.InlineKeyboardButton @@ -105,7 +105,7 @@ func SummaryKeyboard(clickedButton string, sheduleId int64, isGroup bool, dt int switch clickedButton { case "sh_day": markup = [][]tgbotapi.InlineKeyboardButton{ - arrows, near, //week, + arrows, near, week, } case "sh_week": markup = [][]tgbotapi.InlineKeyboardButton{ @@ -113,7 +113,7 @@ func SummaryKeyboard(clickedButton string, sheduleId int64, isGroup bool, dt int } default: markup = [][]tgbotapi.InlineKeyboardButton{ - arrows, day, //week, + arrows, day, week, } } /*if sheduleId == 0 { diff --git a/modules/tg/week_shedule.go b/modules/tg/week_shedule.go new file mode 100644 index 0000000..d2ac731 --- /dev/null +++ b/modules/tg/week_shedule.go @@ -0,0 +1,389 @@ +package tg + +import ( + "context" + "fmt" + "math" + "os" + "os/exec" + "sort" + "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" + "github.com/icza/gox/timex" +) + +var days = [6]string{"Mon", "Tue", "Wed", "Thu", "Fri", "Sat"} + +func (bot *Bot) GetWeekSummary( + now time.Time, + user *database.TgUser, + shedule database.ShedulesInUser, + dw int, + isPersonal bool, +) error { + _, week := now.ISOWeek() + week += dw - bot.Week + var image database.File + if !isPersonal { + image = database.File{ + IsPersonal: false, + IsGroup: shedule.IsGroup, + SheduleId: shedule.SheduleId, + Week: week, + } + } else { + image = database.File{ + IsPersonal: true, + Week: week, + } + } + has, err := bot.DB.Get(&image) + if err != nil { + return err + } + var lastUpd time.Time + if shedule.IsGroup { + var group database.Group + if _, err := bot.DB.ID(shedule.SheduleId).Get(&group); err != nil { + return err + } + lastUpd = group.LastUpd + } else { + var teacher database.Teacher + if _, err := bot.DB.ID(shedule.SheduleId).Get(&teacher); err != nil { + return err + } + lastUpd = teacher.LastUpd + } + + if !has || image.LastUpd.Before(lastUpd) { + // TODO: удалять старые фото + var shedules []database.ShedulesInUser + if isPersonal { + shedules = append(shedules, database.ShedulesInUser{L9Id: user.L9Id}) + if _, err := bot.DB.Get(&shedules); err != nil { + return err + } + } else { + shedules = append(shedules, shedule) + } + bot.CreateWeekImg(now, user, shedules, dw, isPersonal) + } else { + msg := tgbotapi.NewPhoto(user.TgId, tgbotapi.FileID(image.FileId)) + bot.TG.Send(msg) + } + + /* + if os.IsNotExist(fileErr) || info.ModTime().Before(lastUpd) { + bot.CreateWeekImg(now, user, shedules, dw, isPersonal) + } + + var shId int64 + if isPersonal { + shId = 0 + } else { + shId = shedules[0].SheduleId + } + markup := SummaryKeyboard( + "week", + shId, + shedules[0].IsGroup, + dw, + ) + */ + return nil +} + +func (bot *Bot) GetWeekLessons(shedules []database.ShedulesInUser, week int) ([]database.Lesson, error) { + condition := CreateCondition(shedules) + + var lessons []database.Lesson + err := bot.DB. + Where("WEEK(`begin`, 1) = ?", week+bot.Week). + And(condition). + OrderBy("begin"). + Find(&lessons) + + return lessons, err +} + +func (bot *Bot) CreateWeekImg( + now time.Time, + user *database.TgUser, + shedules []database.ShedulesInUser, + dw int, + isPersonal bool, +) error { + _, week := now.ISOWeek() + week += dw + lessons, err := bot.GetWeekLessons(shedules, week-bot.Week) + if err != nil { + return err + } + if len(lessons) == 0 { + return fmt.Errorf("no lessons: %d, week %d", shedules[0].SheduleId, week) + } + + var dates []time.Time + begins := make(map[time.Time]bool) + ends := make(map[time.Time]bool) + height := 0 + minDay := lessons[0].NumInShedule + for _, lesson := range lessons { + t := lesson.Begin + begin := time.Date(2000, 1, 1, t.Hour(), t.Minute(), 0, 0, t.Location()) + begins[begin] = true + + e := lesson.End + end := time.Date(2000, 1, 1, e.Hour(), e.Minute(), 0, 0, e.Location()) + ends[end] = true + + if lesson.NumInShedule > height { + height = lesson.NumInShedule + } else if lesson.NumInShedule < minDay { + minDay = lesson.NumInShedule + } + } + var times []ssau_parser.Pair + var beginsSlice []time.Time + var endsSlice []time.Time + for b := range begins { + beginsSlice = append(beginsSlice, b) + } + for e := range ends { + endsSlice = append(endsSlice, e) + } + sort.Slice(beginsSlice, func(i, j int) bool { + return beginsSlice[i].Before(beginsSlice[j]) + }) + sort.Slice(endsSlice, func(i, j int) bool { + return endsSlice[i].Before(endsSlice[j]) + }) + for i, b := range beginsSlice { + sh := ssau_parser.Pair{ + Begin: b, + End: endsSlice[i], + } + times = append(times, sh) + } + + weekBegin := timex.WeekStart(lessons[0].Begin.Year(), week) + for i := range days { + dates = append(dates, weekBegin.Add(time.Hour*time.Duration(24*i))) + } + + shedule := make([][6][]database.Lesson, height-minDay+1) + pairs := GroupPairs(lessons) + + for _, p := range pairs { + day := int(math.Floor(p[0].Begin.Sub(weekBegin).Hours() / 24)) + shedule[p[0].NumInShedule-minDay][day] = p + } + + var header string + if isPersonal { + header = fmt.Sprintf("%d неделя", week) + } else if shedules[0].IsGroup { + var group database.Group + if _, err := bot.DB.ID(shedules[0].SheduleId).Get(&group); err != nil { + return err + } + header = fmt.Sprintf("%s, %d неделя", group.GroupName, week) + } else { + var teacher database.Teacher + if _, err := bot.DB.ID(shedules[0].SheduleId).Get(&teacher); err != nil { + return err + } + header = fmt.Sprintf("%s %s, %d неделя", teacher.FirstName, teacher.LastName, week-bot.Week) + } + + html := bot.CreateHTMLShedule(header, shedule, dates, times) + + path := GeneratePath(shedules[0], isPersonal, user.L9Id) + if _, err := os.Stat(path); os.IsNotExist(err) { + err = os.MkdirAll(path, os.ModePerm) + if err != nil { + return err + } + } + + input := fmt.Sprintf("./%s/week_%d.html", path, week-bot.Week) + output := fmt.Sprintf("./%s/week_%d.png", path, week-bot.Week) + f, _ := os.Create(input) + defer f.Close() + f.WriteString(html) + + cmd := exec.CommandContext(context.Background(), bot.WkPath, []string{ + "--width", + "1280", + input, + output, + }...) + cmd.Stderr = bot.Debug.Writer() + cmd.Stdout = bot.Debug.Writer() + err = cmd.Run() + if err != nil { + return err + } + + photoBytes, err := os.ReadFile(output) + if err != nil { + return err + } + photoFileBytes := tgbotapi.FileBytes{ + Bytes: photoBytes, + } + photo := tgbotapi.NewPhoto(user.TgId, photoFileBytes) + resp, err := bot.TG.Send(photo) + if err != nil { + return err + } + file := database.File{ + FileId: resp.Photo[0].FileID, + TgId: user.TgId, + IsPersonal: isPersonal, + IsGroup: shedules[0].IsGroup, + SheduleId: shedules[0].SheduleId, + Week: week - bot.Week, + LastUpd: now, + } + if _, err := bot.DB.InsertOne(file); err != nil { + return err + } + + /* + var shId int64 + if isPersonal { + shId = 0 + } else { + shId = shedules[0].SheduleId + } + markup := SummaryKeyboard( + "week", + shId, + shedules[0].IsGroup, + dw, + ) + _, nowWeek := time.Now().ISOWeek() + var str string + if week == nowWeek { + str = fmt.Sprintf("Расписание на текущую (%d) неделю сообщением ниже 👇", week-bot.Week) + } else if week-nowWeek == 1 { + str = fmt.Sprintf("Расписание на следующую (%d) неделю сообщением ниже 👇", week-bot.Week) + } else { + str = fmt.Sprintf("Расписание на %d неделю сообщением ниже 👇", week-bot.Week) + } + bot.EditOrSend(str, markup, editMsg...) + */ + return nil +} + +func GeneratePath(sh database.ShedulesInUser, isPersonal bool, userId int64) string { + var path string + if isPersonal { + path = fmt.Sprintf("personal/%d", userId) + } else if sh.IsGroup { + path = fmt.Sprintf("group/%d", sh.SheduleId) + } else { + path = fmt.Sprintf("staff/%d", sh.SheduleId) + } + return "shedules/" + path +} + +const head = ` + + +Тестовая страница с расписанием + + + + + + + +` +const lessonHead = ` +

+

%s


` + +var shortWeekdays = [6]string{ + "пн", + "вт", + "ср", + "чт", + "пт", + "сб", +} + +func (bot *Bot) CreateHTMLShedule(header string, shedule [][6][]database.Lesson, dates []time.Time, times []ssau_parser.Pair) string { + html := head + html += fmt.Sprintf("
%s
\n", header) + html += "\n\n" + + for i, d := range dates { + day := d.Format("02") + html += fmt.Sprintf("", shortWeekdays[i], day) + } + html += "\n" + for t, tline := range shedule { + begin := times[t].Begin.Format("15:04") + end := times[t].End.Format("15:04") + html += fmt.Sprintf("\n", begin, end) + for i, l := range tline { + + if len(l) > 0 && l[0].Type != "window" { + html += fmt.Sprintf(lessonHead, l[0].Type, l[0].Name) + if l[0].TeacherId != 0 { + var t database.Teacher + bot.DB.ID(l[0].TeacherId).Get(&t) + html += fmt.Sprintf("
%s %s
\n", t.FirstName, t.ShortName) + } + if l[0].Place != "" { + html += fmt.Sprintf("

%s

\n", l[0].Place) + } + if l[0].SubGroup != 0 { + html += fmt.Sprintf("

Подгруппа: %d

\n", l[0].SubGroup) + } + if l[0].Comment != "" { + html += fmt.Sprintf("

%s

\n", l[0].Comment) + } + + if len(l) == 2 { + html += "
\n" + if l[0].Name != l[1].Name { + html += fmt.Sprintf("

\n

%s


", l[1].Name) + } + if l[1].TeacherId != 0 { + var t database.Teacher + bot.DB.ID(l[1].TeacherId).Get(&t) + html += fmt.Sprintf("
%s %s
\n", t.FirstName, t.ShortName) + } + if l[1].Place != "" { + html += fmt.Sprintf("

%s

\n", l[1].Place) + } + if l[1].SubGroup != 0 { + html += fmt.Sprintf("

%d

\n", l[1].SubGroup) + } + if l[1].Comment != "" { + html += fmt.Sprintf("

%s

\n", l[1].Comment) + } + } + + html += "\n" + + } else { + html += "\n" + } + if i%7 == 6 { + html += "\n" + } + } + } + html += "
Время%s

%s

%s
%s
" + return html +}