Compare commits
10 Commits
main
...
translate-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
daeffdfcf7 | ||
|
|
ca7787a8eb | ||
|
|
ed4c9689ec | ||
|
|
418c79ff55 | ||
|
|
911a358333 | ||
|
|
80ad2133f3 | ||
|
|
2c73c4f681 | ||
|
|
cbd37d0a1a | ||
|
|
f1f7d5905c | ||
|
|
0d2b14dcea |
@ -49,7 +49,7 @@ app_service_config_files:
|
||||
- /data/app-service.yaml
|
||||
```
|
||||
|
||||
Now edit `app-service.example.yaml` and save it at `files/app-service.yaml`, changing the tokens.
|
||||
Now edit `app-service.example.yaml` and save it at `files/app-service.yaml`, changing the tokens manually.
|
||||
|
||||
Copy over `.env.example` to `.env` and insert your values.
|
||||
|
||||
@ -101,4 +101,8 @@ Then you can restart with an empty but quite equal server, following the instruc
|
||||
|
||||
- Getting data from Rocket.Chat via (currently) manual mongodb export
|
||||
- Room to Channel conversion:
|
||||
- Read-only attributes of 2 verdigado channels not converted to power levels due to complexity
|
||||
- Read-only attributes of channels not converted to power levels due to complexity
|
||||
- Reactions:
|
||||
- So far only reactions used in our chats have been translated
|
||||
- Individual logos of *netzbegruenung* and *verdigado* have been replaced by a generic sunflower
|
||||
- Skin colour tones and genders have been ignored in the manual translation, using the neutral versions
|
||||
|
||||
49
package-lock.json
generated
49
package-lock.json
generated
@ -12,6 +12,7 @@
|
||||
"axios": "^1.5.0",
|
||||
"dotenv": "^16.3.1",
|
||||
"n-readlines": "^1.0.1",
|
||||
"node-emoji": "^2.1.0",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"sqlite3": "^5.1.6",
|
||||
"typeorm": "^0.3.17",
|
||||
@ -1405,6 +1406,17 @@
|
||||
"integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@sindresorhus/is": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-3.1.2.tgz",
|
||||
"integrity": "sha512-JiX9vxoKMmu8Y3Zr2RVathBL1Cdu4Nt4MuNWemt1Nc06A0RAin9c5FArkhGsyMBWfCu4zj+9b+GxtjAnE4qqLQ==",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sindresorhus/is?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@sinonjs/commons": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz",
|
||||
@ -2498,8 +2510,6 @@
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz",
|
||||
"integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
@ -3204,6 +3214,11 @@
|
||||
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/emojilib": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz",
|
||||
"integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw=="
|
||||
},
|
||||
"node_modules/enabled": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz",
|
||||
@ -6317,6 +6332,17 @@
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz",
|
||||
"integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ=="
|
||||
},
|
||||
"node_modules/node-emoji": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.1.0.tgz",
|
||||
"integrity": "sha512-tcsBm9C6FmPN5Wo7OjFi9lgMyJjvkAeirmjR/ax8Ttfqy4N8PoFic26uqFTIgayHPNI5FH4ltUvfh9kHzwcK9A==",
|
||||
"dependencies": {
|
||||
"@sindresorhus/is": "^3.1.2",
|
||||
"char-regex": "^1.0.2",
|
||||
"emojilib": "^2.4.0",
|
||||
"skin-tone": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
||||
@ -7426,6 +7452,17 @@
|
||||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/skin-tone": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz",
|
||||
"integrity": "sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==",
|
||||
"dependencies": {
|
||||
"unicode-emoji-modifier-base": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/slash": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
|
||||
@ -8292,6 +8329,14 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/unicode-emoji-modifier-base": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz",
|
||||
"integrity": "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/unique-filename": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz",
|
||||
|
||||
@ -50,6 +50,7 @@
|
||||
"axios": "^1.5.0",
|
||||
"dotenv": "^16.3.1",
|
||||
"n-readlines": "^1.0.1",
|
||||
"node-emoji": "^2.1.0",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"sqlite3": "^5.1.6",
|
||||
"typeorm": "^0.3.17",
|
||||
|
||||
@ -75,9 +75,13 @@ async function removeExcessRoomMembers() {
|
||||
)
|
||||
|
||||
// do action for any user in mx, but not in rc
|
||||
const adminUsername = process.env.ADMIN_USERNAME || ''
|
||||
await Promise.all(
|
||||
actualMembers.map(async (actualMember) => {
|
||||
if (!memberNames.includes(actualMember)) {
|
||||
if (
|
||||
!memberNames.includes(actualMember) &&
|
||||
!actualMember.includes(adminUsername) // exclude admin from removal
|
||||
) {
|
||||
log.warn(
|
||||
`Member ${actualMember} should not be in room ${roomMapping.matrixId}, removing`
|
||||
)
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { AxiosError } from 'axios'
|
||||
import * as emoji from 'node-emoji'
|
||||
import { Entity, entities } from '../Entities'
|
||||
import { IdMapping } from '../entity/IdMapping'
|
||||
import log from '../helpers/logger'
|
||||
@ -8,9 +9,11 @@ import {
|
||||
getMessageId,
|
||||
getRoomId,
|
||||
getUserId,
|
||||
getUserMappingByName,
|
||||
save,
|
||||
} from '../helpers/storage'
|
||||
import { axios, formatUserSessionOptions } from '../helpers/synapse'
|
||||
import reactionKeys from '../reactions.json'
|
||||
import { acceptInvitation, inviteMember } from './rooms'
|
||||
|
||||
const applicationServiceToken = process.env.AS_TOKEN || ''
|
||||
@ -39,7 +42,11 @@ export type RcMessage = {
|
||||
pinned?: boolean
|
||||
drid?: string // The direct room id (if belongs to a direct room).
|
||||
// attachments?: any[] // An array of attachment objects, available only when the message has at least one attachment.
|
||||
reactions?: object // Object containing reaction information associated with the message.
|
||||
reactions?: {
|
||||
[key: string]: {
|
||||
usernames: string[]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type MatrixMessage = {
|
||||
@ -93,6 +100,65 @@ export async function createMessage(
|
||||
).data.event_id
|
||||
}
|
||||
|
||||
export async function handleReactions(
|
||||
reactions: object,
|
||||
matrixMessageId: string,
|
||||
matrixRoomId: string
|
||||
): Promise<void> {
|
||||
for (const [reaction, value] of Object.entries(reactions)) {
|
||||
// Lookup key/emoji
|
||||
const reactionKey: string =
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(reactionKeys as any)[reaction] || emoji.get(reaction.replaceAll(':', ''))
|
||||
if (!reactionKey) {
|
||||
log.warn(
|
||||
`Could not find an emoji for ${reaction} for message ${matrixMessageId}, skipping`
|
||||
)
|
||||
return
|
||||
}
|
||||
await Promise.all(
|
||||
value.usernames.map(async (rcUsername: string) => {
|
||||
// generate transaction id
|
||||
const transactionId = Buffer.from(
|
||||
[matrixMessageId, reaction, rcUsername].join('\0')
|
||||
).toString('base64')
|
||||
// lookup user access token
|
||||
const userMapping = await getUserMappingByName(rcUsername)
|
||||
if (!userMapping) {
|
||||
log.warn(
|
||||
`Could not find user mapping for name: ${rcUsername}, skipping reaction ${reaction} for message ${matrixMessageId}`
|
||||
)
|
||||
return
|
||||
}
|
||||
if (!userMapping.accessToken) {
|
||||
throw new Error(
|
||||
`User mapping for name ${rcUsername} has no access token`
|
||||
)
|
||||
}
|
||||
|
||||
const userSessionOptions = formatUserSessionOptions(
|
||||
userMapping.accessToken
|
||||
)
|
||||
log.http(
|
||||
`Adding reaction to message ${matrixMessageId} with symbol ${reactionKey} for user ${rcUsername}`
|
||||
)
|
||||
// put reaction
|
||||
await axios.put(
|
||||
`/_matrix/client/v3/rooms/${matrixRoomId}/send/m.reaction/${transactionId}`,
|
||||
{
|
||||
'm.relates_to': {
|
||||
rel_type: 'm.annotation',
|
||||
event_id: matrixMessageId,
|
||||
key: reactionKey,
|
||||
},
|
||||
},
|
||||
userSessionOptions
|
||||
)
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export async function handle(rcMessage: RcMessage): Promise<void> {
|
||||
log.info(`Parsing message with ID: ${rcMessage._id}`)
|
||||
|
||||
@ -211,6 +277,13 @@ export async function handle(rcMessage: RcMessage): Promise<void> {
|
||||
ts,
|
||||
rcMessage._id
|
||||
)
|
||||
if (rcMessage.reactions) {
|
||||
log.info(
|
||||
`Parsing reactions for message ${rcMessage._id}`,
|
||||
rcMessage.reactions
|
||||
)
|
||||
await handleReactions(rcMessage.reactions, event_id, room_id)
|
||||
}
|
||||
await createMapping(rcMessage._id, event_id)
|
||||
} catch (error) {
|
||||
if (
|
||||
@ -260,6 +333,13 @@ export async function handle(rcMessage: RcMessage): Promise<void> {
|
||||
ts,
|
||||
rcMessage._id
|
||||
)
|
||||
if (rcMessage.reactions) {
|
||||
log.info(
|
||||
`Parsing reactions for message ${rcMessage._id}`,
|
||||
rcMessage.reactions
|
||||
)
|
||||
await handleReactions(rcMessage.reactions, event_id, room_id)
|
||||
}
|
||||
await createMapping(rcMessage._id, event_id)
|
||||
} else {
|
||||
throw error
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { DataSource } from 'typeorm'
|
||||
import { DataSource, ILike } from 'typeorm'
|
||||
import { Entity, entities } from '../Entities'
|
||||
import { IdMapping } from '../entity/IdMapping'
|
||||
import { Membership } from '../entity/Membership'
|
||||
@ -35,6 +35,15 @@ export function getMappingByMatrixId(id: string): Promise<IdMapping | null> {
|
||||
})
|
||||
}
|
||||
|
||||
export function getUserMappingByName(
|
||||
username: string
|
||||
): Promise<IdMapping | null> {
|
||||
return AppDataSource.manager.findOneBy(IdMapping, {
|
||||
matrixId: ILike(`@${username.toLowerCase()}:%`),
|
||||
type: entities[Entity.Users].mappingType,
|
||||
})
|
||||
}
|
||||
|
||||
export async function save(entity: IdMapping | Membership): Promise<void> {
|
||||
await AppDataSource.manager.save(entity)
|
||||
}
|
||||
|
||||
1007
src/reactions.json
Normal file
1007
src/reactions.json
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user