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, caption string, editMsg ...tgbotapi.Message, ) error { _, week := now.ISOWeek() week += dw - bot.Week var image database.File var cols []string if !isPersonal { image = database.File{ TgId: user.TgId, IsPersonal: false, IsGroup: shedule.IsGroup, SheduleId: shedule.SheduleId, Week: week, } cols = []string{"IsPersonal", "IsGroup"} } else { image = database.File{ TgId: user.TgId, IsPersonal: true, Week: week, } cols = []string{"IsPersonal"} } has, err := bot.DB.UseBool(cols...).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) { if _, err := bot.DB.Delete(&image); err != nil { return err } var shedules []database.ShedulesInUser if isPersonal { shedules = append(shedules, database.ShedulesInUser{L9Id: user.L9Id}) if _, err := bot.DB.Get(&shedules[0]); err != nil { return err } } else { shedules = append(shedules, shedule) } return bot.CreateWeekImg(now, user, shedules, dw, isPersonal, caption, editMsg...) } else { var shId int64 if isPersonal { shId = 0 } else { shId = shedule.SheduleId } markup := tgbotapi.InlineKeyboardMarkup{} if caption == "" { markup = SummaryKeyboard( "sh_week", shId, shedule.IsGroup, dw, ) } _, err := bot.EditOrSend(user.TgId, caption, image.FileId, markup, editMsg...) return err } } 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, caption string, editMsg ...tgbotapi.Message, ) error { _, week := now.ISOWeek() week += dw lessons, err := bot.GetWeekLessons(shedules, week-bot.Week) if err != nil { return err } if len(lessons) == 0 { // TODO: сделать костыль поизящнее и предупреждать, если неделя пустая next, err := bot.GetWeekLessons(shedules, week-bot.Week+1) if err != nil { return err } if len(next) > 0 { lessons = next week += 1 } else { return fmt.Errorf("no lessons: %d, week %d", shedules[0].SheduleId, week-bot.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 } // Проверяем пустые строки и эпирическим путём подбираем для них время (или не подбираем вовсе) for y, line := range shedule { count := 0 for _, l := range line { count += len(l) } if count == 0 { nilPair := ssau_parser.Pair{} if y == len(shedule) { times = append(times, nilPair) } else { times = append(times[:y+1], times[y:]...) times[y] = nilPair } } } var header string if isPersonal { header = fmt.Sprintf("Моё расписание, %d неделя", week-bot.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-bot.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(shedules[0].IsGroup, 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.jpg", path, week-bot.Week) f, _ := os.Create(input) defer f.Close() f.WriteString(html) cmd := exec.CommandContext(context.Background(), bot.WkPath, []string{ "--width", "1600", 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, } // TODO: Загнать эту конструкцию внутрь функции var shId int64 if isPersonal { shId = 0 } else { shId = shedules[0].SheduleId } // Качаем фото и сохраняем данные о нём в БД photo := tgbotapi.NewPhoto(user.TgId, photoFileBytes) photo.Caption = caption if caption == "" { photo.ReplyMarkup = SummaryKeyboard( "sh_week", shId, shedules[0].IsGroup, dw, ) } 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, } _, err = bot.DB.InsertOne(file) // Удаляем старое сообщение if len(editMsg) != 0 { del := tgbotapi.NewDeleteMessage( editMsg[0].Chat.ID, editMsg[0].MessageID, ) if _, err := bot.TG.Request(del); err != nil { return err } } return err } 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( isGroup bool, 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 { var begin, end string if times[t].Begin.IsZero() { begin = ("--:--") end = ("--:--") } else { 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 isGroup && 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 !isGroup { var t database.Group bot.DB.ID(l[0].GroupId).Get(&t) html += fmt.Sprintf("

%s

\n", t.GroupName) } 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 && isGroup { 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) } } if len(l) > 1 && !isGroup { for _, gr := range l[1:] { var t database.Group bot.DB.ID(gr.GroupId).Get(&t) html += fmt.Sprintf("

%s

\n", t.GroupName) if gr.SubGroup != 0 { html += fmt.Sprintf("

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

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

%s

%s
%s
" return html }