Добавлено: краткая сводка (частично)

This commit is contained in:
far-galaxy 2023-08-03 21:52:18 +04:00
parent 98ba725f5b
commit 0c2cf9118a
8 changed files with 348 additions and 48 deletions

View File

@ -5,10 +5,12 @@ import (
"os"
"git.l9labs.ru/anufriev.g.a/l9_stud_bot/modules/database"
"git.l9labs.ru/anufriev.g.a/l9_stud_bot/modules/ssau_parser"
"git.l9labs.ru/anufriev.g.a/l9_stud_bot/modules/tg"
)
func main() {
ssau_parser.HeadURL = "http://127.0.0.1:5000/prod"
tg.CheckEnv()
//bot := new(tg.Bot)

View File

@ -2,6 +2,7 @@ package database
import "time"
// Пользователь системы (задел под сайт)
type User struct {
L9Id int64 `xorm:"pk"`
}
@ -18,6 +19,7 @@ const (
SelSeeStaff Position = "select_see_staff" // Выбирает преподавателя для автономной карточки
)
// Пользователь Telegram
type TgUser struct {
L9Id int64 `xorm:"pk"`
TgId int64
@ -25,27 +27,33 @@ type TgUser struct {
PosTag Position
}
// Подключённое к пользователю расписание
type ShedulesInUser struct {
UID int64 `xorm:"pk autoincr"` // Не забывать про автоинкремент!!!
L9Id int64
IsTeacher bool
IsGroup bool
SheduleId int64
}
// Учебная группа
type Group struct {
GroupId int64 `xorm:"pk"`
GroupName string // Полный номер группы
SpecName string // Шифр и название специальности
LastUpd time.Time
}
// Преподаватель
type Teacher struct {
TeacherId int64 `xorm:"pk"`
FirstName string // Фамилия
LastName string // Имя, отчество и прочие окончания
ShortName string // Инициалы
SpecName string // Место работы
LastUpd time.Time
}
// Занятие
type Lesson struct {
LessonId int64 `xorm:"pk autoincr"`
NumInShedule int

View File

@ -2,6 +2,7 @@ package ssau_parser
import (
"strings"
"time"
"git.l9labs.ru/anufriev.g.a/l9_stud_bot/modules/database"
"xorm.io/xorm"
@ -10,10 +11,12 @@ import (
// Согласование недельного расписания с БД
// Возвращает соответственно добавленные и удалённые занятия
func UpdateSchedule(db *xorm.Engine, sh WeekShedule) ([]database.Lesson, []database.Lesson, error) {
if err := checkGroupOrTeacher(db, sh); err != nil {
if _, err := CheckGroupOrTeacher(db, sh); err != nil {
return nil, nil, err
}
if len(sh.Uncovered) == 0 {
return nil, nil, nil
}
first_new := sh.Uncovered[0]
_, week := first_new.Begin.ISOWeek()
var old []database.Lesson
@ -33,6 +36,28 @@ func UpdateSchedule(db *xorm.Engine, sh WeekShedule) ([]database.Lesson, []datab
return nil, nil, err
}
}
// Обновляем время обновления
if len(add) > 0 || len(del) > 0 {
if sh.IsGroup {
gr := database.Group{GroupId: sh.SheduleId}
if _, err := db.Get(&gr); err != nil {
return nil, nil, err
}
gr.LastUpd = time.Now()
if _, err := db.Update(gr); err != nil {
return nil, nil, err
}
} else {
var t database.Teacher
if err := db.Find(&t, &database.Teacher{TeacherId: sh.SheduleId}); err != nil {
return nil, nil, err
}
t.LastUpd = time.Now()
if _, err := db.Update(t); err != nil {
return nil, nil, err
}
}
}
return add, del, nil
}
@ -57,12 +82,13 @@ func isTeacherExists(db *xorm.Engine, teacherId int64) (bool, error) {
}
// Проверка наличия группы или преподавателя в БД и добавление при необходимости
// Возвращает истину, если группы/преподавателя раньше не было
// TODO: Добавить проверку изменений в полях данных
func checkGroupOrTeacher(db *xorm.Engine, sh WeekShedule) error {
func CheckGroupOrTeacher(db *xorm.Engine, sh WeekShedule) (bool, error) {
if sh.IsGroup {
exists, err := isGroupExists(db, sh.SheduleId)
if err != nil {
return err
return false, err
}
if !exists {
@ -72,11 +98,12 @@ func checkGroupOrTeacher(db *xorm.Engine, sh WeekShedule) error {
SpecName: sh.SpecName,
}
db.InsertOne(group)
return true, nil
}
} else {
exists, err := isTeacherExists(db, sh.SheduleId)
if err != nil {
return err
return false, err
}
if !exists {
@ -84,10 +111,11 @@ func checkGroupOrTeacher(db *xorm.Engine, sh WeekShedule) error {
teacher.TeacherId = sh.SheduleId
teacher.SpecName = sh.SpecName
db.InsertOne(teacher)
return true, nil
}
}
return nil
return false, nil
}
func ParseTeacherName(fullName string) database.Teacher {

View File

@ -41,10 +41,10 @@ func TestCheckGroupOrTeacher(t *testing.T) {
}
err := sh.DownloadById(false)
handleError(err)
err = checkGroupOrTeacher(db, sh)
_, err = CheckGroupOrTeacher(db, sh)
handleError(err)
// Повторяем на предмет наличия
err = checkGroupOrTeacher(db, sh)
_, err = CheckGroupOrTeacher(db, sh)
handleError(err)
// Проверяем преподавателя
@ -55,7 +55,7 @@ func TestCheckGroupOrTeacher(t *testing.T) {
}
err = sh.DownloadById(false)
handleError(err)
err = checkGroupOrTeacher(db, sh)
_, err = CheckGroupOrTeacher(db, sh)
handleError(err)
}

View File

@ -63,6 +63,7 @@ func GetSheduleInfo(doc *goquery.Document, sh *WeekShedule) {
var hourMap = map[int]int{8: 0, 9: 1, 11: 2, 13: 3, 15: 4, 17: 5, 18: 6, 20: 7}
// Парсинг страницы с расписанием
// TODO: находить окна
func (sh *WeekShedule) Parse(p Page, uncover bool) error {
doc := p.Doc
GetSheduleInfo(doc, sh)
@ -208,6 +209,10 @@ func ParseLesson(s *goquery.Selection, isGroup bool, sheduleId int64) []Lesson {
} else {
lesson.SubGroup = append(lesson.SubGroup, 0)
}
} else if isGroup && len(groupId) > 1 {
for range groupId {
lesson.SubGroup = append(lesson.SubGroup, 0)
}
}
place := l.Find("div.schedule__place").First().Text()

View File

@ -94,23 +94,21 @@ func (bot *Bot) Find(user *database.TgUser, query string) (tgbotapi.Message, err
sheduleId = allTeachers[0].TeacherId
isGroup = false
}
shedule := database.ShedulesInUser{
IsTeacher: !isGroup,
shedule := ssau_parser.WeekShedule{
IsGroup: isGroup,
SheduleId: sheduleId,
}
msg := tgbotapi.NewMessage(
user.TgId,
fmt.Sprintf(
"Тут должно было быть расписание, но его пока не завезли\n%d",
shedule.SheduleId,
),
)
return bot.TG.Send(msg)
/*
err := bot.GetSummary([]database.ShedulesInUser{shedule}, false)
not_exists, _ := ssau_parser.CheckGroupOrTeacher(bot.DB, shedule)
if not_exists {
msg := tgbotapi.NewMessage(user.TgId, "Загружаю расписание...\nЭто займёт некоторое время")
bot.TG.Send(msg)
err := bot.LoadShedule(shedule)
if err != nil {
return err
}*/
bot.Debug.Println(err)
}
}
bot.GetSummary(user, []database.ShedulesInUser{Swap(shedule)}, false)
return tgbotapi.Message{}, nil
}
// Если получено несколько групп
@ -158,7 +156,7 @@ func (bot *Bot) GetShedule(user *database.TgUser, query *tgbotapi.CallbackQuery)
}
if !isAdd {
shedule := database.ShedulesInUser{
IsTeacher: !isGroup,
IsGroup: isGroup,
SheduleId: groupId,
}
msg := tgbotapi.NewEditMessageText(

248
modules/tg/shedule.go Normal file
View File

@ -0,0 +1,248 @@
package tg
import (
"fmt"
"log"
"strconv"
"strings"
"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"
"xorm.io/xorm"
)
func (bot *Bot) GetPersonalSummary(user *database.TgUser, msg ...tgbotapi.Message) {
var shedules []database.ShedulesInUser
bot.DB.ID(user.L9Id).Find(&shedules)
if len(shedules) == 0 {
bot.Etc(user)
return
} else {
err := bot.GetSummary(user, shedules, true, msg...)
if err != nil {
log.Fatal(err)
}
}
}
// Получить краткую сводку
func (bot *Bot) GetSummary(
user *database.TgUser,
shedules []database.ShedulesInUser,
isPersonal bool,
editMsg ...tgbotapi.Message) error {
now, _ := time.Parse("2006-01-02 15:04 -07", "2023-03-06 07:20 +04") //time.Now().Add(time.Hour * time.Duration(24) * (-1) * 30 * 4)
lessons, err := bot.GetLessons(shedules, now)
if err != nil {
return err
}
if len(lessons) != 0 {
var firstPair, secondPair []database.Lesson
pairs := GroupPairs(lessons)
firstPair = pairs[0]
log.Println(firstPair, secondPair)
str := "📝Краткая сводка:\n\n"
if pairs[0][0].Begin.Day() != now.Day() {
str += "❗️Сегодня пар нет\nБлижайшие занятия "
if firstPair[0].Begin.Sub(now).Hours() < 48 {
str += "завтра\n"
} else {
// TODO: добавить прописные названия месяцев
str += fmt.Sprintf("%s\n\n", firstPair[0].Begin.Format("02.01"))
} /*
day, err := bot.GetDayShedule(pairs)
if err != nil {
return err
}
str += day*/
} else {
if firstPair[0].Begin.Before(now) {
str += "Сейчас:\n\n"
} else {
str += "Ближайшая пара сегодня:\n\n"
}
firstStr, err := PairToStr(firstPair, bot.DB)
if err != nil {
return err
}
str += firstStr
if len(pairs) > 1 {
secondPair = pairs[1]
if firstPair[0].Begin.Day() == secondPair[0].Begin.Day() {
str += "\nПосле неё:\n\n"
secondStr, err := PairToStr(secondPair, bot.DB)
if err != nil {
return err
}
str += secondStr
} else {
str += "\nБольше ничего сегодня нет"
}
} else {
str += "\nБольше ничего сегодня нет"
}
}
/*
var shId int64
if isPersonal {
shId = 0
} else {
shId = shedules[0].SheduleId
}
markup := SummaryKeyboard(
// TODO: создать тип таких префиксов
"sh_near",
shId,
shedules[0].IsGroup,
0,
)*/
bot.EditOrSend(user.TgId, str, tgbotapi.NewInlineKeyboardMarkup(), editMsg...)
} else {
msg := tgbotapi.NewMessage(user.TgId, "Ой! Пар не обнаружено ):")
bot.TG.Send(msg)
}
return nil
}
// Получить список ближайших занятий (для краткой сводки или расписания на день)
func (bot *Bot) GetLessons(shedules []database.ShedulesInUser, now time.Time) ([]database.Lesson, error) {
condition := CreateCondition(shedules)
var lessons []database.Lesson
err := bot.DB.
Where("end > ?", now.Format("2006-01-02 15:04:05")).
And(condition).
OrderBy("begin").
Limit(16).
Find(&lessons)
return lessons, err
}
// Загрузка расписания из ssau.ru/rasp
func (bot *Bot) LoadShedule(shedule ssau_parser.WeekShedule) error {
sh := ssau_parser.WeekShedule{
SheduleId: shedule.SheduleId,
IsGroup: shedule.IsGroup,
}
// TODO: вынести количество недель в переменную, либо автоматически определять конец
for week := 1; week < 21; week++ {
sh.Week = week
err := sh.DownloadById(true)
if err != nil {
return err
}
_, _, err = ssau_parser.UpdateSchedule(bot.DB, sh)
if err != nil {
return err
}
}
return nil
}
// Создать условие группы/преподавателя
func CreateCondition(shedules []database.ShedulesInUser) string {
var groups []string
var teachers []string
for _, sh := range shedules {
if !sh.IsGroup {
teachers = append(teachers, strconv.FormatInt(sh.SheduleId, 10))
} else {
groups = append(groups, strconv.FormatInt(sh.SheduleId, 10))
}
}
var condition, teachers_str, groups_str string
if len(groups) > 0 {
groups_str = strings.Join(groups, ",")
condition = "groupId in (" + groups_str + ") "
}
if len(teachers) > 0 {
if len(condition) > 0 {
condition += " or "
}
teachers_str += strings.Join(teachers, ",")
condition += "teacherId in (" + teachers_str + ") "
}
return condition
}
func GroupPairs(lessons []database.Lesson) [][]database.Lesson {
var shedule [][]database.Lesson
var pair []database.Lesson
l_idx := 0
for l_idx < len(lessons) {
day := lessons[l_idx].Begin
for l_idx < len(lessons) && lessons[l_idx].Begin == day {
pair = append(pair, lessons[l_idx])
l_idx++
}
shedule = append(shedule, pair)
pair = []database.Lesson{}
}
return shedule
}
func PairToStr(pair []database.Lesson, db *xorm.Engine) (string, error) {
var str string
beginStr := pair[0].Begin.Format("15:04")
endStr := pair[0].End.Format("15:04")
str = fmt.Sprintf("📆 %s - %s\n", beginStr, endStr)
for i, sublesson := range pair {
var type_emoji string
switch sublesson.Type {
case "lect":
type_emoji = "📗"
case "pract":
type_emoji = "📕"
case "lab":
type_emoji = "📘"
case "other":
type_emoji = "📙"
case "mil":
type_emoji = "🗿"
default:
type_emoji = "📙"
}
str += fmt.Sprintf("%s%s\n", type_emoji, sublesson.Name)
if sublesson.Place != "" {
str += fmt.Sprintf("🧭 %s\n", sublesson.Place)
}
if sublesson.TeacherId != 0 {
var t database.Teacher
_, err := db.ID(sublesson.TeacherId).Get(&t)
if err != nil {
return "", err
}
str += fmt.Sprintf("👤 %s %s\n", t.FirstName, t.ShortName)
}
if sublesson.SubGroup != 0 {
str += fmt.Sprintf("👥 Подгруппа: %d\n", sublesson.SubGroup)
}
if sublesson.Comment != "" {
str += fmt.Sprintf("💬 %s\n", sublesson.Comment)
}
if i != len(pair)-1 {
str += "+\n"
}
}
str += "------------------------------------------\n"
return str, nil
}

View File

@ -6,6 +6,7 @@ import (
"strings"
"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"
)
@ -61,24 +62,24 @@ func GenerateKeyboard(array []tgbotapi.InlineKeyboardButton) tgbotapi.InlineKeyb
return tgbotapi.InlineKeyboardMarkup{InlineKeyboard: markup}
}
func SummaryKeyboard(clickedButton string, sheduleId int64, isTeacher bool, dt int) tgbotapi.InlineKeyboardMarkup {
tail := GenerateButtonTail(sheduleId, 0, isTeacher)
func SummaryKeyboard(clickedButton string, sheduleId int64, isGroup bool, dt int) tgbotapi.InlineKeyboardMarkup {
tail := GenerateButtonTail(sheduleId, 0, isGroup)
near := []tgbotapi.InlineKeyboardButton{
tgbotapi.NewInlineKeyboardButtonData("Краткая сводка", "near"+tail),
tgbotapi.NewInlineKeyboardButtonData("Краткая сводка", "sh_near"+tail),
}
day := []tgbotapi.InlineKeyboardButton{
tgbotapi.NewInlineKeyboardButtonData("День", "day"+tail),
tgbotapi.NewInlineKeyboardButtonData("День", "sh_day"+tail),
}
week := []tgbotapi.InlineKeyboardButton{
tgbotapi.NewInlineKeyboardButtonData("Неделя", "week"+tail),
tgbotapi.NewInlineKeyboardButtonData("Неделя", "sh_week"+tail),
}
update := GenerateButtonTail(sheduleId, dt, isTeacher)
update := GenerateButtonTail(sheduleId, dt, isGroup)
var arrows []tgbotapi.InlineKeyboardButton
if clickedButton == "day" || clickedButton == "week" {
prev_arrow := GenerateButtonTail(sheduleId, dt-1, isTeacher)
next_arrow := GenerateButtonTail(sheduleId, dt+1, isTeacher)
if clickedButton == "sh_day" || clickedButton == "sh_week" {
prev_arrow := GenerateButtonTail(sheduleId, dt-1, isGroup)
next_arrow := GenerateButtonTail(sheduleId, dt+1, isGroup)
arrows = []tgbotapi.InlineKeyboardButton{
tgbotapi.NewInlineKeyboardButtonData("⏮", clickedButton+prev_arrow),
tgbotapi.NewInlineKeyboardButtonData("🔄", clickedButton+update),
@ -95,11 +96,11 @@ func SummaryKeyboard(clickedButton string, sheduleId int64, isTeacher bool, dt i
var markup [][]tgbotapi.InlineKeyboardButton
switch clickedButton {
case "day":
case "sh_day":
markup = [][]tgbotapi.InlineKeyboardButton{
arrows, near, week,
}
case "week":
case "sh_week":
markup = [][]tgbotapi.InlineKeyboardButton{
arrows, near, day,
}
@ -114,11 +115,11 @@ func SummaryKeyboard(clickedButton string, sheduleId int64, isTeacher bool, dt i
return tgbotapi.InlineKeyboardMarkup{InlineKeyboard: markup}
}
func GenerateButtonTail(sheduleId int64, dt int, isTeacher bool) string {
func GenerateButtonTail(sheduleId int64, dt int, isGroup bool) string {
var tail string
if sheduleId == 0 {
tail = fmt.Sprintf("_personal_%d_0", dt)
} else if isTeacher {
} else if !isGroup {
tail = fmt.Sprintf("_teacher_%d_%d", dt, sheduleId)
} else {
tail = fmt.Sprintf("_group_%d_%d", dt, sheduleId)
@ -126,8 +127,7 @@ func GenerateButtonTail(sheduleId int64, dt int, isTeacher bool) string {
return tail
}
/*
func (bot *Bot) EditOrSend(str string, markup tgbotapi.InlineKeyboardMarkup, editMsg ...tgbotapi.Message) {
func (bot *Bot) EditOrSend(id int64, str string, markup tgbotapi.InlineKeyboardMarkup, editMsg ...tgbotapi.Message) {
if len(editMsg) > 0 {
msg := tgbotapi.NewEditMessageText(
editMsg[0].Chat.ID,
@ -137,23 +137,26 @@ func (bot *Bot) EditOrSend(str string, markup tgbotapi.InlineKeyboardMarkup, edi
msg.ReplyMarkup = &markup
bot.TG.Request(msg)
} else {
msg := tgbotapi.NewMessage(bot.TG_user.TgId, str)
msg.ReplyMarkup = markup
bot.TG.Send(msg)
msg := tgbotapi.NewMessage(id, str)
//msg.ReplyMarkup = &markup
_, err := bot.TG.Send(msg)
if err != nil {
bot.Debug.Println(err)
}
}
}*/
}
func ParseQuery(data []string) ([]database.ShedulesInUser, int, error) {
isGroup := data[1] == "group"
sheduleId, err := strconv.ParseInt(data[3], 0, 64)
isGroup := data[2] == "group"
sheduleId, err := strconv.ParseInt(data[4], 0, 64)
if err != nil {
return nil, 0, err
}
shedule := database.ShedulesInUser{
IsTeacher: !isGroup,
IsGroup: !isGroup,
SheduleId: sheduleId,
}
dt, err := strconv.ParseInt(data[2], 0, 0)
dt, err := strconv.ParseInt(data[3], 0, 0)
if err != nil {
return nil, 0, err
}
@ -175,3 +178,11 @@ func (bot *Bot) DeleteMsg(query *tgbotapi.CallbackQuery) {
delete := tgbotapi.NewDeleteMessage(query.From.ID, query.Message.MessageID)
bot.TG.Request(delete)
}
// Меняем шило на мыло
func Swap(sh ssau_parser.WeekShedule) database.ShedulesInUser {
return database.ShedulesInUser{
IsGroup: sh.IsGroup,
SheduleId: sh.SheduleId,
}
}