-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3 from bit0r1n/classrooms-search
Classrooms: free classrooms search
- Loading branch information
Showing
23 changed files
with
569 additions
and
60 deletions.
There are no files selected for viewing
274 changes: 274 additions & 0 deletions
274
bot/src/commands/actions/schedule/classrooms/classroomSchedule.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,274 @@ | ||
import { Composer, Markup } from 'telegraf' | ||
import { | ||
callbackIdBuild, | ||
ClassroomScheduleType, | ||
getDayBounds, | ||
IDayBounds, | ||
inlineKeyboards, | ||
SuperDuperUpgradedContext | ||
} from '../../../../utils' | ||
import { Classroom, ClassroomLocation, classroomLocationToHuman, Keeper, Lesson, LessonTime, lessonTimeToHuman } from '../../../../keeper' | ||
|
||
export const classroomScheduleHandler = new Composer<SuperDuperUpgradedContext>() | ||
|
||
const keeper = new Keeper(process.env.KEEPER_URL!) | ||
|
||
export enum ClassroomLocationSearch { | ||
InBuilding, | ||
OldBuilding, | ||
NewBuilding, | ||
Dormitory, | ||
Everywhere | ||
} | ||
|
||
const classroomLocationCarousel = [ | ||
ClassroomLocationSearch.InBuilding, | ||
ClassroomLocationSearch.OldBuilding, | ||
ClassroomLocationSearch.NewBuilding, | ||
ClassroomLocationSearch.Dormitory, | ||
ClassroomLocationSearch.Everywhere | ||
] | ||
|
||
const lessonTimeCarousel = [ | ||
LessonTime.First, | ||
LessonTime.Second, | ||
LessonTime.Third, | ||
LessonTime.Fourth, | ||
LessonTime.Fifth, | ||
LessonTime.Sixth, | ||
LessonTime.Seventh, | ||
LessonTime.Eighth | ||
] | ||
|
||
const searchLocationToKeeper = (location: ClassroomLocationSearch): ClassroomLocation[] => { | ||
switch (location) { | ||
case ClassroomLocationSearch.InBuilding: | ||
return [ ClassroomLocation.NewBuilding, ClassroomLocation.OldBuilding ] | ||
case ClassroomLocationSearch.OldBuilding: | ||
return [ ClassroomLocation.OldBuilding ] | ||
case ClassroomLocationSearch.NewBuilding: | ||
return [ ClassroomLocation.OldBuilding ] | ||
case ClassroomLocationSearch.Dormitory: | ||
return [ ClassroomLocation.Dormitory ] | ||
case ClassroomLocationSearch.Everywhere: | ||
return [ ClassroomLocation.NewBuilding, ClassroomLocation.OldBuilding, ClassroomLocation.NewBuilding ] | ||
} | ||
} | ||
|
||
const classroomLocationToString = (location: ClassroomLocationSearch) => { | ||
switch (location) { | ||
case ClassroomLocationSearch.InBuilding: | ||
return 'В университете' | ||
case ClassroomLocationSearch.OldBuilding: | ||
return 'Старый корпус' | ||
case ClassroomLocationSearch.NewBuilding: | ||
return 'Новый корпус' | ||
case ClassroomLocationSearch.Dormitory: | ||
return 'Общежитие' | ||
case ClassroomLocationSearch.Everywhere: | ||
return 'Везде' | ||
} | ||
} | ||
|
||
function filterUnusedClassrooms(classrooms: Classroom[], lessons: Lesson[]): Classroom[] { | ||
const usedClassrooms = new Set(lessons.flatMap(lesson => lesson.classrooms)) | ||
return classrooms.filter(classroom => !usedClassrooms.has(classroom.name)) | ||
} | ||
|
||
function setRange(bounds: IDayBounds, range: string): IDayBounds { | ||
const match = range.match(/^(\d{2}):(\d{2}) - (\d{2}):(\d{2})$/) | ||
if (!match) { | ||
throw new Error('Invalid range') | ||
} | ||
|
||
const [ , startHour, startMinute, endHour, endMinute ] = match.map(Number) | ||
|
||
bounds.start.setHours(startHour - 3, startMinute, 0, 0) | ||
bounds.end.setHours(endHour - 3, endMinute, 0, 0) | ||
|
||
return bounds | ||
} | ||
|
||
function groupClassrooms(classrooms: Classroom[]): Record<ClassroomLocation, Classroom[]> { | ||
return classrooms.reduce((acc, classroom) => { | ||
if (!acc[classroom.location]) { | ||
acc[classroom.location] = [] | ||
} | ||
|
||
acc[classroom.location].push(classroom) | ||
|
||
acc[classroom.location].sort((a, b) => a.floor - b.floor) | ||
|
||
return acc | ||
}, {} as Record<ClassroomLocation, Classroom[]>) | ||
} | ||
|
||
async function updateFreeSearchMessage(ctx: SuperDuperUpgradedContext) { | ||
if (!ctx.session?.classroomSearch) return | ||
|
||
const computerEmoji = ctx.session.classroomSearch.onlyComputer ? '✅' : '❌' | ||
|
||
await ctx.editMessageText('🥂 Выбери где и к скольки ты хочешь увидеть свободные на сегодня аудитории', { | ||
reply_markup: Markup.inlineKeyboard([ | ||
[ | ||
Markup.button.callback('<', | ||
callbackIdBuild('classroom_schedule', [ | ||
ClassroomScheduleType.Free, | ||
'type', | ||
'prev' | ||
]) | ||
), | ||
Markup.button.callback(classroomLocationToString(classroomLocationCarousel[ctx.session?.classroomSearch.locationIndex]), | ||
callbackIdBuild('classroom_schedule', [ | ||
ClassroomScheduleType.Free, | ||
'type', | ||
'current' | ||
]) | ||
), | ||
Markup.button.callback('>', | ||
callbackIdBuild('classroom_schedule', [ | ||
ClassroomScheduleType.Free, | ||
'type', | ||
'next' | ||
]) | ||
) | ||
], | ||
[ | ||
Markup.button.callback('<', | ||
callbackIdBuild('classroom_schedule', [ | ||
ClassroomScheduleType.Free, | ||
'time', | ||
'prev' | ||
]) | ||
), | ||
Markup.button.callback(lessonTimeToHuman(lessonTimeCarousel[ctx.session.classroomSearch.timeIndex]), | ||
callbackIdBuild('classroom_schedule', [ | ||
ClassroomScheduleType.Free, | ||
'time', | ||
'current' | ||
]) | ||
), | ||
Markup.button.callback('>', | ||
callbackIdBuild('classroom_schedule', [ | ||
ClassroomScheduleType.Free, | ||
'time', | ||
'next' | ||
]) | ||
) | ||
], | ||
[ | ||
Markup.button.callback(computerEmoji + ' Компьютерная аудитория', | ||
callbackIdBuild('classroom_schedule', [ | ||
ClassroomScheduleType.Free, | ||
'only_computer' | ||
]) | ||
) | ||
], | ||
[ | ||
Markup.button.callback('Искать', | ||
callbackIdBuild('classroom_schedule', [ | ||
ClassroomScheduleType.Free, | ||
'confirm' | ||
]) | ||
) | ||
] | ||
]).reply_markup | ||
}) | ||
} | ||
|
||
classroomScheduleHandler.action('classroom_schedule', async (ctx) => { | ||
ctx.session = { classroomSearch: { locationIndex: 0, timeIndex: 0, onlyComputer: false } } | ||
await ctx.editMessageText('🥏 Выбери что тебе нужно найти', { | ||
reply_markup: inlineKeyboards.classroomScheduleType.reply_markup | ||
}) | ||
}) | ||
|
||
classroomScheduleHandler.action(callbackIdBuild('classroom_schedule', [ ClassroomScheduleType.Free ]), async (ctx) => { | ||
await updateFreeSearchMessage(ctx) | ||
}) | ||
|
||
classroomScheduleHandler.action( | ||
[ | ||
callbackIdBuild('classroom_schedule', [ ClassroomScheduleType.Free, 'type', 'next' ]), | ||
callbackIdBuild('classroom_schedule', [ ClassroomScheduleType.Free, 'type', 'prev' ]), | ||
callbackIdBuild('classroom_schedule', [ ClassroomScheduleType.Free, 'type', 'current' ]) | ||
], async (ctx) => { | ||
if (ctx.match.input.includes('current')) return | ||
if (!ctx.session?.classroomSearch) { | ||
return await ctx.editMessageText('🤥') | ||
} | ||
|
||
ctx.session.classroomSearch.locationIndex = | ||
(ctx.match.input.includes('next') | ||
? (ctx.session.classroomSearch.locationIndex + 1) | ||
: (ctx.session.classroomSearch.locationIndex - 1 + classroomLocationCarousel.length)) | ||
% classroomLocationCarousel.length | ||
|
||
await updateFreeSearchMessage(ctx) | ||
}) | ||
|
||
classroomScheduleHandler.action( | ||
[ | ||
callbackIdBuild('classroom_schedule', [ ClassroomScheduleType.Free, 'time', 'next' ]), | ||
callbackIdBuild('classroom_schedule', [ ClassroomScheduleType.Free, 'time', 'prev' ]), | ||
callbackIdBuild('classroom_schedule', [ ClassroomScheduleType.Free, 'time', 'current' ]) | ||
], async (ctx) => { | ||
if (ctx.match.input.includes('current')) return | ||
if (!ctx.session?.classroomSearch) { | ||
return await ctx.editMessageText('🤥') | ||
} | ||
|
||
ctx.session.classroomSearch.timeIndex = | ||
(ctx.match.input.includes('next') | ||
? (ctx.session.classroomSearch.timeIndex + 1) | ||
: (ctx.session.classroomSearch.timeIndex - 1 + lessonTimeCarousel.length)) | ||
% lessonTimeCarousel.length | ||
|
||
await updateFreeSearchMessage(ctx) | ||
}) | ||
|
||
classroomScheduleHandler.action(callbackIdBuild('classroom_schedule', [ ClassroomScheduleType.Free, 'only_computer' ]), async (ctx) => { | ||
if (!ctx.session?.classroomSearch) { | ||
return await ctx.editMessageText('🤥') | ||
} | ||
|
||
ctx.session.classroomSearch.onlyComputer = !ctx.session.classroomSearch.onlyComputer | ||
|
||
await updateFreeSearchMessage(ctx) | ||
}) | ||
|
||
classroomScheduleHandler.action(callbackIdBuild('classroom_schedule', [ ClassroomScheduleType.Free, 'confirm' ]), async (ctx) => { | ||
if (!ctx.session?.classroomSearch) { | ||
return await ctx.editMessageText('🤥') | ||
} | ||
|
||
const { locationIndex, timeIndex, onlyComputer } = ctx.session.classroomSearch | ||
|
||
const locationSearch = searchLocationToKeeper(classroomLocationCarousel[locationIndex]) | ||
const timeSearch = lessonTimeCarousel[timeIndex] | ||
const todayBounds = getDayBounds() | ||
setRange(todayBounds, lessonTimeToHuman(timeSearch)) | ||
|
||
const classrooms = await keeper.getClassrooms({ location: locationSearch, is_computer: onlyComputer }) | ||
const lessons = await keeper.getLessons({ from: todayBounds.start, before: todayBounds.end }) | ||
|
||
const unused = filterUnusedClassrooms(classrooms, lessons) | ||
|
||
if (!unused.length) { | ||
return await ctx.editMessageText('🚗 Где все') | ||
} | ||
|
||
const groupedUnused = groupClassrooms(unused) | ||
let unusedInfo = '' | ||
|
||
for (const [ location, classrooms ] of Object.entries(groupedUnused)) { | ||
unusedInfo | ||
+= '\t⛳ ' + classroomLocationToHuman(parseInt(location)) | ||
+ '\n' | ||
+ '\t\t' + classrooms.map(c => c.name).join(', ') | ||
+ '\n\n' | ||
} | ||
|
||
await ctx.editMessageText(`🪙 Свободные на сегодня (${lessonTimeToHuman(lessonTimeCarousel[timeIndex])}) аудитории:\n\n` | ||
+ unusedInfo) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import { Composer } from 'telegraf' | ||
import { SuperDuperUpgradedContext } from '../../../../utils' | ||
import { classroomScheduleHandler } from './classroomSchedule' | ||
|
||
export const classroomScheduleMasterHandler = new Composer<SuperDuperUpgradedContext>() | ||
|
||
classroomScheduleMasterHandler | ||
.use(classroomScheduleHandler) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from './classrooms' | ||
export * from './weeks' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 1 addition & 1 deletion
2
bot/src/commands/actions/weeks/index.ts → .../commands/actions/schedule/weeks/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
8 changes: 4 additions & 4 deletions
8
bot/src/commands/actions/weeks/selfWeek.ts → ...mmands/actions/schedule/weeks/selfWeek.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.