Telegram attachments (#1127)

Signed-off-by: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com>
This commit is contained in:
Denis Bykhov 2022-03-11 15:05:44 +06:00 committed by GitHub
parent 173c062369
commit 98bdcfce68
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 176 additions and 91 deletions

View File

@ -61,6 +61,7 @@
"@anticrm/server-core": "~0.6.1",
"@anticrm/server-token": "~0.6.0",
"@anticrm/model-attachment": "~0.6.0",
"@anticrm/model-contact": "~0.6.0",
"@anticrm/mongo": "~0.6.0",
"@anticrm/dev-storage": "~0.6.0",
"@anticrm/server-attachment": "~0.6.0",

View File

@ -199,9 +199,25 @@ program
})
program
.command('clear-telegram-history')
.command('clear-telegram-history <workspace>')
.description('clear telegram history')
.option('-w, --workspace <workspace>', 'target workspace')
.action(async (workspace: string, cmd) => {
return await withDatabase(mongodbUri, async (db) => {
const telegramDB = process.env.TELEGRAM_DATABASE
if (telegramDB === undefined) {
console.error('please provide TELEGRAM_DATABASE.')
process.exit(1)
}
console.log(`clearing ${workspace} history:`)
await clearTelegramHistory(mongodbUri, workspace, telegramDB, minio)
})
})
program
.command('clear-telegram-all-history')
.description('clear telegram history')
.action(async (cmd) => {
return await withDatabase(mongodbUri, async (db) => {
const telegramDB = process.env.TELEGRAM_DATABASE
@ -211,12 +227,10 @@ program
}
const workspaces = await listWorkspaces(db)
const targetWorkspaces =
cmd.workspace !== undefined ? workspaces.filter((x) => x.workspace === cmd.workspace) : workspaces
for (const w of targetWorkspaces) {
for (const w of workspaces) {
console.log(`clearing ${w.workspace} history:`)
await clearTelegramHistory(mongodbUri, w.workspace, telegramDB)
await clearTelegramHistory(mongodbUri, w.workspace, telegramDB, minio)
}
})
})

View File

