Добавлено: недельное расписание

(не проверено и не допилено полностью, просто я устал)
This commit is contained in:
far-galaxy 2023-08-08 19:36:33 +04:00
parent a3bab6398f
commit 530646e13c
8 changed files with 450 additions and 11 deletions

View File

@ -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)

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)
}
}
}

View File

@ -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 {

389
modules/tg/week_shedule.go Normal file
View File

@ -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 = `<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(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 {
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 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 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 {
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)
}
}
html += "</th>\n"
} else {
html += "<th class=\"subj\"></th>\n"
}
if i%7 == 6 {
html += "</tr>\n"
}
}
}
html += "</table></body></html>"
return html
}