mirror of
https://github.com/hcengineering/platform.git
synced 2024-11-21 16:09:12 +03:00
Fix telegram bot (#6323)
This commit is contained in:
parent
e591e073a4
commit
1cb4c86a19
@ -221,7 +221,7 @@ export async function configurePlatform (): Promise<void> {
|
||||
setMetadata(presentation.metadata.FrontVersion, config.VERSION)
|
||||
}
|
||||
setMetadata(telegram.metadata.TelegramURL, config.TELEGRAM_URL ?? 'http://localhost:8086')
|
||||
setMetadata(telegram.metadata.BotUrl, config.TELEGRAM_BOT_URL)
|
||||
setMetadata(telegram.metadata.BotUrl, config.TELEGRAM_BOT_URL ?? 'http://localhost:4020')
|
||||
setMetadata(gmail.metadata.GmailURL, config.GMAIL_URL ?? 'http://localhost:8087')
|
||||
setMetadata(calendar.metadata.CalendarServiceURL, config.CALENDAR_URL ?? 'http://localhost:8095')
|
||||
setMetadata(notification.metadata.PushPublicKey, config.PUSH_PUBLIC_KEY)
|
||||
|
@ -29,6 +29,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/activity": "^0.6.0",
|
||||
"@hcengineering/chunter": "^0.6.20",
|
||||
"@hcengineering/love": "^0.6.0",
|
||||
"@hcengineering/model": "^0.6.11",
|
||||
"@hcengineering/core": "^0.6.32",
|
||||
"@hcengineering/platform": "^0.6.11",
|
||||
|
@ -17,6 +17,8 @@ import { type Builder } from '@hcengineering/model'
|
||||
import notification from '@hcengineering/model-notification'
|
||||
import core from '@hcengineering/model-core'
|
||||
import contact from '@hcengineering/model-contact'
|
||||
import chunter from '@hcengineering/chunter'
|
||||
import love from '@hcengineering/love'
|
||||
|
||||
import telegram from './plugin'
|
||||
|
||||
@ -66,7 +68,17 @@ export function defineNotifications (builder: Builder): void {
|
||||
|
||||
builder.createDoc(notification.class.NotificationProviderDefaults, core.space.Model, {
|
||||
provider: notification.providers.InboxNotificationProvider,
|
||||
ignoredTypes: [notification.ids.CollaboratoAddNotification],
|
||||
ignoredTypes: [],
|
||||
enabledTypes: [telegram.ids.NewMessageNotification]
|
||||
})
|
||||
|
||||
builder.createDoc(notification.class.NotificationProviderDefaults, core.space.Model, {
|
||||
provider: telegram.providers.TelegramNotificationProvider,
|
||||
ignoredTypes: [
|
||||
notification.ids.CollaboratoAddNotification,
|
||||
love.ids.InviteNotification,
|
||||
love.ids.KnockNotification
|
||||
],
|
||||
enabledTypes: [chunter.ids.DMNotification, chunter.ids.ThreadNotification]
|
||||
})
|
||||
}
|
||||
|
@ -34,6 +34,7 @@
|
||||
export let allowFullsize: boolean = false
|
||||
export let hideFooter: boolean = false
|
||||
export let adaptive: 'default' | 'freezeActions' | 'doubleRow' | 'disabled' = 'disabled'
|
||||
export let showCancelButton: boolean = true
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
@ -97,13 +98,15 @@
|
||||
on:click={okAction}
|
||||
disabled={!canSave}
|
||||
/>
|
||||
<ButtonBase
|
||||
type={'type-button'}
|
||||
kind={'secondary'}
|
||||
size={type === 'type-aside' ? 'large' : 'medium'}
|
||||
label={ui.string.Cancel}
|
||||
on:click={onCancel}
|
||||
/>
|
||||
{#if showCancelButton}
|
||||
<ButtonBase
|
||||
type={'type-button'}
|
||||
kind={'secondary'}
|
||||
size={type === 'type-aside' ? 'large' : 'medium'}
|
||||
label={ui.string.Cancel}
|
||||
on:click={onCancel}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
@ -20,7 +20,8 @@
|
||||
NotificationGroup,
|
||||
NotificationType,
|
||||
NotificationTypeSetting,
|
||||
NotificationProviderDefaults
|
||||
NotificationProviderDefaults,
|
||||
NotificationProviderSetting
|
||||
} from '@hcengineering/notification'
|
||||
import { getResource, IntlString } from '@hcengineering/platform'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
@ -132,12 +133,13 @@
|
||||
|
||||
async function getFilteredProviders (
|
||||
providers: NotificationProvider[],
|
||||
types: BaseNotificationType[]
|
||||
types: BaseNotificationType[],
|
||||
providersSettings: NotificationProviderSetting[]
|
||||
): Promise<NotificationProvider[]> {
|
||||
const result: NotificationProvider[] = []
|
||||
|
||||
for (const provider of providers) {
|
||||
const providerSetting = $providersSettings.find((p) => p.attachedTo === provider._id)
|
||||
const providerSetting = providersSettings.find((p) => p.attachedTo === provider._id)
|
||||
|
||||
if (providerSetting !== undefined && !providerSetting.enabled) continue
|
||||
if (providerSetting === undefined && !provider.defaultEnabled) continue
|
||||
@ -164,7 +166,7 @@
|
||||
|
||||
let filteredProviders: NotificationProvider[] = []
|
||||
|
||||
$: void getFilteredProviders(providers, types).then((result) => {
|
||||
$: void getFilteredProviders(providers, types, $providersSettings).then((result) => {
|
||||
filteredProviders = result
|
||||
})
|
||||
|
||||
|
@ -0,0 +1,209 @@
|
||||
<!--
|
||||
// Copyright © 2024 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { CodeForm, Icon, IconCheckmark, Label, Loading, Modal, ModernButton } from '@hcengineering/ui'
|
||||
import presentation from '@hcengineering/presentation'
|
||||
import { getEmbeddedLabel, getMetadata, IntlString } from '@hcengineering/platform'
|
||||
import { concatLink, getCurrentAccount } from '@hcengineering/core'
|
||||
import { onMount } from 'svelte'
|
||||
|
||||
import telegram from '../plugin'
|
||||
import TelegramColor from './icons/TelegramColor.svelte'
|
||||
|
||||
let isTestingConnection = false
|
||||
let isConnectionEstablished = false
|
||||
let connectionError: Error | undefined
|
||||
|
||||
let info: { name: string, username: string, photoUrl: string } | undefined = undefined
|
||||
let isLoading = false
|
||||
|
||||
const url = getMetadata(telegram.metadata.BotUrl) ?? ''
|
||||
|
||||
onMount(() => {
|
||||
void loadBotInfo()
|
||||
})
|
||||
|
||||
async function loadBotInfo (): Promise<void> {
|
||||
if (info !== undefined || isLoading) return
|
||||
isLoading = true
|
||||
try {
|
||||
const link = concatLink(url, '/info')
|
||||
const res = await fetch(link, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: 'Bearer ' + getMetadata(presentation.metadata.Token),
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
info = await res.json()
|
||||
} catch (e) {}
|
||||
isLoading = false
|
||||
}
|
||||
|
||||
async function handleTestConnection (): Promise<void> {
|
||||
isTestingConnection = true
|
||||
isConnectionEstablished = false
|
||||
connectionError = undefined
|
||||
|
||||
try {
|
||||
const link = concatLink(url, '/test')
|
||||
const res = await fetch(link, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: 'Bearer ' + getMetadata(presentation.metadata.Token),
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
isConnectionEstablished = res.ok
|
||||
if (!res.ok) {
|
||||
connectionError = new Error('Connection failed')
|
||||
}
|
||||
} catch (e) {
|
||||
connectionError = e as Error
|
||||
}
|
||||
isTestingConnection = false
|
||||
}
|
||||
|
||||
const codeFields = [
|
||||
{ id: 'code-1', name: 'code-1', optional: false },
|
||||
{ id: 'code-2', name: 'code-2', optional: false },
|
||||
{ id: 'code-3', name: 'code-3', optional: false },
|
||||
{ id: 'code-4', name: 'code-4', optional: false },
|
||||
{ id: 'code-5', name: 'code-5', optional: false },
|
||||
{ id: 'code-6', name: 'code-6', optional: false }
|
||||
]
|
||||
|
||||
let isCodeValid = false
|
||||
let codeError: IntlString | undefined
|
||||
|
||||
async function handleCode (event: CustomEvent<string>): Promise<void> {
|
||||
isCodeValid = false
|
||||
codeError = undefined
|
||||
|
||||
try {
|
||||
const link = concatLink(url, '/auth')
|
||||
const res = await fetch(link, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: 'Bearer ' + getMetadata(presentation.metadata.Token),
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ code: event.detail, account: getCurrentAccount()._id })
|
||||
})
|
||||
isCodeValid = res.ok
|
||||
if (!res.ok) {
|
||||
codeError = res.status === 409 ? telegram.string.AccountAlreadyConnected : telegram.string.InvalidCode
|
||||
}
|
||||
} catch (e) {
|
||||
codeError = telegram.string.SomethingWentWrong
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal
|
||||
label={getEmbeddedLabel('Connect Telegram Bot')}
|
||||
type="type-popup"
|
||||
okLabel={presentation.string.Ok}
|
||||
okAction={() => {}}
|
||||
showCancelButton={false}
|
||||
canSave
|
||||
on:close
|
||||
>
|
||||
<div class="hulyModal-content__titleGroup" style="padding: 0">
|
||||
{#if isLoading}
|
||||
<div class="flex-row-top mt-2 h-32">
|
||||
<Loading size="medium" />
|
||||
</div>
|
||||
{:else if info}
|
||||
<div class="flex-col mt-2">
|
||||
<div class="title overflow-label mb-4">
|
||||
<div class="flex-row-center flex-gap-2">
|
||||
{#if info.photoUrl !== ''}
|
||||
<img class="photo" src={info.photoUrl} alt="" />
|
||||
{:else}
|
||||
<Icon icon={TelegramColor} size="x-large" />
|
||||
{/if}
|
||||
{info.name} (@{info.username})
|
||||
<ModernButton
|
||||
label={telegram.string.TestConnection}
|
||||
size="small"
|
||||
loading={isTestingConnection}
|
||||
on:click={handleTestConnection}
|
||||
/>
|
||||
{#if isConnectionEstablished}
|
||||
<span class="flex-row-center flex-gap-1 label-connected">
|
||||
<Label label={telegram.string.Connected} />
|
||||
<Icon icon={IconCheckmark} size="medium" />
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
{#if connectionError}
|
||||
<span class="label-error mt-2">
|
||||
<Label label={telegram.string.ConnectBotError} />
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex-row-center flex-gap-1 mt-2">
|
||||
<Label label={telegram.string.ConnectBotInfoStart} />
|
||||
<a target="_blank" href={`https://t.me/${info.username}`}>{info.username}</a>
|
||||
<Label label={telegram.string.ConnectBotInfoEnd} />
|
||||
</div>
|
||||
|
||||
<CodeForm fields={codeFields} size="small" on:submit={handleCode} />
|
||||
{#if codeError}
|
||||
<span class="label-error mt-2">
|
||||
<Label label={codeError} />
|
||||
</span>
|
||||
{:else if isCodeValid}
|
||||
<span class="flex-row-center flex-gap-1 mt-2 label-connected">
|
||||
<Label label={telegram.string.Connected} />
|
||||
<Icon icon={IconCheckmark} size="medium" />
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<span class="label-error mt-2">
|
||||
<Label label={getEmbeddedLabel('Unable connect to service. Please try again.')} />
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<style lang="scss">
|
||||
.label-connected {
|
||||
color: var(--global-online-color);
|
||||
}
|
||||
|
||||
.label-error {
|
||||
color: var(--global-error-TextColor);
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--theme-link-color);
|
||||
|
||||
&:hover,
|
||||
&:active,
|
||||
&:visited {
|
||||
color: var(--theme-link-color);
|
||||
}
|
||||
}
|
||||
|
||||
.photo {
|
||||
border-radius: 50%;
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
}
|
||||
</style>
|
@ -14,185 +14,26 @@
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { CodeForm, Icon, IconCheckmark, Label, Loading, ModernButton } from '@hcengineering/ui'
|
||||
import presentation from '@hcengineering/presentation'
|
||||
import { getEmbeddedLabel, getMetadata, IntlString } from '@hcengineering/platform'
|
||||
import { concatLink, getCurrentAccount } from '@hcengineering/core'
|
||||
import { ModernButton, showPopup } from '@hcengineering/ui'
|
||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||
|
||||
import telegram from '../plugin'
|
||||
import TelegramColor from './icons/TelegramColor.svelte'
|
||||
import ConfigureBotPopup from './ConfigureBotPopup.svelte'
|
||||
|
||||
export let enabled: boolean
|
||||
|
||||
let isTestingConnection = false
|
||||
let isConnectionEstablished = false
|
||||
let connectionError: Error | undefined
|
||||
|
||||
let info: { name: string, username: string, photoUrl: string } | undefined = undefined
|
||||
let isLoading = false
|
||||
|
||||
const url = getMetadata(telegram.metadata.BotUrl) ?? ''
|
||||
|
||||
$: if (enabled) {
|
||||
void loadBotInfo()
|
||||
}
|
||||
|
||||
async function loadBotInfo (): Promise<void> {
|
||||
if (info !== undefined || isLoading) return
|
||||
isLoading = true
|
||||
try {
|
||||
const link = concatLink(url, '/info')
|
||||
const res = await fetch(link, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: 'Bearer ' + getMetadata(presentation.metadata.Token),
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
info = await res.json()
|
||||
} catch (e) {}
|
||||
isLoading = false
|
||||
}
|
||||
|
||||
async function handleTestConnection (): Promise<void> {
|
||||
isTestingConnection = true
|
||||
isConnectionEstablished = false
|
||||
connectionError = undefined
|
||||
|
||||
try {
|
||||
const link = concatLink(url, '/test')
|
||||
const res = await fetch(link, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: 'Bearer ' + getMetadata(presentation.metadata.Token),
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
isConnectionEstablished = res.ok
|
||||
if (!res.ok) {
|
||||
connectionError = new Error('Connection failed')
|
||||
}
|
||||
} catch (e) {
|
||||
connectionError = e as Error
|
||||
}
|
||||
isTestingConnection = false
|
||||
}
|
||||
|
||||
const codeFields = [
|
||||
{ id: 'code-1', name: 'code-1', optional: false },
|
||||
{ id: 'code-2', name: 'code-2', optional: false },
|
||||
{ id: 'code-3', name: 'code-3', optional: false },
|
||||
{ id: 'code-4', name: 'code-4', optional: false },
|
||||
{ id: 'code-5', name: 'code-5', optional: false },
|
||||
{ id: 'code-6', name: 'code-6', optional: false }
|
||||
]
|
||||
|
||||
let isCodeValid = false
|
||||
let codeError: IntlString | undefined
|
||||
|
||||
async function handleCode (event: CustomEvent<string>): Promise<void> {
|
||||
isCodeValid = false
|
||||
codeError = undefined
|
||||
|
||||
try {
|
||||
const link = concatLink(url, '/auth')
|
||||
const res = await fetch(link, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: 'Bearer ' + getMetadata(presentation.metadata.Token),
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ code: event.detail, account: getCurrentAccount()._id })
|
||||
})
|
||||
isCodeValid = res.ok
|
||||
if (!res.ok) {
|
||||
codeError = res.status === 409 ? telegram.string.AccountAlreadyConnected : telegram.string.InvalidCode
|
||||
}
|
||||
} catch (e) {
|
||||
codeError = telegram.string.SomethingWentWrong
|
||||
}
|
||||
function configureBot (): void {
|
||||
showPopup(ConfigureBotPopup, {})
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if enabled}
|
||||
{#if isLoading}
|
||||
<div class="flex-row-top mt-2 w-6">
|
||||
<Loading size="small" />
|
||||
</div>
|
||||
{:else if info}
|
||||
<div class="flex-col mt-2">
|
||||
<div class="flex-row-center flex-gap-2">
|
||||
{#if info.photoUrl !== ''}
|
||||
<img class="photo" src={info.photoUrl} alt="" />
|
||||
{:else}
|
||||
<Icon icon={TelegramColor} size="x-large" />
|
||||
{/if}
|
||||
{info.name} (@{info.username})
|
||||
<ModernButton
|
||||
label={telegram.string.TestConnection}
|
||||
size="small"
|
||||
loading={isTestingConnection}
|
||||
on:click={handleTestConnection}
|
||||
/>
|
||||
{#if isConnectionEstablished}
|
||||
<span class="flex-row-center flex-gap-1 label-connected">
|
||||
<Label label={telegram.string.Connected} />
|
||||
<Icon icon={IconCheckmark} size="medium" />
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
{#if connectionError}
|
||||
<span class="label-error mt-2">
|
||||
<Label label={telegram.string.ConnectBotError} />
|
||||
</span>
|
||||
{/if}
|
||||
<div class="flex-row-center flex-gap-1 mt-2">
|
||||
<Label label={telegram.string.ConnectBotInfoStart} />
|
||||
<a target="_blank" href={`https://t.me/${info.username}`}>{info.username}</a>
|
||||
<Label label={telegram.string.ConnectBotInfoEnd} />
|
||||
</div>
|
||||
|
||||
<CodeForm fields={codeFields} size="small" on:submit={handleCode} />
|
||||
{#if codeError}
|
||||
<span class="label-error mt-2">
|
||||
<Label label={codeError} />
|
||||
</span>
|
||||
{:else if isCodeValid}
|
||||
<span class="flex-row-center flex-gap-1 mt-2 label-connected">
|
||||
<Label label={telegram.string.Connected} />
|
||||
<Icon icon={IconCheckmark} size="medium" />
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<span class="label-error mt-2">
|
||||
<Label label={getEmbeddedLabel('Unable connect to service. Please try again.')} />
|
||||
</span>
|
||||
{/if}
|
||||
<div class="configure mt-2">
|
||||
<ModernButton label={getEmbeddedLabel('Configure')} kind="primary" size="small" on:click={configureBot} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.label-connected {
|
||||
color: var(--global-online-color);
|
||||
}
|
||||
|
||||
.label-error {
|
||||
color: var(--global-error-TextColor);
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--theme-link-color);
|
||||
|
||||
&:hover,
|
||||
&:active,
|
||||
&:visited {
|
||||
color: var(--theme-link-color);
|
||||
}
|
||||
}
|
||||
|
||||
.photo {
|
||||
border-radius: 50%;
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
.configure {
|
||||
width: 9.5rem;
|
||||
}
|
||||
</style>
|
||||
|
@ -66,6 +66,7 @@ export interface TelegramNotificationRecord {
|
||||
quote?: string
|
||||
body: string
|
||||
sender: string
|
||||
link: string
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -23,6 +23,7 @@ import {
|
||||
FindOptions,
|
||||
FindResult,
|
||||
Hierarchy,
|
||||
MeasureContext,
|
||||
Ref,
|
||||
Tx,
|
||||
TxCreateDoc,
|
||||
@ -98,6 +99,7 @@ export async function IsIncomingMessage (
|
||||
}
|
||||
|
||||
export async function sendEmailNotification (
|
||||
ctx: MeasureContext,
|
||||
text: string,
|
||||
html: string,
|
||||
subject: string,
|
||||
@ -106,7 +108,7 @@ export async function sendEmailNotification (
|
||||
try {
|
||||
const sesURL = getMetadata(serverNotification.metadata.SesUrl)
|
||||
if (sesURL === undefined || sesURL === '') {
|
||||
console.log('Please provide email service url to enable email confirmations.')
|
||||
ctx.error('Please provide email service url to enable email confirmations.')
|
||||
return
|
||||
}
|
||||
await fetch(concatLink(sesURL, '/send'), {
|
||||
@ -122,7 +124,7 @@ export async function sendEmailNotification (
|
||||
})
|
||||
})
|
||||
} catch (err) {
|
||||
console.log('Could not send email notification', err)
|
||||
ctx.error('Could not send email notification', { err, receiver })
|
||||
}
|
||||
}
|
||||
|
||||
@ -146,7 +148,7 @@ async function notifyByEmail (
|
||||
const content = await getContentByTemplate(doc, senderName, type, control, '', data)
|
||||
|
||||
if (content !== undefined) {
|
||||
await sendEmailNotification(content.text, content.html, content.subject, account.email)
|
||||
await sendEmailNotification(control.ctx, content.text, content.html, content.subject, account.email)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -296,7 +296,7 @@ async function sendEmailNotifications (
|
||||
if (content === undefined) return
|
||||
|
||||
for (const channel of channels) {
|
||||
await sendEmailNotification(content.text, content.html, content.subject, channel.value)
|
||||
await sendEmailNotification(control.ctx, content.text, content.html, content.subject, channel.value)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,10 +17,11 @@ import notification, {
|
||||
Collaborators,
|
||||
CommonNotificationType,
|
||||
NotificationContent,
|
||||
notificationId,
|
||||
NotificationProvider,
|
||||
NotificationType
|
||||
} from '@hcengineering/notification'
|
||||
import type { TriggerControl } from '@hcengineering/server-core'
|
||||
import serverCore, { TriggerControl } from '@hcengineering/server-core'
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
import contact, { formatName, PersonAccount } from '@hcengineering/contact'
|
||||
import core, {
|
||||
@ -42,9 +43,10 @@ import core, {
|
||||
TxRemoveDoc,
|
||||
TxUpdateDoc,
|
||||
type MeasureContext,
|
||||
Markup
|
||||
Markup,
|
||||
concatLink
|
||||
} from '@hcengineering/core'
|
||||
import { getResource, IntlString, translate } from '@hcengineering/platform'
|
||||
import { getMetadata, getResource, IntlString, translate } from '@hcengineering/platform'
|
||||
import serverNotification, {
|
||||
getPersonAccountById,
|
||||
HTMLPresenter,
|
||||
@ -53,7 +55,10 @@ import serverNotification, {
|
||||
SenderInfo,
|
||||
TextPresenter
|
||||
} from '@hcengineering/server-notification'
|
||||
import { DocUpdateMessage } from '@hcengineering/activity'
|
||||
import { ActivityMessage, DocUpdateMessage } from '@hcengineering/activity'
|
||||
import serverView from '@hcengineering/server-view'
|
||||
import { workbenchId } from '@hcengineering/workbench'
|
||||
import { encodeObjectURI } from '@hcengineering/view'
|
||||
|
||||
import { NotifyResult } from './types'
|
||||
|
||||
@ -546,3 +551,28 @@ export function createPullCollaboratorsTx (
|
||||
$pull: { collaborators: { $in: collaborators } }
|
||||
})
|
||||
}
|
||||
|
||||
export async function getNotificationLink (
|
||||
control: TriggerControl,
|
||||
doc: Doc,
|
||||
message?: Ref<ActivityMessage>
|
||||
): Promise<string> {
|
||||
const linkProviders = control.modelDb.findAllSync(serverView.mixin.ServerLinkIdProvider, {})
|
||||
const provider = linkProviders.find(({ _id }) => _id === doc._class)
|
||||
|
||||
let id: string = doc._id
|
||||
|
||||
if (provider !== undefined) {
|
||||
const encodeFn = await getResource(provider.encode)
|
||||
|
||||
id = await encodeFn(doc, control)
|
||||
}
|
||||
|
||||
const front = control.branding?.front ?? getMetadata(serverCore.metadata.FrontUrl) ?? ''
|
||||
const path = [workbenchId, 'platform', notificationId, encodeObjectURI(id, doc._class)]
|
||||
.map((p) => encodeURIComponent(p))
|
||||
.join('/')
|
||||
const link = concatLink(front, path)
|
||||
|
||||
return message !== undefined ? `${link}?message=${message}` : link
|
||||
}
|
||||
|
@ -38,16 +38,16 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/activity": "^0.6.0",
|
||||
"@hcengineering/contact": "^0.6.24",
|
||||
"@hcengineering/chunter": "^0.6.20",
|
||||
"@hcengineering/contact": "^0.6.24",
|
||||
"@hcengineering/core": "^0.6.32",
|
||||
"@hcengineering/notification": "^0.6.23",
|
||||
"@hcengineering/platform": "^0.6.11",
|
||||
"@hcengineering/server-core": "^0.6.1",
|
||||
"@hcengineering/server-token": "^0.6.11",
|
||||
"@hcengineering/server-notification": "^0.6.1",
|
||||
"@hcengineering/server-telegram": "^0.6.0",
|
||||
"@hcengineering/server-notification-resources": "^0.6.0",
|
||||
"@hcengineering/server-telegram": "^0.6.0",
|
||||
"@hcengineering/server-token": "^0.6.11",
|
||||
"@hcengineering/setting": "^0.6.17",
|
||||
"@hcengineering/telegram": "^0.6.21",
|
||||
"@hcengineering/text": "^0.6.5"
|
||||
|
@ -34,13 +34,17 @@ import telegram, { TelegramMessage, TelegramNotificationRecord } from '@hcengine
|
||||
import { BaseNotificationType, InboxNotification, NotificationType } from '@hcengineering/notification'
|
||||
import setting, { Integration } from '@hcengineering/setting'
|
||||
import { NotificationProviderFunc, ReceiverInfo, SenderInfo } from '@hcengineering/server-notification'
|
||||
import { getMetadata, getResource } from '@hcengineering/platform'
|
||||
import { getMetadata, getResource, translate } from '@hcengineering/platform'
|
||||
import serverTelegram from '@hcengineering/server-telegram'
|
||||
import { getTranslatedNotificationContent, getTextPresenter } from '@hcengineering/server-notification-resources'
|
||||
import {
|
||||
getTranslatedNotificationContent,
|
||||
getTextPresenter,
|
||||
getNotificationLink
|
||||
} from '@hcengineering/server-notification-resources'
|
||||
import { generateToken } from '@hcengineering/server-token'
|
||||
import chunter, { ChatMessage } from '@hcengineering/chunter'
|
||||
import { markupToHTML } from '@hcengineering/text'
|
||||
import activity, { ActivityMessage } from '@hcengineering/activity'
|
||||
import activity, { ActivityMessage, DocUpdateMessage } from '@hcengineering/activity'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -162,6 +166,14 @@ async function activityMessageToHtml (control: TriggerControl, message: Activity
|
||||
return undefined
|
||||
}
|
||||
|
||||
function isReactionMessage (message?: ActivityMessage): boolean {
|
||||
return (
|
||||
message !== undefined &&
|
||||
message._class === activity.class.DocUpdateMessage &&
|
||||
(message as DocUpdateMessage).objectClass === activity.class.Reaction
|
||||
)
|
||||
}
|
||||
|
||||
async function getTranslatedData (
|
||||
data: InboxNotification,
|
||||
doc: Doc,
|
||||
@ -171,6 +183,7 @@ async function getTranslatedData (
|
||||
title: string
|
||||
quote: string | undefined
|
||||
body: string
|
||||
link: string
|
||||
}> {
|
||||
const { hierarchy } = control
|
||||
|
||||
@ -189,15 +202,19 @@ async function getTranslatedData (
|
||||
if (hierarchy.isDerived(doc._class, activity.class.ActivityMessage)) {
|
||||
const html = await activityMessageToHtml(control, doc as ActivityMessage)
|
||||
if (html !== undefined) {
|
||||
title = ''
|
||||
quote = html
|
||||
}
|
||||
}
|
||||
|
||||
if (isReactionMessage(message)) {
|
||||
title = await translate(activity.string.Reacted, {})
|
||||
}
|
||||
|
||||
return {
|
||||
title,
|
||||
quote,
|
||||
body
|
||||
body,
|
||||
link: await getNotificationLink(control, doc, message?._id)
|
||||
}
|
||||
}
|
||||
|
||||
@ -217,7 +234,7 @@ const SendTelegramNotifications: NotificationProviderFunc = async (
|
||||
const botUrl = getMetadata(serverTelegram.metadata.BotUrl)
|
||||
|
||||
if (botUrl === undefined || botUrl === '') {
|
||||
console.log('Please provide telegram bot service url to enable telegram notifications.')
|
||||
control.ctx.error('Please provide telegram bot service url to enable telegram notifications.')
|
||||
return []
|
||||
}
|
||||
|
||||
@ -226,7 +243,7 @@ const SendTelegramNotifications: NotificationProviderFunc = async (
|
||||
}
|
||||
|
||||
try {
|
||||
const { title, body, quote } = await getTranslatedData(data, doc, control, message)
|
||||
const { title, body, quote, link } = await getTranslatedData(data, doc, control, message)
|
||||
const record: TelegramNotificationRecord = {
|
||||
notificationId: data._id,
|
||||
account: receiver._id,
|
||||
@ -234,7 +251,8 @@ const SendTelegramNotifications: NotificationProviderFunc = async (
|
||||
sender: data.intlParams?.senderName?.toString() ?? formatName(sender.person?.name ?? 'System'),
|
||||
title,
|
||||
quote,
|
||||
body
|
||||
body,
|
||||
link
|
||||
}
|
||||
|
||||
await fetch(concatLink(botUrl, '/notify'), {
|
||||
@ -246,7 +264,11 @@ const SendTelegramNotifications: NotificationProviderFunc = async (
|
||||
body: JSON.stringify([record])
|
||||
})
|
||||
} catch (err) {
|
||||
console.log('Could not send telegram notification', err)
|
||||
control.ctx.error('Could not send telegram notification', {
|
||||
err,
|
||||
notificationId: data._id,
|
||||
receiver: receiver.account.email
|
||||
})
|
||||
}
|
||||
|
||||
return []
|
||||
|
@ -42,6 +42,9 @@ async function onStart (ctx: Context, worker: PlatformWorker): Promise<void> {
|
||||
const message = welcomeMessage + '\n\n' + commandsHelp + '\n\n' + connectedMessage
|
||||
|
||||
await ctx.replyWithHTML(message)
|
||||
if (record.telegramUsername !== ctx.from?.username) {
|
||||
await worker.updateTelegramUsername(record, ctx.from?.username)
|
||||
}
|
||||
} else {
|
||||
const connectMessage = await translate(telegram.string.ConnectMessage, { app: config.App }, lang)
|
||||
const message = welcomeMessage + '\n\n' + commandsHelp + '\n\n' + connectMessage
|
||||
@ -87,17 +90,27 @@ async function onConnect (ctx: Context, worker: PlatformWorker): Promise<void> {
|
||||
return
|
||||
}
|
||||
|
||||
const code = await worker.generateCode(id)
|
||||
const code = await worker.generateCode(id, ctx.from?.username)
|
||||
await ctx.reply(`*${code}*`, { parse_mode: 'MarkdownV2' })
|
||||
}
|
||||
|
||||
async function onReply (id: number, message: TextMessage, replyTo: number, worker: PlatformWorker): Promise<boolean> {
|
||||
async function onReply (
|
||||
id: number,
|
||||
message: TextMessage,
|
||||
replyTo: number,
|
||||
worker: PlatformWorker,
|
||||
username?: string
|
||||
): Promise<boolean> {
|
||||
const userRecord = await worker.getUserRecord(id)
|
||||
|
||||
if (userRecord === undefined) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (userRecord.telegramUsername !== username) {
|
||||
await worker.updateTelegramUsername(userRecord, username)
|
||||
}
|
||||
|
||||
const notification = await worker.getNotificationRecord(replyTo, userRecord.email)
|
||||
|
||||
if (notification === undefined) {
|
||||
@ -127,7 +140,7 @@ export async function setUpBot (worker: PlatformWorker): Promise<Telegraf> {
|
||||
}
|
||||
|
||||
const replyTo = message.reply_to_message
|
||||
const isReplied = await onReply(id, message as TextMessage, replyTo.message_id, worker)
|
||||
const isReplied = await onReply(id, message as TextMessage, replyTo.message_id, worker, ctx.from.username)
|
||||
|
||||
if (isReplied) {
|
||||
await ctx.react('👍')
|
||||
|
@ -111,6 +111,7 @@ export function createServer (bot: Telegraf, worker: PlatformWorker, ctx: Measur
|
||||
}
|
||||
|
||||
await limiter.add(record.telegramId, async () => {
|
||||
ctx.info('Sending test message', { email: token.email, username: record.telegramUsername })
|
||||
const testMessage = await translate(telegram.string.TestMessage, { app: config.App })
|
||||
await bot.telegram.sendMessage(record.telegramId, testMessage)
|
||||
})
|
||||
@ -146,6 +147,7 @@ export function createServer (bot: Telegraf, worker: PlatformWorker, ctx: Measur
|
||||
}
|
||||
|
||||
void limiter.add(newRecord.telegramId, async () => {
|
||||
ctx.info('Connected account', { email: token.email, username: newRecord.telegramUsername })
|
||||
const message = await translate(telegram.string.AccountConnectedHtml, { app: config.App, email: token.email })
|
||||
await bot.telegram.sendMessage(newRecord.telegramId, message, { parse_mode: 'HTML' })
|
||||
})
|
||||
@ -175,11 +177,11 @@ export function createServer (bot: Telegraf, worker: PlatformWorker, ctx: Measur
|
||||
app.post(
|
||||
'/notify',
|
||||
wrapRequest(async (req, res, token) => {
|
||||
ctx.info('Received notification', { email: token.email })
|
||||
if (req.body == null || !Array.isArray(req.body)) {
|
||||
ctx.error('Invalid request body', { body: req.body, email: token.email })
|
||||
throw new ApiError(400)
|
||||
}
|
||||
|
||||
const notificationRecords = req.body as TelegramNotificationRecord[]
|
||||
const userRecord = await worker.getUserRecordByEmail(token.email)
|
||||
|
||||
@ -188,6 +190,12 @@ export function createServer (bot: Telegraf, worker: PlatformWorker, ctx: Measur
|
||||
throw new ApiError(404)
|
||||
}
|
||||
|
||||
ctx.info('Received notification', {
|
||||
email: token.email,
|
||||
username: userRecord.telegramUsername,
|
||||
ids: notificationRecords.map((it) => it.notificationId)
|
||||
})
|
||||
|
||||
for (const notificationRecord of notificationRecords) {
|
||||
void limiter.add(userRecord.telegramId, async () => {
|
||||
const formattedMessage = toTelegramHtml(notificationRecord)
|
||||
|
@ -52,8 +52,10 @@ export const start = async (): Promise<void> => {
|
||||
const app = createServer(bot, worker, ctx)
|
||||
|
||||
if (config.Domain === '') {
|
||||
ctx.info('Starting bot with polling')
|
||||
void bot.launch({ dropPendingUpdates: true })
|
||||
} else {
|
||||
ctx.info('Starting bot with webhook', { domain: config.Domain, port: config.BotPort })
|
||||
void bot.launch({ webhook: { domain: config.Domain, port: config.BotPort }, dropPendingUpdates: true }).then(() => {
|
||||
void bot.telegram.getWebhookInfo().then((info) => {
|
||||
ctx.info('Webhook info', info)
|
||||
|
@ -18,6 +18,7 @@ import { InboxNotification } from '@hcengineering/notification'
|
||||
|
||||
export interface UserRecord {
|
||||
telegramId: number
|
||||
telegramUsername?: string
|
||||
email: string
|
||||
}
|
||||
|
||||
@ -30,6 +31,7 @@ export interface NotificationRecord {
|
||||
|
||||
export interface OtpRecord {
|
||||
telegramId: number
|
||||
telegramUsername?: string
|
||||
code: string
|
||||
expires: Timestamp
|
||||
createdOn: Timestamp
|
||||
|
@ -74,7 +74,8 @@ const maxBodyLength = 2000
|
||||
const maxSenderLength = 100
|
||||
|
||||
export function toTelegramHtml (record: TelegramNotificationRecord): string {
|
||||
const title = record.title !== '' ? `<b>${platformToTelegram(record.title, maxTitleLength)}</b>` + '\n' : ''
|
||||
const title =
|
||||
record.title !== '' ? `<a href='${record.link}'>${platformToTelegram(record.title, maxTitleLength)}</a>` + '\n' : ''
|
||||
const quote =
|
||||
record.quote !== undefined && record.quote !== ''
|
||||
? `<blockquote>${platformToTelegram(record.quote, maxQuoteLength)}</blockquote>` + '\n'
|
||||
@ -132,8 +133,8 @@ export function platformToTelegram (message: string, limit: number): string {
|
||||
newMessage += unescape(text)
|
||||
|
||||
if (textLength > limit) {
|
||||
const extra = textLength - limit
|
||||
newMessage = newMessage.slice(0, -extra)
|
||||
const extra = textLength - limit + 1
|
||||
newMessage = newMessage.slice(0, -extra) + '…'
|
||||
}
|
||||
},
|
||||
onclosetag: (tag) => {
|
||||
|
@ -64,11 +64,7 @@ export class PlatformWorker {
|
||||
}
|
||||
}
|
||||
|
||||
async getUsersRecords (): Promise<UserRecord[]> {
|
||||
return await this.usersStorage.find().toArray()
|
||||
}
|
||||
|
||||
async addUser (id: number, email: string): Promise<UserRecord | undefined> {
|
||||
async addUser (id: number, email: string, telegramUsername?: string): Promise<UserRecord | undefined> {
|
||||
const emailRes = await this.usersStorage.findOne({ email })
|
||||
|
||||
if (emailRes !== null) {
|
||||
@ -83,11 +79,18 @@ export class PlatformWorker {
|
||||
return
|
||||
}
|
||||
|
||||
const insertResult = await this.usersStorage.insertOne({ telegramId: id, email })
|
||||
const insertResult = await this.usersStorage.insertOne({ telegramId: id, email, telegramUsername })
|
||||
|
||||
return (await this.usersStorage.findOne({ _id: insertResult.insertedId })) ?? undefined
|
||||
}
|
||||
|
||||
async updateTelegramUsername (userRecord: UserRecord, telegramUsername?: string): Promise<void> {
|
||||
await this.usersStorage.updateOne(
|
||||
{ telegramId: userRecord.telegramId, email: userRecord.email },
|
||||
{ $set: { telegramUsername } }
|
||||
)
|
||||
}
|
||||
|
||||
async addNotificationRecord (record: NotificationRecord): Promise<void> {
|
||||
await this.notificationsStorage.insertOne(record)
|
||||
}
|
||||
@ -148,10 +151,10 @@ export class PlatformWorker {
|
||||
throw new Error('Invalid OTP')
|
||||
}
|
||||
|
||||
return await this.addUser(otpData.telegramId, email)
|
||||
return await this.addUser(otpData.telegramId, email, otpData.telegramUsername)
|
||||
}
|
||||
|
||||
async generateCode (telegramId: number): Promise<string> {
|
||||
async generateCode (telegramId: number, telegramUsername?: string): Promise<string> {
|
||||
const now = Date.now()
|
||||
const otpData = (
|
||||
await this.otpStorage.find({ telegramId }).sort({ createdOn: SortingOrder.Descending }).limit(1).toArray()
|
||||
@ -168,7 +171,7 @@ export class PlatformWorker {
|
||||
const timeToLive = config.OtpTimeToLiveSec * 1000
|
||||
const expires = now + timeToLive
|
||||
|
||||
await this.otpStorage.insertOne({ telegramId, code: newCode, expires, createdOn: now })
|
||||
await this.otpStorage.insertOne({ telegramId, code: newCode, expires, createdOn: now, telegramUsername })
|
||||
|
||||
return newCode
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user