@ -14,24 +14,60 @@
// limitations under the License.
//
import { MongoClient } from 'mongodb'
import { DOMAIN_TX } from '@anticrm/core'
import { DOMAIN_TX, Ref } from '@anticrm/core'
import { DOMAIN_ATTACHMENT } from '@anticrm/model-attachment'
import contact, { DOMAIN_CHANNEL } from '@anticrm/model-contact'
import { DOMAIN_TELEGRAM } from '@anticrm/model-telegram'
import telegram from '@anticrm/telegram'
import telegram, { SharedTelegramMessage, SharedTelegramMessages } from '@anticrm/telegram'
import { Client } from 'minio'
import { MongoClient } from 'mongodb'
const LastMessages = 'last-msgs'
/**
* @public
*/
export async function clearTelegramHistory (mongoUrl: string, workspace: string, tgDb: string): Promise<void> {
export async function clearTelegramHistory (
mongoUrl: string,
workspace: string,
tgDb: string,
minio: Client
): Promise<void> {
const client = new MongoClient(mongoUrl)
try {
await client.connect()
const workspaceDB = client.db(workspace)
const telegramDB = client.db(tgDb)
const sharedMessages = await workspaceDB
.collection(DOMAIN_TELEGRAM)
.find<SharedTelegramMessages>({
_class: telegram.class.SharedMessages
})
.toArray()
const sharedIds: Ref<SharedTelegramMessage>[] = []
for (const sharedMessage of sharedMessages) {
for (const message of sharedMessage.messages) {
sharedIds.push(message._id)
}
}
const files = await workspaceDB
.collection(DOMAIN_ATTACHMENT)
.find(
{
attachedToClass: telegram.class.Message,
attachedTo: { $nin: sharedIds }
},
{
projection: {
file: 1
}
}
)
.toArray()
const attachments = files.map((file) => file.file)
console.log('clearing txes and messages...')
await Promise.all([
workspaceDB.collection(DOMAIN_TX).deleteMany({
@ -39,7 +75,21 @@ export async function clearTelegramHistory (mongoUrl: string, workspace: string,
}),
workspaceDB.collection(DOMAIN_TELEGRAM).deleteMany({
_class: telegram.class.Message
})
}),
workspaceDB.collection(DOMAIN_CHANNEL).updateMany(
{
provider: contact.channelProvider.Telegram
},
{
$set: {
items: 0
}
}
),
workspaceDB.collection(DOMAIN_ATTACHMENT).deleteMany({
attachedToClass: telegram.class.Message
}),
minio.removeObjects(workspace, Array.from(attachments))
])
console.log('clearing telegram service data...')

View File

@ -30,6 +30,7 @@
"@anticrm/core": "~0.6.0",
"@anticrm/platform": "~0.6.5",
"@anticrm/model-core": "~0.6.0",
"@anticrm/model-attachment": "~0.6.0",
"@anticrm/model-contact": "~0.6.1",
"@anticrm/telegram": "~0.6.0",
"@anticrm/telegram-resources": "~0.6.0",

View File

@ -14,14 +14,20 @@
// limitations under the License.
//
import { Builder, Model, TypeString, TypeBoolean, Prop, ArrOf, Index } from '@anticrm/model'
import { Builder, Model, TypeString, TypeBoolean, Prop, ArrOf, Index, Collection } from '@anticrm/model'
import core, { TAttachedDoc } from '@anticrm/model-core'
import contact from '@anticrm/model-contact'
import telegram from './plugin'
import type { TelegramMessage, SharedTelegramMessage, SharedTelegramMessages } from '@anticrm/telegram'
import type {
TelegramMessage,
NewTelegramMessage,
SharedTelegramMessage,
SharedTelegramMessages
} from '@anticrm/telegram'
import { Domain, IndexKind, Type } from '@anticrm/core'
import setting from '@anticrm/setting'
import activity from '@anticrm/activity'
import attachment from '@anticrm/model-attachment'
export const DOMAIN_TELEGRAM = 'telegram' as Domain
@ -37,6 +43,22 @@ export class TTelegramMessage extends TAttachedDoc implements TelegramMessage {
@Prop(TypeBoolean(), telegram.string.Incoming)
incoming!: boolean
@Prop(Collection(attachment.class.Attachment), attachment.string.Attachments)
attachments?: number
}
@Model(telegram.class.NewMessage, core.class.AttachedDoc, DOMAIN_TELEGRAM)
export class TNewTelegramMessage extends TAttachedDoc implements NewTelegramMessage {
@Prop(TypeString(), telegram.string.Content)
@Index(IndexKind.FullText)
content!: string
@Prop(TypeString(), telegram.string.Status)
status!: 'new' | 'sent'
@Prop(Collection(attachment.class.Attachment), attachment.string.Attachments)
attachments?: number
}
@Model(telegram.class.SharedMessages, core.class.AttachedDoc, DOMAIN_TELEGRAM)
@ -46,7 +68,7 @@ export class TSharedTelegramMessages extends TAttachedDoc implements SharedTeleg
}
export function createModel (builder: Builder): void {
builder.createModel(TTelegramMessage, TSharedTelegramMessages)
builder.createModel(TTelegramMessage, TSharedTelegramMessages, TNewTelegramMessage)
builder.createDoc(
contact.class.ChannelProvider,

View File

@ -29,7 +29,8 @@ export default mergeIds(telegramId, telegram, {
Incoming: '' as IntlString,
Messages: '' as IntlString,
Telegram: '' as IntlString,
TelegramIntegrationDesc: '' as IntlString
TelegramIntegrationDesc: '' as IntlString,
Status: '' as IntlString
},
ids: {
TxSharedCreate: '' as Ref<TxViewlet>

View File

@ -20,6 +20,7 @@
"Incoming": "Incoming",
"Messages": "Messages",
"Telegram": "Telegram",
"TelegramIntegrationDesc": "Use telegram integration"
"TelegramIntegrationDesc": "Use telegram integration",
"Status": "Status"
}
}

View File

@ -20,6 +20,7 @@
"Incoming": "Входящее",
"Messages": "Сообщения",
"Telegram": "Telegram",
"TelegramIntegrationDesc": "Подключить Telegram"
"TelegramIntegrationDesc": "Подключить Telegram",
"Status": "Статус"
}
}

View File

@ -41,6 +41,8 @@
"@anticrm/chunter": "~0.6.0",
"@anticrm/login": "~0.6.1",
"@anticrm/core": "~0.6.11",
"@anticrm/notification-resources": "~0.6.0"
"@anticrm/notification-resources": "~0.6.0",
"@anticrm/attachment": "~0.6.0",
"@anticrm/attachment-resources": "~0.6.0"
}
}

