diff --git a/backend/package.json b/backend/package.json
index 87aa9a6..95a8dca 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -16,9 +16,9 @@
"@grammyjs/auto-retry": "^1.1.1",
"@grammyjs/runner": "^2.0.3",
"@prisma/client": "5.11.0",
- "@telegum/grammy-buttons": "^0.4.0",
- "@telegum/grammy-messages": "^0.4.0",
- "@telegum/tgx": "^0.1.1",
+ "@telegum/grammy-buttons": "^0.5.1",
+ "@telegum/grammy-messages": "^0.5.1",
+ "@telegum/tgx": "^0.2.1",
"axios": "^1.6.8",
"dotenv": "^16.4.5",
"grammy": "^1.22.4",
diff --git a/backend/src/bot/handlers/commands/start.ts b/backend/src/bot/handlers/commands/menu.ts
similarity index 93%
rename from backend/src/bot/handlers/commands/start.ts
rename to backend/src/bot/handlers/commands/menu.ts
index 9b9afcf..ce52ef8 100644
--- a/backend/src/bot/handlers/commands/start.ts
+++ b/backend/src/bot/handlers/commands/menu.ts
@@ -4,7 +4,7 @@ import views from '~/bot/handlers/views'
export default handler((composer) => {
composer
- .command('start')
+ .command('menu')
.filter(filters.pm)
.use(async (ctx) => {
await ctx
diff --git a/backend/src/bot/handlers/commands/start.tsx b/backend/src/bot/handlers/commands/start.tsx
new file mode 100644
index 0000000..90f7ebb
--- /dev/null
+++ b/backend/src/bot/handlers/commands/start.tsx
@@ -0,0 +1,49 @@
+import views from '~/bot/handlers/views'
+import { handler } from '~/bot/handlers'
+import filters from '~/bot/filters'
+import { TelegramNotLinkedToInnohassleAccountError } from '~/domain/errors'
+import { InnohassleLoginButton } from '~/bot/login-button'
+
+export default handler((composer) => {
+ composer
+ .command('start')
+ .filter(filters.pm)
+ .use(async (ctx) => {
+ let authorized
+ try {
+ authorized = await ctx.domain.isUserAuthorized(ctx.from.id)
+ } catch (error) {
+ authorized = false
+ if (error instanceof TelegramNotLinkedToInnohassleAccountError) {
+ // ignore
+ } else {
+ ctx.logger.error({
+ msg: 'failed to check whether user is authorized',
+ error: error,
+ })
+ }
+ }
+
+ let content
+ if (authorized) {
+ content = await views.main.render(ctx, {})
+ } else {
+ content = (
+ <>
+ {ctx.t['WelcomeMessage.Unauthorized']}
+
+
+ {ctx.t['Buttons.LoginWithInnohassle']}
+
+
+ >
+ )
+ }
+
+ await ctx.send(content).to(ctx.chat.id)
+ })
+})
diff --git a/backend/src/bot/handlers/root.ts b/backend/src/bot/handlers/root.tsx
similarity index 53%
rename from backend/src/bot/handlers/root.ts
rename to backend/src/bot/handlers/root.tsx
index e912cea..37bd668 100644
--- a/backend/src/bot/handlers/root.ts
+++ b/backend/src/bot/handlers/root.tsx
@@ -1,27 +1,24 @@
-import { InlineKeyboard } from 'grammy'
import commands from './commands'
import views from './views'
import { handler } from '.'
import { TelegramNotLinkedToInnohassleAccountError } from '~/domain/errors'
+import { InnohassleLoginButton } from '~/bot/login-button'
export default handler((composer) => {
composer = composer.errorBoundary(async (err) => {
if (err.error instanceof TelegramNotLinkedToInnohassleAccountError) {
const ctx = err.ctx
- const loginUrl = new URL(ctx.config.innohassle.telegramLoginUrl)
- loginUrl.searchParams.set('bot', `${ctx.me.username}?start=_`)
- await ctx.reply(ctx.t['InNoHassle.LinkAccountsRequest.Message'], {
- reply_markup: new InlineKeyboard([[
- {
- text: ctx.t['InNoHassle.LinkAccountsRequest.Button'],
- login_url: {
- url: loginUrl.toString(),
- bot_username: ctx.config.innohassle.telegramBotUsername,
- forward_text: ctx.t['InNoHassle.LinkAccountsRequest.ForwardText'],
- },
- },
- ]]),
- })
+ await ctx
+ .send(
+
+ {ctx.t['Buttons.LoginWithInnohassle']}
+ ,
+ )
+ .to(ctx.chat!.id)
} else {
throw err.error
}
diff --git a/backend/src/bot/login-button.tsx b/backend/src/bot/login-button.tsx
new file mode 100644
index 0000000..203b9c7
--- /dev/null
+++ b/backend/src/bot/login-button.tsx
@@ -0,0 +1,24 @@
+export function InnohassleLoginButton({
+ loginUrl: loginUrlStr,
+ loginBotUsername,
+ returnBotUsername,
+ children,
+}: {
+ loginUrl: string
+ loginBotUsername: string
+ returnBotUsername: string
+ children: string
+}) {
+ const loginUrl = new URL(loginUrlStr)
+ loginUrl.searchParams.set('bot', `${returnBotUsername}?start=_`)
+ return (
+
+ )
+}
diff --git a/backend/src/domain/index.ts b/backend/src/domain/index.ts
index 4779577..e9a72cd 100644
--- a/backend/src/domain/index.ts
+++ b/backend/src/domain/index.ts
@@ -142,6 +142,10 @@ export class Domain {
})
}
+ public async isUserAuthorized(telegramId: number): Promise {
+ return !!(await this.getUserSportData(telegramId))
+ }
+
private async requestSport(
telegramId: number,
method: M,
diff --git a/backend/src/translations/_en.tsx b/backend/src/translations/_en.tsx
index 4f737d4..fb58135 100644
--- a/backend/src/translations/_en.tsx
+++ b/backend/src/translations/_en.tsx
@@ -7,13 +7,26 @@ import { tgxFromHtml } from '~/utils/tgx-from-html'
function dateLong(date: Date): string {
const day = date.toLocaleDateString('en-US', { weekday: 'long', timeZone: TIMEZONE })
const month = date.toLocaleDateString('en-US', { month: 'long', timeZone: TIMEZONE })
- const dayOfMonth = date.getDate()
- const year = date.getFullYear()
+ const dayOfMonth = date.toLocaleDateString('en-US', { day: 'numeric', timeZone: TIMEZONE })
+ const year = date.toLocaleDateString('en-US', { year: 'numeric', timeZone: TIMEZONE })
return `${day}, ${month} ${dayOfMonth}, ${year}`
}
+function dateAndTimeShort(
+ startsAt: Date,
+ endsAt: Date,
+): string {
+ const weekDayShort = startsAt.toLocaleDateString('en-US', { weekday: 'short', timeZone: TIMEZONE })
+ const monthShort = startsAt.toLocaleDateString('en-US', { month: 'short', timeZone: TIMEZONE })
+ const dayOfMonth = startsAt.toLocaleDateString('en-US', { day: 'numeric', timeZone: TIMEZONE })
+
+ return `${weekDayShort} ${monthShort} ${dayOfMonth}, ${clockTime(startsAt, TIMEZONE)}—${clockTime(endsAt, TIMEZONE)}`
+}
+
export default {
+ 'WelcomeMessage.Unauthorized': 'Welcome to IU Sport Bot.\n\nPlease, login:',
+
'Weekday.TwoLetters': (weekday: Weekday) => {
switch (weekday) {
case 'mon': return 'Mo'
@@ -27,20 +40,21 @@ export default {
},
'Buttons.Back': '← Back',
+ 'Buttons.LoginWithInnohassle': 'Login with InNoHassle',
'HowGoodAmI.Thinking': 'Hmm... Let me think 🤔',
'HowGoodAmI.Answer': (percent: number) => `You're better than ${percent}% of students!`,
'HowGoodAmI.Failed': 'I don\'t know 🤷♂️',
- 'Alert.CheckInSuccessful': (training: TrainingDetailed) => [
- '✅ Check-in successful',
+ 'Alert.CheckInSuccessful': ({ title, startsAt, endsAt }: TrainingDetailed) => [
+ '✅ Check-in successful ✅',
'',
- `${training.title} at ${training.startsAt}`,
+ `${title}\n${dateAndTimeShort(startsAt, endsAt)}`,
].join('\n'),
- 'Alert.CheckInCancelled': (training: TrainingDetailed) => [
- '❌ Check-in cancelled',
+ 'Alert.CheckInCancelled': ({ title, startsAt, endsAt }: TrainingDetailed) => [
+ '❌ Check-in cancelled ❌',
'',
- `${training.title} at ${training.startsAt}`,
+ `${title}\n${dateAndTimeShort(startsAt, endsAt)}`,
].join('\n'),
'Alert.CheckInUnavailable': 'You cannot check-in for this training.',
'Alert.AlreadyCheckedIn': 'You are already checked in for this training.',
@@ -103,8 +117,4 @@ export default {
),
'Views.Training.Buttons.CheckIn': 'Check-in',
'Views.Training.Buttons.CancelCheckIn': 'Cancel check-in',
-
- 'InNoHassle.LinkAccountsRequest.Message': 'To use this Telegram bot, please login to InNoHassle with your Telegram account. When you\'re done, send /start to continue!',
- 'InNoHassle.LinkAccountsRequest.Button': 'Login with InNoHassle',
- 'InNoHassle.LinkAccountsRequest.ForwardText': 'Link your Telegram to InNoHassle.',
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 6bd8175..2eb7f2f 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -48,14 +48,14 @@ importers:
specifier: 5.11.0
version: 5.11.0(prisma@5.11.0)
'@telegum/grammy-buttons':
- specifier: ^0.4.0
- version: 0.4.0(@telegum/tgx@0.1.1)(grammy@1.22.4)
+ specifier: ^0.5.1
+ version: 0.5.1(@telegum/tgx@0.2.1)(grammy@1.22.4)
'@telegum/grammy-messages':
- specifier: ^0.4.0
- version: 0.4.0(@telegum/tgx@0.1.1)(grammy@1.22.4)
+ specifier: ^0.5.1
+ version: 0.5.1(@telegum/tgx@0.2.1)(grammy@1.22.4)
'@telegum/tgx':
- specifier: ^0.1.1
- version: 0.1.1
+ specifier: ^0.2.1
+ version: 0.2.1
axios:
specifier: ^1.6.8
version: 1.6.8
@@ -1343,28 +1343,28 @@ packages:
- typescript
dev: true
- /@telegum/grammy-buttons@0.4.0(@telegum/tgx@0.1.1)(grammy@1.22.4):
- resolution: {integrity: sha512-zQUjwZ5wkxiRC1N22bfhR3jf0v6res758Gw+FMiX21jXszAFUd0lHxXHoFklaxvkzKGCdLsnZih9/kn82jO2DA==}
+ /@telegum/grammy-buttons@0.5.1(@telegum/tgx@0.2.1)(grammy@1.22.4):
+ resolution: {integrity: sha512-EHDyv37fKc4A43kJZuR2KSv0RFrC1sEPxHx5jC83Kqaq2Fe9jyy7W2D0jJG60FysMsjdK/96tzRussUBLnrecQ==}
peerDependencies:
- '@telegum/tgx': ^0.1.0
+ '@telegum/tgx': ^0.2.1
grammy: ^1.22.4
dependencies:
- '@telegum/tgx': 0.1.1
+ '@telegum/tgx': 0.2.1
grammy: 1.22.4
dev: false
- /@telegum/grammy-messages@0.4.0(@telegum/tgx@0.1.1)(grammy@1.22.4):
- resolution: {integrity: sha512-XUA77yrstJlmcxWiKDZngdRhBFvtWx2ZJCrF1qtNmKVpdf3pYZ21dqqgs5pRNH/ey3Efv+StXEkogob18rHylA==}
+ /@telegum/grammy-messages@0.5.1(@telegum/tgx@0.2.1)(grammy@1.22.4):
+ resolution: {integrity: sha512-cmCjiACeR5/FadiVtEYwMaJveG/W9VDiXCuHJs6uKCFIFWyf9xRwbn1MlE9tY2Od4z8p+A28pRMvNg3XS/FsUw==}
peerDependencies:
- '@telegum/tgx': ^0.1.0
+ '@telegum/tgx': ^0.2.1
grammy: ^1.22.4
dependencies:
- '@telegum/tgx': 0.1.1
+ '@telegum/tgx': 0.2.1
grammy: 1.22.4
dev: false
- /@telegum/tgx@0.1.1:
- resolution: {integrity: sha512-4MKeCaop+Bi7sLefbc1TWxkrFoyINmyUwZjcAVUfmVyRsavt2sV5rXJ4hlnVDVjkUNNjECTNUWp/BhVKn0aJdg==}
+ /@telegum/tgx@0.2.1:
+ resolution: {integrity: sha512-17mhEkEwwH2zIiXRwstfd0BgHOYCvwmGzqXAtrYtn5cVbFc+hzMAyDOzudtGO9SgNsJvr4rtWyuLV4Yfubh0RA==}
dev: false
/@tufjs/canonical-json@2.0.0: