This repository has been archived on 2023-08-30. You can view files and clone it, but cannot push or open issues or pull requests.
l9_stud_bot/modules/tg/week_shedule.go

452 lines
12 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
if !isPersonal {
image = database.File{
TgId: user.TgId,
IsPersonal: false,
IsGroup: shedule.IsGroup,
SheduleId: shedule.SheduleId,
Week: week,
}
} else {
image = database.File{
TgId: user.TgId,
IsPersonal: true,
Week: week,
}
}
has, err := bot.DB.UseBool().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.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,
}
// 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 = `<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Тестовая страница с расписанием</title>
<meta name='viewport' content='width=device-width,initial-scale=1'/>
<meta name="mobile-web-app-capable" content="yes">
</head>
<style>
.note div,.rasp div{background-color:#f0f8ff;padding:10px;text-align:center;border-radius:10px}.note,th.head,th.time{font-family:monospace}.subj div #text,.subj p{display:none}html{font-size:1.5rem}body{background:#dc14bd}table{table-layout:fixed;width:100%;border-spacing:5px 5px}.note div{margin:10px 0}.head p,.subj p,hr{margin:0}.rasp div{transition:.3s}th.head{background-color:#0ff;border-radius:10px;padding:5px;font-size:1.05rem}th.subj,th.time{background-color:#f0f8ff;padding:10px;border-radius:10px}th.time{font-size:1.1rem}.subj h2,.subj p{font-size:.85rem}th.subj:not(.lab,.lect,.pract,.other){background-color:#a9a9a9}.subj div{border-radius:10px;padding:5px}.subj p{font-family:monospace;color:#f0f8ff}.subj h2,.subj h3,.subj h5{font-family:monospace;text-align:left;margin:5px}.subj h3{font-size:.65rem}.subj h5{font-size:.7rem;font-weight:400}.lect div{background-color:#7fff00}.pract div{background-color:#dc143c}.lab div{background-color:#8a2be2}.other div{background-color:#ff8c00}.mil div{background-color:#ff8c00}.window div{background-color:blue}
</style>
<body>
`
const lessonHead = `<th class="subj %s" valign="top">
<div><p></p></div>
<h2>%s</h2><hr>`
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("<div class=\"note\"><div id=\"week\">%s</div></div>\n", header)
html += "<table class=\"rasp\">\n<tr><th class=\"head\" style=\"width: 4rem\">Время</th>\n"
for i, d := range dates {
day := d.Format("02")
html += fmt.Sprintf("<th class=\"head\">%s<p>%s</p></th>", shortWeekdays[i], day)
}
html += "</tr>\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("<tr>\n<th class=\"time\">%s<hr>%s</th>", 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("<h5 id=\"prep\">%s %s</h5>\n", t.FirstName, t.ShortName)
}
if l[0].Place != "" {
html += fmt.Sprintf("<h3>%s</h3>\n", l[0].Place)
}
if !isGroup {
var t database.Group
bot.DB.ID(l[0].GroupId).Get(&t)
html += fmt.Sprintf("<h3>%s</h3>\n", t.GroupName)
}
if l[0].SubGroup != 0 {
html += fmt.Sprintf("<h3>Подгруппа: %d</h3>\n", l[0].SubGroup)
}
if l[0].Comment != "" {
html += fmt.Sprintf("<h3>%s</h3>\n", l[0].Comment)
}
if len(l) == 2 && isGroup {
html += "<hr>\n"
if l[0].Name != l[1].Name {
html += fmt.Sprintf("<div><p></p></div>\n<h2>%s</h2><hr>", l[1].Name)
}
if l[1].TeacherId != 0 {
var t database.Teacher
bot.DB.ID(l[1].TeacherId).Get(&t)
html += fmt.Sprintf("<h5 id=\"prep\">%s %s</h5>\n", t.FirstName, t.ShortName)
}
if l[1].Place != "" {
html += fmt.Sprintf("<h3>%s</h3>\n", l[1].Place)
}
if l[1].SubGroup != 0 {
html += fmt.Sprintf("<h3>Подгруппа: %d</h3>\n", l[1].SubGroup)
}
if l[1].Comment != "" {
html += fmt.Sprintf("<h3>%s</h3>\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("<h3>%s</h3>\n", t.GroupName)
if gr.SubGroup != 0 {
html += fmt.Sprintf("<h3>Подгруппа: %d</h3>\n<hr>\n", l[1].SubGroup)
}
}
}
html += "</th>\n"
} else {
html += "<th class=\"subj\"></th>\n"
}
if i%7 == 6 {
html += "</tr>\n"
}
}
}
html += "</table></body></html>"
return html
}