View File

@ -14,23 +14,24 @@
// limitations under the License.
-->
<script lang="ts">
import attachment from '@anticrm/attachment'
import { AttachmentRefInput } from '@anticrm/attachment-resources'
import contact, { Channel, Contact, EmployeeAccount, formatName } from '@anticrm/contact'
import { getCurrentAccount, Ref, SortingOrder, Space } from '@anticrm/core'
import login from '@anticrm/login'
import { getMetadata } from '@anticrm/platform'
import { generateId, getCurrentAccount, Ref, SortingOrder, Space } from '@anticrm/core'
import { NotificationClientImpl } from '@anticrm/notification-resources'
import { createQuery, getClient } from '@anticrm/presentation'
import setting from '@anticrm/setting'
import type { SharedTelegramMessage, TelegramMessage } from '@anticrm/telegram'
import { ReferenceInput } from '@anticrm/text-editor'
import type { NewTelegramMessage, SharedTelegramMessage, TelegramMessage } from '@anticrm/telegram'
import { ActionIcon, Button, IconShare, ScrollBox, showPopup } from '@anticrm/ui'
import telegram from '../plugin'
import Connect from './Connect.svelte'
import TelegramIcon from './icons/Telegram.svelte'
import Messages from './Messages.svelte'
import { NotificationClientImpl } from '@anticrm/notification-resources'
export let object: Contact
let channel: Channel | undefined = undefined
let objectId: Ref<NewTelegramMessage> = generateId()
const client = getClient()
const notificationClient = NotificationClientImpl.getClient()
@ -48,7 +49,6 @@
let enabled: boolean
let selected: Set<Ref<SharedTelegramMessage>> = new Set<Ref<SharedTelegramMessage>>()
let selectable = false
const url = getMetadata(login.metadata.TelegramUrl) ?? ''
const messagesQuery = createQuery()
const accauntsQuery = createQuery()
@ -67,7 +67,13 @@
const accountsIds = new Set(messages.map((p) => p.modifiedBy as Ref<EmployeeAccount>))
updateAccountsQuery(accountsIds)
},
{ sort: { modifiedOn: SortingOrder.Descending }, limit: 500 }
{
sort: { modifiedOn: SortingOrder.Descending },
limit: 500,
lookup: {
_id: { attachments: attachment.class.Attachment }
}
}
)
}
@ -87,63 +93,24 @@
}
)
async function sendMsg (to: string, msg: string) {
const res = await fetch(url + '/send-msg', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + getMetadata(login.metadata.LoginToken),
'Content-Type': 'application/json'
},
body: JSON.stringify({
to,
msg
})
})
if (channel !== undefined) {
await notificationClient.updateLastView(channel._id, channel._class, undefined, true)
}
return res
}
async function addContact (phone: string) {
const [lastName, firstName] = object.name.split(',')
return await fetch(url + '/add-contact', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + getMetadata(login.metadata.LoginToken),
'Content-Type': 'application/json'
},
body: JSON.stringify({
firstName: firstName ?? '',
lastName: lastName ?? '',
phone
})
})
}
async function onMessage (event: CustomEvent) {
const to = channel?.value ?? ''
const sendRes = await sendMsg(to, event.detail)
if (channel === undefined) return
const { message, attachments } = event.detail
await client.createDoc(
telegram.class.NewMessage,
telegram.space.Telegram,
{
content: message,
status: 'new',
attachments,
attachedTo: channel._id,
attachedToClass: channel._class,
collection: 'newMessages'
},
objectId
)
if (sendRes.status !== 400 || !to.startsWith('+')) {
return
}
const err = await sendRes.json()
if (err.code !== 'CONTACT_IMPORT_REQUIRED') {
return
}
const addRes = await addContact(to)
if (Math.trunc(addRes.status / 100) !== 2) {
const { message } = await addRes.json().catch(() => ({ message: 'Unknown error' }))
throw Error(message)
}
await sendMsg(to, event.detail)
objectId = generateId()
}
function getName (message: TelegramMessage, accounts: EmployeeAccount[]): string {
@ -240,7 +207,12 @@
</div>
</div>
{:else if enabled}
<ReferenceInput on:message={onMessage} />
<AttachmentRefInput
space={telegram.space.Telegram}
_class={telegram.class.NewMessage}
{objectId}
on:message={onMessage}
/>
{:else}
<div class="flex-center">
<Button

View File

@ -14,17 +14,20 @@
// limitations under the License.
-->
<script lang="ts">
import type { SharedTelegramMessage } from '@anticrm/telegram'
import { MessageViewer } from '@anticrm/presentation'
import { Attachment } from '@anticrm/attachment'
import { AttachmentList } from '@anticrm/attachment-resources'
import { formatName } from '@anticrm/contact'
import { CheckBox } from '@anticrm/ui'
import { WithLookup } from '@anticrm/core'
import { MessageViewer } from '@anticrm/presentation'
import type { SharedTelegramMessage } from '@anticrm/telegram'
import { CheckBox, getPlatformColorForText } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
import { getPlatformColorForText } from '@anticrm/ui'
export let message: SharedTelegramMessage
export let message: WithLookup<SharedTelegramMessage>
export let showName: boolean = false
export let selected: boolean = false
export let selectable: boolean = false
$: attachments = message.$lookup?.attachments ? (message.$lookup.attachments as Attachment[]) : undefined
const dispatch = createEventDispatcher()
</script>
@ -32,7 +35,7 @@
<div
class="flex-between"
class:selectable
on:click|preventDefault={() => {
on:click={() => {
dispatch('select', message)
}}
>
@ -41,6 +44,9 @@
{#if showName}
<div class="name" style="color: {getPlatformColorForText(message.sender)}">{formatName(message.sender)}</div>
{/if}
{#if attachments}
<AttachmentList {attachments} />
{/if}
<div class="flex">
<div class="caption-color mr-4"><MessageViewer message={message.content} /></div>
<div class="time">

View File

@ -22,16 +22,29 @@ import type { IntegrationType, Handler } from '@anticrm/setting'
/**
* @public
*/
export interface TelegramMessage extends AttachedDoc {
export interface BaseTelegramMessage extends Doc {
content: string
attachments?: number
}
/**
* @public
*/
export interface TelegramMessage extends BaseTelegramMessage, AttachedDoc {
incoming: boolean
}
/**
* @public
*/
export interface SharedTelegramMessage extends Doc {
content: string
export interface NewTelegramMessage extends BaseTelegramMessage, AttachedDoc {
status: 'new' | 'sent'
}
/**
* @public
*/
export interface SharedTelegramMessage extends BaseTelegramMessage {
incoming: boolean
sender: string
}
@ -62,6 +75,7 @@ export default plugin(telegramId, {
},
class: {
Message: '' as Ref<Class<TelegramMessage>>,
NewMessage: '' as Ref<Class<NewTelegramMessage>>,
SharedMessage: '' as Ref<Class<SharedTelegramMessage>>,
SharedMessages: '' as Ref<Class<SharedTelegramMessages>>
},