select predefined messages
Deploy db-migrations / db-migrations (push) Successful in 49s
Deploy service message-delivery-method-web-push / service message-delivery-method-web-push (push) Successful in 1m9s
Deploy app frontend / app frontend (push) Successful in 1m53s
Deploy service tg-bot / service tg-bot (push) Successful in 1m36s

This commit is contained in:
2024-12-17 02:41:14 +02:00
parent c1edb1937f
commit 0e37ebfe5f
15 changed files with 630 additions and 70 deletions
@@ -40,7 +40,7 @@
<script setup lang="ts">
import { nextTick, useTemplateRef, watch } from 'vue'
import { type QRCodeDocument } from '@hereconnect/types'
import { defaultMessages } from '@/constants/defaultMessages'
import { defaultMessages } from '@hereconnect/constants'
// Пропсы
const props = defineProps<{ placement: null | QRCodeDocument['placement'] }>()
+20
View File
@@ -0,0 +1,20 @@
// Предустановленные сообщения по типу размещения
export const defaultMessages = {
car: [
'Ваш автомобиль мешает',
'Выключите свет',
'Спустило колесо',
'Подойдите к вашей машине',
'Эвакуатор',
],
pet: ['Нашёлся ваш питомец'],
door: ['Я рядом с дверью'],
store: ['Подойдите к кассе', 'Вопрос по товару', 'Помощь с покупкой'],
restaurant_table: [
'Позовите официанта',
'Принести меню',
'Принести счёт',
'Пожаловаться на обслуживание',
],
other: [],
} as const
+1
View File
@@ -1,2 +1,3 @@
export { qrCodePlacements, type QrCodePlacement } from "./qrCodePlacements";
export { EPOCH } from "./EPOCH";
export { defaultMessages } from "./defaultMessages";
+213 -16
View File
@@ -106,7 +106,7 @@ importers:
version: 5.2.1(vite@5.4.11(@types/node@20.17.9)(sass-embedded@1.82.0))(vue@3.5.13(typescript@5.7.2))
'@vitest/eslint-plugin':
specifier: 1.1.7
version: 1.1.7(@typescript-eslint/utils@8.18.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.7.2))(eslint@9.15.0(jiti@1.21.6))(typescript@5.7.2)(vitest@2.1.6(@types/node@20.17.9)(jsdom@25.0.1(canvas@2.11.2))(sass-embedded@1.82.0))
version: 1.1.7(@typescript-eslint/utils@8.18.1(eslint@9.15.0(jiti@1.21.6))(typescript@5.7.2))(eslint@9.15.0(jiti@1.21.6))(typescript@5.7.2)(vitest@2.1.6(@types/node@20.17.9)(jsdom@25.0.1(canvas@2.11.2))(sass-embedded@1.82.0))
'@vue/eslint-config-prettier':
specifier: ^10.1.0
version: 10.1.0(eslint@9.15.0(jiti@1.21.6))(prettier@3.4.1)
@@ -399,8 +399,8 @@ importers:
specifier: ^1.1.14
version: 1.1.14
'@types/node':
specifier: ^22.10.1
version: 22.10.1
specifier: ^22.10.2
version: 22.10.2
'@types/pouchdb-adapter-http':
specifier: ^6.1.6
version: 6.1.6
@@ -414,20 +414,20 @@ importers:
specifier: ^2.2.9
version: 2.2.9
'@typescript-eslint/eslint-plugin':
specifier: ^8.18.0
version: 8.18.0(@typescript-eslint/parser@8.18.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2))(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2)
specifier: ^8.18.1
version: 8.18.1(@typescript-eslint/parser@8.18.1(eslint@9.17.0(jiti@1.21.6))(typescript@5.7.2))(eslint@9.17.0(jiti@1.21.6))(typescript@5.7.2)
'@typescript-eslint/parser':
specifier: ^8.18.0
version: 8.18.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2)
specifier: ^8.18.1
version: 8.18.1(eslint@9.17.0(jiti@1.21.6))(typescript@5.7.2)
eslint:
specifier: ^9.16.0
version: 9.16.0(jiti@1.21.6)
specifier: ^9.17.0
version: 9.17.0(jiti@1.21.6)
tsx:
specifier: ^4.19.2
version: 4.19.2
type-fest:
specifier: ^4.30.1
version: 4.30.1
specifier: ^4.30.2
version: 4.30.2
typescript:
specifier: ~5.7.2
version: 5.7.2
@@ -905,6 +905,10 @@ packages:
resolution: {integrity: sha512-tw2HxzQkrbeuvyj1tG2Yqq+0H9wGoI2IMk4EOsQeX+vmd75FtJAzf+gTA69WF+baUKRYQ3x2kbLE08js5OsTVg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@eslint/js@9.17.0':
resolution: {integrity: sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@eslint/object-schema@2.1.4':
resolution: {integrity: sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -1412,6 +1416,14 @@ packages:
eslint: ^8.57.0 || ^9.0.0
typescript: '>=4.8.4 <5.8.0'
'@typescript-eslint/eslint-plugin@8.18.1':
resolution: {integrity: sha512-Ncvsq5CT3Gvh+uJG0Lwlho6suwDfUXH0HztslDf5I+F2wAFAZMRwYLEorumpKLzmO2suAXZ/td1tBg4NZIi9CQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
'@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0
eslint: ^8.57.0 || ^9.0.0
typescript: '>=4.8.4 <5.8.0'
'@typescript-eslint/parser@8.16.0':
resolution: {integrity: sha512-D7DbgGFtsqIPIFMPJwCad9Gfi/hC0PWErRRHFnaCWoEDYi5tQUDiJCTmGUbBiLzjqAck4KcXt9Ayj0CNlIrF+w==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -1429,6 +1441,13 @@ packages:
eslint: ^8.57.0 || ^9.0.0
typescript: '>=4.8.4 <5.8.0'
'@typescript-eslint/parser@8.18.1':
resolution: {integrity: sha512-rBnTWHCdbYM2lh7hjyXqxk70wvon3p2FyaniZuey5TrcGBpfhVp0OxOa6gxr9Q9YhZFKyfbEnxc24ZnVbbUkCA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: '>=4.8.4 <5.8.0'
'@typescript-eslint/scope-manager@8.16.0':
resolution: {integrity: sha512-mwsZWubQvBki2t5565uxF0EYvG+FwdFb8bMtDuGQLdCCnGPrDEDvm1gtfynuKlnpzeBRqdFCkMf9jg1fnAK8sg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -1437,6 +1456,10 @@ packages:
resolution: {integrity: sha512-PNGcHop0jkK2WVYGotk/hxj+UFLhXtGPiGtiaWgVBVP1jhMoMCHlTyJA+hEj4rszoSdLTK3fN4oOatrL0Cp+Xw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@typescript-eslint/scope-manager@8.18.1':
resolution: {integrity: sha512-HxfHo2b090M5s2+/9Z3gkBhI6xBH8OJCFjH9MhQ+nnoZqxU3wNxkLT+VWXWSFWc3UF3Z+CfPAyqdCTdoXtDPCQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@typescript-eslint/type-utils@8.16.0':
resolution: {integrity: sha512-IqZHGG+g1XCWX9NyqnI/0CX5LL8/18awQqmkZSl2ynn8F76j579dByc0jhfVSnSnhf7zv76mKBQv9HQFKvDCgg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -1454,6 +1477,13 @@ packages:
eslint: ^8.57.0 || ^9.0.0
typescript: '>=4.8.4 <5.8.0'
'@typescript-eslint/type-utils@8.18.1':
resolution: {integrity: sha512-jAhTdK/Qx2NJPNOTxXpMwlOiSymtR2j283TtPqXkKBdH8OAMmhiUfP0kJjc/qSE51Xrq02Gj9NY7MwK+UxVwHQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: '>=4.8.4 <5.8.0'
'@typescript-eslint/types@8.16.0':
resolution: {integrity: sha512-NzrHj6thBAOSE4d9bsuRNMvk+BvaQvmY4dDglgkgGC0EW/tB3Kelnp3tAKH87GEwzoxgeQn9fNGRyFJM/xd+GQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -1462,6 +1492,10 @@ packages:
resolution: {integrity: sha512-FNYxgyTCAnFwTrzpBGq+zrnoTO4x0c1CKYY5MuUTzpScqmY5fmsh2o3+57lqdI3NZucBDCzDgdEbIaNfAjAHQA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@typescript-eslint/types@8.18.1':
resolution: {integrity: sha512-7uoAUsCj66qdNQNpH2G8MyTFlgerum8ubf21s3TSM3XmKXuIn+H2Sifh/ES2nPOPiYSRJWAk0fDkW0APBWcpfw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@typescript-eslint/typescript-estree@8.16.0':
resolution: {integrity: sha512-E2+9IzzXMc1iaBy9zmo+UYvluE3TW7bCGWSF41hVWUE01o8nzr1rvOQYSxelxr6StUvRcTMe633eY8mXASMaNw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -1477,6 +1511,12 @@ packages:
peerDependencies:
typescript: '>=4.8.4 <5.8.0'
'@typescript-eslint/typescript-estree@8.18.1':
resolution: {integrity: sha512-z8U21WI5txzl2XYOW7i9hJhxoKKNG1kcU4RzyNvKrdZDmbjkmLBo8bgeiOJmA06kizLI76/CCBAAGlTlEeUfyg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '>=4.8.4 <5.8.0'
'@typescript-eslint/utils@8.16.0':
resolution: {integrity: sha512-C1zRy/mOL8Pj157GiX4kaw7iyRLKfJXBR3L82hk5kS/GyHcOFmy4YUq/zfZti72I9wnuQtA/+xzft4wCC8PJdA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -1494,6 +1534,13 @@ packages:
eslint: ^8.57.0 || ^9.0.0
typescript: '>=4.8.4 <5.8.0'
'@typescript-eslint/utils@8.18.1':
resolution: {integrity: sha512-8vikiIj2ebrC4WRdcAdDcmnu9Q/MXXwg+STf40BVfT8exDqBCUPdypvzcUPxEqRGKg9ALagZ0UWcYCtn+4W2iQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: '>=4.8.4 <5.8.0'
'@typescript-eslint/visitor-keys@8.16.0':
resolution: {integrity: sha512-pq19gbaMOmFE3CbL0ZB8J8BFCo2ckfHBfaIsaOZgBIF4EoISJIdLX5xRhd0FGB0LlHReNRuzoJoMGpTjq8F2CQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -1502,6 +1549,10 @@ packages:
resolution: {integrity: sha512-pCh/qEA8Lb1wVIqNvBke8UaRjJ6wrAWkJO5yyIbs8Yx6TNGYyfNjOo61tLv+WwLvoLPp4BQ8B7AHKijl8NGUfw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@typescript-eslint/visitor-keys@8.18.1':
resolution: {integrity: sha512-Vj0WLm5/ZsD013YeUKn+K0y8p1M0jPpxOkKdbD1wB0ns53a5piVY02zjf072TblEweAbcYiFiPoSMF3kp+VhhQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@unhead/dom@1.11.13':
resolution: {integrity: sha512-8Bpo3e50i49/z0TMiskQk3OqUVJpWOO0cnEEydJeFnjsPczDH76H3mWLvB11cv1B/rjLdBiPgui7yetFta5LCw==}
@@ -2169,6 +2220,16 @@ packages:
jiti:
optional: true
eslint@9.17.0:
resolution: {integrity: sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
hasBin: true
peerDependencies:
jiti: '*'
peerDependenciesMeta:
jiti:
optional: true
espree@10.3.0:
resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -3649,8 +3710,8 @@ packages:
resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==}
engines: {node: '>=10'}
type-fest@4.30.1:
resolution: {integrity: sha512-ojFL7eDMX2NF0xMbDwPZJ8sb7ckqtlAi1GsmgsFXvErT9kFTk1r0DuQKvrCh73M6D4nngeHJmvogF9OluXs7Hw==}
type-fest@4.30.2:
resolution: {integrity: sha512-UJShLPYi1aWqCdq9HycOL/gwsuqda1OISdBO3t8RlXQC4QvtuIz4b5FCfe2dQIWEpmlRExKmcTBfP1r9bhY7ig==}
engines: {node: '>=16'}
typescript-eslint@8.16.0:
@@ -4388,6 +4449,11 @@ snapshots:
eslint: 9.16.0(jiti@1.21.6)
eslint-visitor-keys: 3.4.3
'@eslint-community/eslint-utils@4.4.1(eslint@9.17.0(jiti@1.21.6))':
dependencies:
eslint: 9.17.0(jiti@1.21.6)
eslint-visitor-keys: 3.4.3
'@eslint-community/regexpp@4.12.1': {}
'@eslint/config-array@0.19.0':
@@ -4418,6 +4484,8 @@ snapshots:
'@eslint/js@9.16.0': {}
'@eslint/js@9.17.0': {}
'@eslint/object-schema@2.1.4': {}
'@eslint/plugin-kit@0.2.3':
@@ -4915,6 +4983,23 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/eslint-plugin@8.18.1(@typescript-eslint/parser@8.18.1(eslint@9.17.0(jiti@1.21.6))(typescript@5.7.2))(eslint@9.17.0(jiti@1.21.6))(typescript@5.7.2)':
dependencies:
'@eslint-community/regexpp': 4.12.1
'@typescript-eslint/parser': 8.18.1(eslint@9.17.0(jiti@1.21.6))(typescript@5.7.2)
'@typescript-eslint/scope-manager': 8.18.1
'@typescript-eslint/type-utils': 8.18.1(eslint@9.17.0(jiti@1.21.6))(typescript@5.7.2)
'@typescript-eslint/utils': 8.18.1(eslint@9.17.0(jiti@1.21.6))(typescript@5.7.2)
'@typescript-eslint/visitor-keys': 8.18.1
eslint: 9.17.0(jiti@1.21.6)
graphemer: 1.4.0
ignore: 5.3.2
natural-compare: 1.4.0
ts-api-utils: 1.4.3(typescript@5.7.2)
typescript: 5.7.2
transitivePeerDependencies:
- supports-color
'@typescript-eslint/parser@8.16.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.7.2)':
dependencies:
'@typescript-eslint/scope-manager': 8.16.0
@@ -4940,6 +5025,18 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/parser@8.18.1(eslint@9.17.0(jiti@1.21.6))(typescript@5.7.2)':
dependencies:
'@typescript-eslint/scope-manager': 8.18.1
'@typescript-eslint/types': 8.18.1
'@typescript-eslint/typescript-estree': 8.18.1(typescript@5.7.2)
'@typescript-eslint/visitor-keys': 8.18.1
debug: 4.3.7
eslint: 9.17.0(jiti@1.21.6)
typescript: 5.7.2
transitivePeerDependencies:
- supports-color
'@typescript-eslint/scope-manager@8.16.0':
dependencies:
'@typescript-eslint/types': 8.16.0
@@ -4950,6 +5047,11 @@ snapshots:
'@typescript-eslint/types': 8.18.0
'@typescript-eslint/visitor-keys': 8.18.0
'@typescript-eslint/scope-manager@8.18.1':
dependencies:
'@typescript-eslint/types': 8.18.1
'@typescript-eslint/visitor-keys': 8.18.1
'@typescript-eslint/type-utils@8.16.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.7.2)':
dependencies:
'@typescript-eslint/typescript-estree': 8.16.0(typescript@5.7.2)
@@ -4984,10 +5086,23 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/type-utils@8.18.1(eslint@9.17.0(jiti@1.21.6))(typescript@5.7.2)':
dependencies:
'@typescript-eslint/typescript-estree': 8.18.1(typescript@5.7.2)
'@typescript-eslint/utils': 8.18.1(eslint@9.17.0(jiti@1.21.6))(typescript@5.7.2)
debug: 4.3.7
eslint: 9.17.0(jiti@1.21.6)
ts-api-utils: 1.4.3(typescript@5.7.2)
typescript: 5.7.2
transitivePeerDependencies:
- supports-color
'@typescript-eslint/types@8.16.0': {}
'@typescript-eslint/types@8.18.0': {}
'@typescript-eslint/types@8.18.1': {}
'@typescript-eslint/typescript-estree@8.16.0(typescript@5.7.2)':
dependencies:
'@typescript-eslint/types': 8.16.0
@@ -5017,6 +5132,20 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/typescript-estree@8.18.1(typescript@5.7.2)':
dependencies:
'@typescript-eslint/types': 8.18.1
'@typescript-eslint/visitor-keys': 8.18.1
debug: 4.3.7
fast-glob: 3.3.2
is-glob: 4.0.3
minimatch: 9.0.5
semver: 7.6.3
ts-api-utils: 1.4.3(typescript@5.7.2)
typescript: 5.7.2
transitivePeerDependencies:
- supports-color
'@typescript-eslint/utils@8.16.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.7.2)':
dependencies:
'@eslint-community/eslint-utils': 4.4.1(eslint@9.15.0(jiti@1.21.6))
@@ -5051,6 +5180,28 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/utils@8.18.1(eslint@9.15.0(jiti@1.21.6))(typescript@5.7.2)':
dependencies:
'@eslint-community/eslint-utils': 4.4.1(eslint@9.15.0(jiti@1.21.6))
'@typescript-eslint/scope-manager': 8.18.1
'@typescript-eslint/types': 8.18.1
'@typescript-eslint/typescript-estree': 8.18.1(typescript@5.7.2)
eslint: 9.15.0(jiti@1.21.6)
typescript: 5.7.2
transitivePeerDependencies:
- supports-color
'@typescript-eslint/utils@8.18.1(eslint@9.17.0(jiti@1.21.6))(typescript@5.7.2)':
dependencies:
'@eslint-community/eslint-utils': 4.4.1(eslint@9.17.0(jiti@1.21.6))
'@typescript-eslint/scope-manager': 8.18.1
'@typescript-eslint/types': 8.18.1
'@typescript-eslint/typescript-estree': 8.18.1(typescript@5.7.2)
eslint: 9.17.0(jiti@1.21.6)
typescript: 5.7.2
transitivePeerDependencies:
- supports-color
'@typescript-eslint/visitor-keys@8.16.0':
dependencies:
'@typescript-eslint/types': 8.16.0
@@ -5061,6 +5212,11 @@ snapshots:
'@typescript-eslint/types': 8.18.0
eslint-visitor-keys: 4.2.0
'@typescript-eslint/visitor-keys@8.18.1':
dependencies:
'@typescript-eslint/types': 8.18.1
eslint-visitor-keys: 4.2.0
'@unhead/dom@1.11.13':
dependencies:
'@unhead/schema': 1.11.13
@@ -5089,9 +5245,9 @@ snapshots:
vite: 5.4.11(@types/node@20.17.9)(sass-embedded@1.82.0)
vue: 3.5.13(typescript@5.7.2)
'@vitest/eslint-plugin@1.1.7(@typescript-eslint/utils@8.18.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.7.2))(eslint@9.15.0(jiti@1.21.6))(typescript@5.7.2)(vitest@2.1.6(@types/node@20.17.9)(jsdom@25.0.1(canvas@2.11.2))(sass-embedded@1.82.0))':
'@vitest/eslint-plugin@1.1.7(@typescript-eslint/utils@8.18.1(eslint@9.15.0(jiti@1.21.6))(typescript@5.7.2))(eslint@9.15.0(jiti@1.21.6))(typescript@5.7.2)(vitest@2.1.6(@types/node@20.17.9)(jsdom@25.0.1(canvas@2.11.2))(sass-embedded@1.82.0))':
dependencies:
'@typescript-eslint/utils': 8.18.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.7.2)
'@typescript-eslint/utils': 8.18.1(eslint@9.15.0(jiti@1.21.6))(typescript@5.7.2)
eslint: 9.15.0(jiti@1.21.6)
optionalDependencies:
typescript: 5.7.2
@@ -5878,6 +6034,47 @@ snapshots:
transitivePeerDependencies:
- supports-color
eslint@9.17.0(jiti@1.21.6):
dependencies:
'@eslint-community/eslint-utils': 4.4.1(eslint@9.17.0(jiti@1.21.6))
'@eslint-community/regexpp': 4.12.1
'@eslint/config-array': 0.19.0
'@eslint/core': 0.9.0
'@eslint/eslintrc': 3.2.0
'@eslint/js': 9.17.0
'@eslint/plugin-kit': 0.2.3
'@humanfs/node': 0.16.6
'@humanwhocodes/module-importer': 1.0.1
'@humanwhocodes/retry': 0.4.1
'@types/estree': 1.0.6
'@types/json-schema': 7.0.15
ajv: 6.12.6
chalk: 4.1.2
cross-spawn: 7.0.6
debug: 4.3.7
escape-string-regexp: 4.0.0
eslint-scope: 8.2.0
eslint-visitor-keys: 4.2.0
espree: 10.3.0
esquery: 1.6.0
esutils: 2.0.3
fast-deep-equal: 3.1.3
file-entry-cache: 8.0.0
find-up: 5.0.0
glob-parent: 6.0.2
ignore: 5.3.2
imurmurhash: 0.1.4
is-glob: 4.0.3
json-stable-stringify-without-jsonify: 1.0.1
lodash.merge: 4.6.2
minimatch: 3.1.2
natural-compare: 1.4.0
optionator: 0.9.4
optionalDependencies:
jiti: 1.21.6
transitivePeerDependencies:
- supports-color
espree@10.3.0:
dependencies:
acorn: 8.14.0
@@ -7381,7 +7578,7 @@ snapshots:
type-fest@0.20.2: {}
type-fest@4.30.1: {}
type-fest@4.30.2: {}
typescript-eslint@8.16.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.7.2):
dependencies:
+5 -5
View File
@@ -63,16 +63,16 @@
"@grammyjs/types": "^3.17.0",
"@hereconnect/types": "workspace:^",
"@types/bun": "^1.1.14",
"@types/node": "^22.10.1",
"@types/node": "^22.10.2",
"@types/pouchdb-adapter-http": "^6.1.6",
"@types/pouchdb-core": "^7.0.15",
"@types/pouchdb-find": "^7.3.3",
"@types/pouchdb-upsert": "^2.2.9",
"@typescript-eslint/eslint-plugin": "^8.18.0",
"@typescript-eslint/parser": "^8.18.0",
"eslint": "^9.16.0",
"@typescript-eslint/eslint-plugin": "^8.18.1",
"@typescript-eslint/parser": "^8.18.1",
"eslint": "^9.17.0",
"tsx": "^4.19.2",
"type-fest": "^4.30.1",
"type-fest": "^4.30.2",
"typescript": "~5.7.2"
}
}
+2
View File
@@ -52,6 +52,7 @@ interface CreateQRCodeOptions {
placement: QRCodeDocument["placement"];
actions: QRCodeDocument["actions"];
messageDeliveryMethod?: QRCodeDocument["messageDeliveryMethod"];
predefinedMessages?: QRCodeDocument["predefinedMessages"];
browser_uuid?: string;
telegram_id?: string;
}
@@ -74,6 +75,7 @@ export async function createQRCode(options: CreateQRCodeOptions) {
name: options.name,
placement: options.placement,
actions: options.actions,
predefinedMessages: options.predefinedMessages,
uri,
url,
messageDeliveryMethod: options.messageDeliveryMethod || "telegram",
@@ -73,6 +73,7 @@ export async function qrCodeActions(ctx: MyContext) {
break;
default:
console.warn(`Игнорируем callback_data в qrCodeActions: ${data}`);
await ctx.answerCallbackQuery({ text: "Неизвестное действие." });
}
}
+3 -3
View File
@@ -5,10 +5,10 @@ import { qrCodeActions } from "./actions/qrCodeActions";
/**
* Обработка callback_query для управления QR-кодами
*/
export async function callbackQueryHandler(ctx: MyContext) {
export async function callbackQueryHandler(ctx: MyContext, next: () => void) {
const data = ctx.callbackQuery?.data;
if (!data) return; // Игнорируем пустые callback queries
if (!data) return next(); // Игнорируем пустые callback queries
if (
data.startsWith("manage:info:") ||
@@ -22,6 +22,6 @@ export async function callbackQueryHandler(ctx: MyContext) {
await manageMenuActions(ctx);
} else {
// Ответ на неизвестные callback queries
await ctx.answerCallbackQuery({ text: "Неизвестное действие." });
next();
}
}
+3 -4
View File
@@ -2,6 +2,8 @@ import { MyContext } from "../../types/myContext";
import { ensureUserExists } from "../../api/users";
export async function startCommand(ctx: MyContext) {
ctx.session = {};
const userId = ctx.from?.id;
if (userId === undefined) {
@@ -15,9 +17,6 @@ export async function startCommand(ctx: MyContext) {
await ensureUserExists(userId);
await ctx.reply(
"Привет! Я бот сервиса «НаСвязи». Я помогу вам создавать и настраивать QR-коды.",
);
await ctx.reply(
"Используйте команду /new чтобы создать новый QR-код или /manage для управления уже созданными кодами.",
"Привет! Я бот сервиса «НаСвязи». Я помогу вам создавать и настраивать QR-коды.\n\nИспользуйте команду /new чтобы создать новый QR-код или /manage для управления уже созданными кодами.",
);
}
@@ -10,6 +10,8 @@ import {
import { createQRCode } from "../../api/qrCodes";
import { generateQRCodeImage } from "../../services/qrGenerator";
import { selectMultipleActions } from "./selectMultipleActions";
import { defaultMessages } from "@hereconnect/constants";
import { selectPredefinedMessages } from "./selectPredefinedMessages";
export async function newQRCodeConversation(
conversation: Conversation<MyContext>,
@@ -30,7 +32,7 @@ export async function newQRCodeConversation(
return;
}
// 1. Выбор типа объекта (placement)
// Выбор типа объекта (placement)
await ctx.reply("Выберите объект, для которого будет создан QR-код:", {
reply_markup: {
inline_keyboard: buildPlacementKeyboard(qrCodePlacements),
@@ -52,7 +54,24 @@ export async function newQRCodeConversation(
// Подтверждаем выбор
await placementChoice.answerCallbackQuery({ text: "Объект выбран!" });
// 2. Ввод названия QR-кода
// Выбор действий
const availableActions = [
{ action: "sendMessage", label: "Отправить сообщение" },
{ action: "viewContactInfo", label: "Посмотреть контактные данные" },
] as const;
const chosenActions = await selectMultipleActions(
conversation,
ctx,
availableActions,
);
if (chosenActions.length === 0) {
await ctx.reply("Вы не выбрали ни одного действия. Попробуйте снова /new.");
return;
}
// Ввод названия QR-кода
let exampleName: string;
switch (typedPlacement) {
@@ -76,6 +95,15 @@ export async function newQRCodeConversation(
break;
}
let predefinedMessages: QRCodeDocument["predefinedMessages"] = undefined;
if (chosenActions.includes("sendMessage")) {
predefinedMessages = await selectPredefinedMessages(
conversation,
ctx,
typedPlacement,
);
}
// Отправляем сообщение пользователю с примером
await ctx.reply(
`Введите название для вашего QR-кода (например: «${exampleName}»):`,
@@ -83,42 +111,23 @@ export async function newQRCodeConversation(
const nameMsg = await conversation.waitFor("message:text");
const name = nameMsg.message.text.trim();
if (name.length < 2) {
await ctx.reply(
"Название должно содержать минимум 2 символа. Попробуйте снова /new.",
);
return;
}
// 3. Выбор действий
const availableActions = [
{ action: "sendMessage", label: "📩 Отправить сообщение" },
{ action: "viewContactInfo", label: "📞 Посмотреть контактные данные" },
] as const;
// Уведомление о создании QR-кода
await ctx.reply("Создаём QR-код…");
const chosenActions = await selectMultipleActions(
conversation,
ctx,
availableActions,
);
if (chosenActions.length === 0) {
await ctx.reply("Вы не выбрали ни одного действия. Попробуйте снова /new.");
return;
}
// 4. Создание документа в БД
// Создание документа в БД
const { url } = await createQRCode({
userUuid,
name,
placement: typedPlacement,
actions: chosenActions,
predefinedMessages,
});
// 5. Генерация QR-кода
// Генерация QR-кода
const pngBuffer = await generateQRCodeImage(url, { format: "png" });
// 6. Отправляем пользователю QR-код
// Отправляем пользователю QR-код
await ctx.reply("Ваш QR-код готов! Вот он:");
await ctx.replyWithPhoto(
@@ -31,7 +31,7 @@ export async function selectMultipleActions<T extends readonly Action[]>(
const updateKeyboard = () => {
return availableActions.map(({ action, label }) => [
{
text: `${selectedActions.has(action) ? "️ " : ""}${label}`,
text: `${selectedActions.has(action) ? "️ " : ""}${label}`,
callback_data: `action:${action}`,
},
]);
@@ -76,10 +76,9 @@ export async function selectMultipleActions<T extends readonly Action[]>(
const action = choice.replace("action:", "");
if (action === "done") {
await actionChoice.answerCallbackQuery({ text: "Выбор завершён!" });
// Уведомление о создании QR-кода
await ctx.reply("Создаём QR-код…");
await actionChoice.answerCallbackQuery({
text: "Выбор действий завершён!",
});
break;
}
@@ -0,0 +1,325 @@
import { MyContext } from "../../types/myContext";
import { Conversation } from "@grammyjs/conversations";
import { defaultMessages } from "@hereconnect/constants";
import { InlineKeyboardMarkup } from "@grammyjs/types";
// Тип разговора
type MyConversation = Conversation<MyContext>;
export async function selectPredefinedMessages(
conversation: MyConversation,
ctx: MyContext,
placement: keyof typeof defaultMessages,
): Promise<string[]> {
const messages: string[] = [...defaultMessages[placement]];
let mode: "default" | "edit" | "sort" = "default";
let selectedIndex: number | null = null;
let sortOrder: number[] = [];
const updateKeyboard = (): InlineKeyboardMarkup => ({
inline_keyboard: messages
.map((message, index) => {
const prefix =
mode === "edit" && selectedIndex === index
? "📝 "
: mode === "sort" && sortOrder.includes(index)
? `${sortOrder.indexOf(index) + 1}. `
: "";
return [
{
text: `${prefix}${message}`,
callback_data: `select:${index}`,
},
];
})
.concat(
mode === "sort"
? [
[{ text: "✅ Завершить сортировку", callback_data: "done_sort" }],
[
{
text: "❌ Отменить сортировку",
callback_data: "cancel_sort",
},
],
]
: mode === "edit" && selectedIndex !== null
? [
[
{
text: `❌ Удалить «${messages[selectedIndex]}»`,
callback_data: `delete:${selectedIndex}`,
},
],
[
{
text: "❌ Отменить редактирование",
callback_data: "cancel_edit",
},
],
]
: messages.length > 0
? [
[{ text: "🔀 Поменять порядок", callback_data: "sort" }],
[{ text: "✅ Готово", callback_data: "done" }],
]
: [[{ text: "✅ Готово", callback_data: "done" }]],
),
});
// Функция для отправки или редактирования сообщения
const sendOrEditMessage = async (
text: string,
options: {
reply_markup: InlineKeyboardMarkup;
parse_mode?: "Markdown" | "MarkdownV2" | "HTML";
},
forceNew = false,
) => {
try {
if (forceNew && ctx.session.messageId) {
try {
await ctx.api.deleteMessage(ctx.chat!.id, ctx.session.messageId);
ctx.session.messageId = undefined;
console.log("deleteMessage", ctx.session.messageId);
} catch (deleteErr) {
console.warn(
"Ошибка удаления старого сообщения:",
ctx.session.messageId,
deleteErr,
);
ctx.session.messageId = undefined;
}
}
if (ctx.session.messageId) {
await ctx.api.editMessageText(
ctx.chat!.id,
ctx.session.messageId,
text,
options,
);
} else {
const replyMessage = await ctx.reply(text, options);
console.log("replyMessage.message_id", replyMessage.message_id);
ctx.session.messageId = replyMessage.message_id;
}
} catch (err) {
console.error(
"Ошибка обновления сообщения, создаем новое:",
ctx.session.messageId,
err,
);
if (ctx.session.messageId) {
try {
await ctx.api.deleteMessage(ctx.chat!.id, ctx.session.messageId);
console.log("deleteMessage", ctx.session.messageId);
} catch (deleteErr) {
console.warn(
"Ошибка удаления старого сообщения:",
ctx.session.messageId,
deleteErr,
);
}
}
const newMessage = await ctx.reply(text, options);
console.log("newMessage.message_id", newMessage.message_id);
ctx.session.messageId = newMessage.message_id;
}
};
// Начальное сообщение
await sendOrEditMessage(
`**Настройка предустановленных сообщений**
Эти сообщения будут предлагаться пользователю при сканировании QR‑кода. Он сможет выбрать одно из них или написать своё.
Напишите сообщение для добавления, нажмите на сообщение для редактирования или выберите "🔀 Поменять порядок" для сортировки.`,
{ parse_mode: "Markdown", reply_markup: updateKeyboard() },
);
while (true) {
const actionChoice = await conversation.waitFor([
"callback_query:data",
"message:text",
]);
const choice = actionChoice.update.callback_query?.data;
if (!choice && !actionChoice.message) {
if (choice) {
// await actionChoice.answerCallbackQuery({
// text: "Произошла ошибка. Попробуйте снова.",
// });
continue;
}
ctx.session.messageId = undefined;
break;
}
const messageText = actionChoice.message?.text.trim();
if (!choice) {
if (messageText) {
if (mode === "edit" && selectedIndex !== null) {
messages[selectedIndex] = messageText;
mode = "default";
selectedIndex = null;
// await ctx.reply(`Сообщение обновлено: ${messageText}`);
await sendOrEditMessage(
"Сообщение обновлено.",
{
parse_mode: "Markdown",
reply_markup: updateKeyboard(),
},
true,
);
} else {
messages.push(messageText);
// await ctx.reply(`Сообщение добавлено: ${messageText}`);
await sendOrEditMessage(
"Сообщение добавлено.",
{
parse_mode: "Markdown",
reply_markup: updateKeyboard(),
},
true,
);
}
} else {
await ctx.reply(`Введите текст сообщения.`);
}
continue;
}
if (choice === "done") {
// await actionChoice.answerCallbackQuery({ text: "Настройка завершена!" });
await ctx.api.deleteMessage(ctx.chat!.id, ctx.session.messageId!);
ctx.session.messageId = undefined;
break;
}
if (choice === "sort") {
mode = "sort";
sortOrder.length = 0;
// await actionChoice.answerCallbackQuery({ text: "Режим сортировки." });
await sendOrEditMessage("Нажмите на сообщения по порядку.", {
parse_mode: "Markdown",
reply_markup: updateKeyboard(),
});
continue;
}
if (mode === "sort" && choice.startsWith("select:")) {
const index = parseInt(choice.replace("select:", ""), 10);
if (sortOrder.includes(index)) {
sortOrder.splice(sortOrder.indexOf(index), 1);
} else {
sortOrder.push(index);
}
const sortedMessages: string[] = [];
sortOrder.forEach((sortIndex, messagesIndex) => {
sortedMessages[messagesIndex] = messages[sortIndex];
});
messages.forEach((message, index) => {
if (!sortOrder.includes(index)) {
sortedMessages.push(message);
}
});
// Обновляем порядок сообщений
for (let i = 0; i < sortedMessages.length; i++) {
messages[i] = sortedMessages[i];
}
for (let i = 0; i < sortOrder.length; i++) {
sortOrder[i] = i;
}
// await actionChoice.answerCallbackQuery({ text: "Сообщение перемещено." });
await sendOrEditMessage(
"Нажмите на сообщения по порядку, начиная с первого.",
{ parse_mode: "Markdown", reply_markup: updateKeyboard() },
);
continue;
}
if (choice === "cancel_sort") {
mode = "default";
sortOrder.length = 0;
// await actionChoice.answerCallbackQuery({ text: "Сортировка отменена." });
await sendOrEditMessage(
"Сортировка отменена. Возврат к стандартному режиму.",
{ parse_mode: "Markdown", reply_markup: updateKeyboard() },
);
continue;
}
if (choice === "done_sort") {
mode = "default";
sortOrder.length = 0;
// await actionChoice.answerCallbackQuery({ text: "Сортировка завершена." });
await sendOrEditMessage("Сортировка завершена.", {
parse_mode: "Markdown",
reply_markup: updateKeyboard(),
});
continue;
}
if (choice.startsWith("select:")) {
const index = parseInt(choice.replace("select:", ""), 10);
if (selectedIndex === index) {
// await actionChoice.answerCallbackQuery({
// text: "Редактирование отменено.",
// });
mode = "default";
selectedIndex = null;
await sendOrEditMessage("Редактирование отменено.", {
parse_mode: "Markdown",
reply_markup: updateKeyboard(),
});
continue;
}
mode = "edit";
selectedIndex = index;
// await actionChoice.answerCallbackQuery({
// text: `Редактирование сообщения: "${messages[index]}"`,
// });
await sendOrEditMessage(
`Редактирование сообщения: "${messages[index]}"`,
{ parse_mode: "Markdown", reply_markup: updateKeyboard() },
);
continue;
}
if (choice === "cancel_edit") {
mode = "default";
selectedIndex = null;
// await actionChoice.answerCallbackQuery({
// text: "Редактирование отменено.",
// });
await sendOrEditMessage("Редактирование отменено.", {
parse_mode: "Markdown",
reply_markup: updateKeyboard(),
});
continue;
}
if (choice.startsWith("delete:")) {
const index = parseInt(choice.replace("delete:", ""), 10);
const deletedMessage = messages.splice(index, 1)[0];
mode = "default";
selectedIndex = null;
// await actionChoice.answerCallbackQuery({
// text: `Сообщение "${deletedMessage}" удалено.`,
// });
await sendOrEditMessage(`Сообщение "${deletedMessage}" удалено.`, {
parse_mode: "Markdown",
reply_markup: updateKeyboard(),
});
}
}
return messages;
}
+11 -5
View File
@@ -45,19 +45,25 @@ export const createBot = ({
// Подключаем conversations для многошаговых сценариев
bot.use(conversations());
bot.use(createConversation(newQRCodeConversation));
// Регистрация команд
bot.command("start", startCommand);
bot.command("help", helpCommand);
bot.command("new", newCommand);
bot.command("manage", manageCommand);
// Обработка callback_query
bot.on("callback_query:data", callbackQueryHandler);
bot.command("cancel", async (ctx) => {
await ctx.conversation.exit();
await ctx.reply("Leaving.");
});
// Middleware для обработки ответов на сообщения
bot.use(forwardQrCodeMessageMiddleware);
// /new
bot.use(createConversation(newQRCodeConversation));
bot.command("new", newCommand);
// Обработка callback_query
bot.on("callback_query:data", callbackQueryHandler);
return bot;
};
@@ -32,10 +32,10 @@ export const forwardQrCodeMessageMiddleware = async (
ctx.msg.text.match(qrCodeMessageRegexp);
if (!m?.groups) {
console.log("m.groups is empty", m, m?.groups);
await ctx.reply(
`не возможно обработать это сообщение.\nВыберете в телеграм ответить у любого сообщения QR-кода чтобы я его мог переслать в диалог.`,
);
// console.log("m.groups is empty", m, m?.groups);
// await ctx.reply(
// `не возможно обработать это сообщение.\nВыберете в телеграм ответить у любого сообщения QR-кода чтобы я его мог переслать в диалог.`,
// );
return next();
}
+1
View File
@@ -7,6 +7,7 @@ import type { UserDocument } from "@hereconnect/types";
export interface SessionData {
step?: number;
messageId?: number;
}
// Базовый контекст без conversation