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

455 lines
12 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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 = `<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>
.subj div,th.head,th.subj,th.time{border-radius:10px}.note,.subj p,th.head,th.time{font-family:monospace}.note div,.rasp div{background-color:#f0f8ff;padding:10px;text-align:center;border-radius:10px}.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;padding:5px;font-size:1.05rem}th.subj,th.time{background-color:#f0f8ff;padding: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{padding:5px}.subj p{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}.mil div,.other div{background-color:#ff8c00}.window div{background-color:#00f}.cons div{background-color:green}.exam div{background-color:purple}.kurs div{background-color:orange}
</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
}