Skip to content
Extraits de code Groupes Projets

Comparer les révisions

Les modifications sont affichées comme si la révision source était fusionnée avec la révision cible. En savoir plus sur la comparaison des révisions.

Source

Sélectionner le projet cible
No results found
Sélectionner une révision Git
  • cherry-pick-moise
  • develop
  • develop-workaround-old-matrix-download-endpoint-removed
  • rebase-v4
  • renovate/configure
5 résultats

Cible

Sélectionner le projet cible
No results found
Sélectionner une révision Git
  • cherry-pick-moise
  • develop
  • fix-oversized-file-transfer
  • implement-discord-markdown-update
  • matrix-answer-modified-fix
  • matrix-attachment-order-fix
  • matrix-attachments-order-fix
7 résultats
Afficher les modifications

Validations dans la source 519

419 additional commits have been omitted to prevent performance issues.
104 files
+ 10314
7581
Comparer les modifications
  • Côte à côte
  • En ligne

Fichiers

.eslintrc

0 → 100644
+58 −0
Numéro de ligne d'origine Numéro de ligne de diff Ligne de diff
{
    "parser": "@typescript-eslint/parser",
    "plugins": ["@typescript-eslint"],
    "parserOptions": {
        "ecmaVersion": 9,
        "ecmaFeatures": {
            "jsx": false
        },
        "project": "tsconfig.json"
    },
    "env": {
        "node": true,
        "jasmine": true
    },
    "extends": ["plugin:@typescript-eslint/recommended"],
    "rules": {
        "ordered-imports": "off",
        "no-trailing-spaces": "error",
        "max-classes-per-file": ["warn", 1],
        "object-literal-sort-keys": "off",
        "@typescript-eslint/naming-convention": "warn",
        "@typescript-eslint/no-explicit-any": "error",
        "@typescript-eslint/prefer-for-of": "error",
        "@typescript-eslint/typedef": "warn",
        "@typescript-eslint/no-floating-promises": "error",
        "curly": "error",
        "no-empty": "off",
        "no-invalid-this": "error",
        "@typescript-eslint/no-throw-literal": "warn",
        "prefer-const": "error",
        "indent": ["error", 4],
        "max-lines": ["warn", 500],
        "no-duplicate-imports": "error",
        "@typescript-eslint/array-type": "error",
        "@typescript-eslint/promise-function-async": "error",
        "no-bitwise": "error",
        "no-console": "error",
        "no-debugger": "error",
        "prefer-template": "error",
        // Disable these as they were introduced by @typescript-eslint/recommended
        "@typescript-eslint/no-use-before-define": "off",
        "@typescript-eslint/no-inferrable-types": "off",
        "@typescript-eslint/member-delimiter-style": "off",
        "@typescript-eslint/no-unused-expressions": "off",
        "@typescript-eslint/interface-name-prefix": "off"
    },
    "overrides": [
        {
            "files": [
                "test/**/*"
            ],
            "rules": {
                "@typescript-eslint/no-empty-function": "off",
                "@typescript-eslint/no-explicit-any": "off"
            }
        }
    ]
}

.github/CODEOWNERS

0 → 100644
+1 −0
Numéro de ligne d'origine Numéro de ligne de diff Ligne de diff
* @matrix-org/bridges
 No newline at end of file
+3 −0
Numéro de ligne d'origine Numéro de ligne de diff Ligne de diff
<!--
Hi there 👋, please check you've read the [CONTRIBUTING](../CONTRIBUTING.md) guide before submitting a PR (it's worth it, honestly).
-->
+44 −0
Numéro de ligne d'origine Numéro de ligne de diff Ligne de diff
name: Container Image

on:
  push:
    branches: [ develop ]
    tags: [ 'v*' ]
  pull_request:
    branches: [ develop ]

env:
  IMAGE_NAME: ${{ github.repository }}
  REGISTRY: ghcr.io

jobs:
  push:
    runs-on: ubuntu-latest

    permissions:
      contents: read
      packages: write

    steps:
      - name: Checkout repository
        uses: actions/checkout@v2

      - name: Log into registry ${{ env.REGISTRY }}
        uses: docker/login-action@v1
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract Docker metadata
        id: meta
        uses: docker/metadata-action@v3
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}

      - name: Build and push Docker image
        uses: docker/build-push-action@v3
        with:
          push: ${{ github.event_name != 'pull_request' }}
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
+22 −0
Numéro de ligne d'origine Numéro de ligne de diff Ligne de diff
name: Newsfile

on:
  push:
    branches: ["develop", "release-*"]
  pull_request:
  workflow_dispatch:

jobs:
  changelog:
    if: ${{ github.base_ref == 'develop'  || contains(github.base_ref, 'release-') }}
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
        with:
          ref: ${{ github.event.pull_request.head.sha }}
          fetch-depth: 0
      - uses: actions/setup-python@v2
      - run: pip install towncrier==21.9.0
      - run: scripts/check-newsfragment
        env:
          PULL_REQUEST_NUMBER: ${{ github.event.number }}
+10 −0
Numéro de ligne d'origine Numéro de ligne de diff Ligne de diff
name: Contribution requirements

on:
  pull_request:
    types: [opened, edited, synchronize]

jobs:
  signoff:
    uses: matrix-org/backend-meta/.github/workflows/sign-off.yml@v1.4.0
          
+28 −0
Numéro de ligne d'origine Numéro de ligne de diff Ligne de diff
name: CI

on: [push, pull_request]

jobs:
  lint:
   runs-on: ubuntu-latest
   steps:
    - uses: actions/checkout@v2
    - name: Use Node.js
      uses: actions/setup-node@v1
      with:
        node-version: 16
    - run: yarn
    - run: yarn lint
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node_version: [16, 18]
    steps:
      - uses: actions/checkout@v2
      - name: Use Node.js ${{ matrix.node_version }}
        uses: actions/setup-node@v1
        with:
          node-version: ${{ matrix.node_version }}
      - run: yarn
      - run: yarn test
+10 −0
Numéro de ligne d'origine Numéro de ligne de diff Ligne de diff
@@ -38,6 +38,10 @@ jspm_packages
# Optional npm cache directory
.npm

# Optional local Yarn settings
.yarn
.yarnrc

# Optional REPL history
.node_repl_history

@@ -49,3 +53,9 @@ build

*.db
*.db.backup

.vscode/
*.code-workspace

# Local History for Visual Studio Code
.history/

.mocharc.yml

0 → 100644
+6 −0
Numéro de ligne d'origine Numéro de ligne de diff Ligne de diff
reporter: list
ui: bdd
require: 
  - "ts-node/register"
  - "source-map-support/register"
recursive: true
 No newline at end of file

.npmrc

0 → 100644
+2 −0
Numéro de ligne d'origine Numéro de ligne de diff Ligne de diff
engine-strict = true
@mx-puppet:registry="https://gitlab.com/api/v4/packages/npm/"

.nycrc

0 → 100644
+5 −0
Numéro de ligne d'origine Numéro de ligne de diff Ligne de diff
{
    "extends": "@istanbuljs/nyc-config-typescript",
    "reporter": ["text", "text-summary", "lcov"],
    "all": true
}

.travis.yml

supprimé100644 → 0
+0 −27
Numéro de ligne d'origine Numéro de ligne de diff Ligne de diff
language: node_js
install: npm install && npm run build
node_js:
    - "10"
    - "12"

jobs:
    - script: npm run test
      name: "Unit - Node 10"
      node_js: "10"
    - script: npm run test
      name: "Unit - Node 12"
      node_js: "12"
    - script: npm run lint
      name: "Linting"
      node_js: "12"
    - script: npm run coverage
      name: "Coverage"
      node_js: "12"

notifications:
    webhooks:
        urls:
            - "https://scalar.vector.im/api/neb/services/hooks/dHJhdmlzLWNpLyU0MEhhbGYtU2hvdCUzQWhhbGYtc2hvdC51ay8lMjFxUE5PblVzTnNaclRvRlpxeEIlM0FoYWxmLXNob3QudWs"
        on_success: change  # always|never|change
        on_failure: always
        on_start: never

CHANGELOG.md

0 → 100644
+73 −0
Numéro de ligne d'origine Numéro de ligne de diff Ligne de diff
3.1.1 (2022-11-10)
==================

Bugfixes
--------

- Fix a crash caused by processing metrics for Matrix events. ([\#869](https://github.com/matrix-org/matrix-appservice-discord/issues/869))


3.1.0 (2022-11-03)
==================

Features
--------

- Adds a config value, in order to disable forwarding room topic changes from Matrix to Discord (`disableRoomTopicNotifications`, false by default). ([\#836](https://github.com/matrix-org/matrix-appservice-discord/issues/836))


Bugfixes
--------

- Include the domain name in the regular expression. ([\#834](https://github.com/matrix-org/matrix-appservice-discord/issues/834))
- Remove usage of unreliable field `age` on events, allowing the bridge to work with non-Synapse homeserver implementations. ([\#842](https://github.com/matrix-org/matrix-appservice-discord/issues/842))
- Prevent crashes when handling messages sent to voice channels. ([\#858](https://github.com/matrix-org/matrix-appservice-discord/issues/858))


3.0.0 (2022-08-12)
==================

Bugfixes
--------

- Make sure we don't lose errors thrown when checking usage limits. ([\#823](https://github.com/matrix-org/matrix-appservice-discord/issues/823))
- Fix Docker instances not starting due to being unable to load a dynamic library in the latest unstable image. ([\#828](https://github.com/matrix-org/matrix-appservice-discord/issues/828))
- Remove matrix.to hyperlinks when relaying non-Discord user mentions to Discord.
  Fix mentioning Matrix users in Discord. ([\#829](https://github.com/matrix-org/matrix-appservice-discord/issues/829))


Deprecations and Removals
-------------------------

- Minimum required Node.js version is now 16. ([\#825](https://github.com/matrix-org/matrix-appservice-discord/issues/825))


Internal Changes
----------------

- Remove unused variables. ([\#657](https://github.com/matrix-org/matrix-appservice-discord/issues/657))
- Add workflow for building docker images, and push new docker images to ghcr.io. ([\#826](https://github.com/matrix-org/matrix-appservice-discord/issues/826))
- Remove `git config` workaround to pull a dependency from github.com. ([\#830](https://github.com/matrix-org/matrix-appservice-discord/issues/830))


2.0.0 (2022-08-05)
==================

Improved Documentation
----------------------

- Update `CONTRIBUTING.md` guide to reference the newly-updated guide for all of the matrix.org bridge repos. ([\#794](https://github.com/matrix-org/matrix-appservice-discord/issues/794))


Deprecations and Removals
-------------------------

- Node.JS 12 is now unsupported, please upgrade to Node.JS 14 or later. Node.JS 16 becomes the new default version. ([\#811](https://github.com/matrix-org/matrix-appservice-discord/issues/811))


Internal Changes
----------------

- Add automatic changelog generation via [Towncrier](https://github.com/twisted/towncrier). ([\#787](https://github.com/matrix-org/matrix-appservice-discord/issues/787))
- Use `yarn` instead of `npm` for package management and scripts. ([\#796](https://github.com/matrix-org/matrix-appservice-discord/issues/796))
- Add new CI workflow to check for signoffs. ([\#818](https://github.com/matrix-org/matrix-appservice-discord/issues/818))

CONTRIBUTING.md

0 → 100644
+38 −0
Numéro de ligne d'origine Numéro de ligne de diff Ligne de diff
Hi there! Please read the [CONTRIBUTING.md](https://github.com/matrix-org/matrix-appservice-bridge/blob/develop/CONTRIBUTING.md) guide for all matrix.org bridge
projects.

## matrix-appservice-discord Guidelines

* Discussion of ideas for the bridge and work items should be in [#discord:half-shot.uk](https://matrix.to/#/#discord:half-shot.uk).
* Everything submitted as a PR should have at least one test, the only exception being non-code items.

## Overview of the Bridge

The bridge runs as a standalone server that connects to both the Discord API
network and a local Matrix homeserver over the [application service
protocol](https://matrix.org/docs/spec/application_service/unstable.html).
Primarily it syncs events and users from Matrix to Discord and vice versa.

While the bridge is constantly evolving and we can't keep this section updated
with each component, we follow the principle of handler and processor classes
and each part of the functionality of the bridge will be in a seperate class.
For example, the processing of Matrix events destined for Discord are handled
inside the `MatrixEventProcessor` class.

## Setting up

* You will need to [setup the bridge](https://github.com/Half-Shot/matrix-appservice-discord/tree/develop#setup-the-bridge) similarly to how we describe,
  but you should setup a homeserver locally on your development machine. We would recommend [Synapse](https://github.com/matrix-org/synapse).
* The bridge uses `yarn` for dependency management and package scripts instead of `npm`.
  For details, view the full setup instructions in the [README](README.md#set-up-the-bridge).

## Testing

CI will lint and test your code automatically,
but you can save yourself some time by checking locally before submitting code.
Refer to the main matrix.org bridge contributing guide for instructions on how to
[lint](https://github.com/matrix-org/matrix-appservice-bridge/blob/develop/CONTRIBUTING.md#%EF%B8%8F-code-style) and
[test](https://github.com/matrix-org/matrix-appservice-bridge/blob/develop/CONTRIBUTING.md#-tests--ci).

Please bear in mind that you will need to cover the whole, or a reasonable
degree of your code. You can check to see if you have with `yarn coverage`.
+5 −5
Numéro de ligne d'origine Numéro de ligne de diff Ligne de diff
FROM node:alpine AS BUILD
FROM node:16-slim AS BUILD
COPY . /tmp/src
# install some dependencies needed for the build process
RUN apk add --no-cache -t build-deps make gcc g++ python ca-certificates libc-dev wget
RUN apt update && apt install -y build-essential make gcc g++ python3 ca-certificates libc-dev wget git

RUN cd /tmp/src \
    && npm install \
    && npm run build
    && yarn

FROM node:alpine
FROM node:16-slim
ENV NODE_ENV=production
COPY --from=BUILD /tmp/src/build /build
COPY --from=BUILD /tmp/src/config /config
+41 −13
Numéro de ligne d'origine Numéro de ligne de diff Ligne de diff
# Matrix Discord Bridge

A bridge between [Matrix](http://matrix.org/) and [Discord](https://discordapp.com/).
A bridge between [Matrix](http://matrix.org/) and [Discord](https://discord.com/).
Currently the bridge is in **Beta** and quite usable for everyday
bridging, with one or two bugs cropping up.

![Screenshot of Riot and Discord working together](screenshot.png)
![Screenshot of Element and Discord working together](screenshot.png)


## Helping out


[![Build Status](https://travis-ci.org/Half-Shot/matrix-appservice-discord.svg?branch=develop)](https://travis-ci.org/Half-Shot/matrix-appservice-discord)
[![Docker Automated build](https://img.shields.io/docker/builds/halfshot/matrix-appservice-discord.svg)](https://hub.docker.com/r/halfshot/matrix-appservice-discord)
[![#discord:half-shot.uk](https://img.shields.io/matrix/discord:half-shot.uk.svg?server_fqdn=matrix.half-shot.uk&label=%23discord:half-shot.uk&logo=matrix)](https://matrix.to/#/#discord:half-shot.uk)

### PRs
@@ -19,19 +20,24 @@ about any neat ideas you might have. If you are going to make a change, please m

### Issues
You can also file bug reports/ feature requests on Github Issues which also helps a ton. Please remember to include logs.
Please also be aware that this is an unoffical project worked on in my (Half-Shot) spare time.
Please also be aware that this is an unoffical project worked on in our spare time.

## Setting up

The bridge has been tested against the [Synapse](https://github.com/matrix-org/synapse) homeserver, although any homeserver
that implements the [AS API](https://matrix.org/docs/spec/application_service/r0.1.0.html) should work with this bridge.

The bridge supports any version of Node.js >= v10.X, including all [current releases](https://nodejs.org/en/about/releases/).
The bridge supports any version of Node.js between v14.X - v18.X. View the [releases](https://nodejs.org/en/about/releases/) for more details.

The bridge uses Yarn for dependency management and package scripts.
For the time being, **only Yarn Classic / v1 is supported.** To install it, follow [these instructions](https://classic.yarnpkg.com/en/docs/install).

If you already have Yarn 2+ installed, you may configure just this project to use Yarn Classic
by running ``yarn set version classic`` in the directory where you cloned this repository.

### Set up the bridge

* Run ``npm install`` to grab the dependencies.
* Run ``npm run build`` to build the typescript into javascript.
* Run ``yarn`` to grab the dependencies.
* Copy ``config/config.sample.yaml`` to ``config.yaml`` and edit it to reflect your setup.
  * Note that you are expected to set ``domain`` and ``homeserverURL`` to your **public** host name.
  While localhost would work, it does not resolve correctly with Webhooks/Avatars.
@@ -79,22 +85,26 @@ docker build -t halfshot/matrix-appservice-discord .
# Run the container
docker run -v /matrix-appservice-discord:/data -p 9005:9005 halfshot/matrix-appservice-discord
```
#### Metrics

The bridge supports reporting metrics via Prometheus. You can configure metrics support in the config
file. The metrics will be reported under the URL provided in the registration file, on the `/metrics` endpoint.

#### 3PID Protocol Support

This bridge support searching for rooms within networks via the 3pid system
used in clients like [Riot](https://riot.im). Any new servers/guilds you bridge
should show up in the network list on Riot and other clients.
used in clients like [Element](https://element.io). Any new servers/guilds you bridge
should show up in the network list on Element and other clients.

### Setting up Discord

* Create a new application via https://discordapp.com/developers/applications
* Create a new application via https://discord.com/developers/applications
* Make sure to create a bot user. Fill in ``config.yaml``
* Run ``npm run addbot`` to get a authorisation link.
* Run ``yarn addbot`` to get a authorisation link.
* Give this link to owners of the guilds you plan to bridge.
* Finally, you can join a room with ``#_discord_guildid_channelid``
  * These can be taken from the url ("/$GUILDID/$CHANNELID") when you are in a channel.
  * Riot (and other clients with third party protocol support) users can directly join channels from the room directory.
  * Element (and other clients with third party protocol support) users can directly join channels from the room directory.
* You can use Webhooks to make messages relayed by the bridge not nested by the bot user. This will also display the avatar of the user speaking on matrix with their messages.
  * The bot should create this automatically, but if not perform the following:
    * Enable ``Manage Webhooks`` on the role added by the bot.
@@ -103,10 +113,28 @@ should show up in the network list on Riot and other clients.
### Running the Bridge

* For the bot to appear online on Discord you need to run the bridge itself.
* ``npm start``
* ``yarn start``
* Particular configuration keys can be overridden by defining corresponding environment variables. For instance, `auth.botToken` can be set with `APPSERVICE_DISCORD_AUTH_BOT_TOKEN`.

[Howto](./docs/howto.md)

## End User Documentation

### Bridging a Room

You must get an authorization link from bridge owner. You must be a server admin or get
help from server admin on Discord side.

* Invite the Matrix side bot to your room and wait for it to join.
* On Discord side use the authorization link to invite bot to Discord server.
* Find out a serverid and channelid for your server/channel you want to bridge. Search the web for instructions.
* In Matrix room give command ``!discord bridge ServerID ChannelID``
* The bridge will ask for confirmation from the Discord server admins to complete the bridge. Once approved, you're all set.

### Unbridging a Room

* In Matrix room give command ``!discord unbridge``

## Features and Roadmap
In a vague order of what is coming up next

changelog.d/git.keep

0 → 100644
+0 −0
Numéro de ligne d'origine Numéro de ligne de diff Ligne de diff
Numéro de ligne d'origine Numéro de ligne de diff Ligne de diff
# This is a sample of the config file showing all avaliable options.
# This is a sample of the config file showing all available options.
# Where possible we have documented what they do, and all values are the
# default values.

bridge:
  # Domain part of the bridge, e.g. matrix.org
  domain: "localhost"
  # This should be your publically facing URL because Discord may use it to
  # This should be your publicly-facing URL because Discord may use it to
  # fetch media from the media store.
  homeserverUrl: "http://localhost:8008"
  # The TCP port on which the appservice runs on.
  port: 9005
  # Interval at which to process users in the 'presence queue'. If you have
  # 5 users, one user will be processed every 500 milliseconds according to the
  # value below. This has a minimum value of 250.
@@ -21,6 +23,9 @@ bridge:
  disableTypingNotifications: false
  # Disable deleting messages on Discord if a message is redacted on Matrix.
  disableDeletionForwarding: false
  # Disable portal bridging, where Matrix users can search for unbridged Discord
  # rooms on their Matrix server.
  disablePortalBridging: false
  # Enable users to bridge rooms using !discord commands. See
  # https://t2bot.io/discord for instructions.
  enableSelfServiceBridging: false
@@ -29,10 +34,24 @@ bridge:
  disableReadReceipts: false
  # Disable Join Leave echos from matrix
  disableJoinLeaveNotifications: false
  # Disable Invite echos from matrix
  disableInviteNotifications: false
  # Disable Room Topic echos from matrix
  disableRoomTopicNotifications: false
  # Auto-determine the language of code blocks (this can be CPU-intensive)
  determineCodeLanguage: false
  # MXID of an admin user that will be PMd if the bridge experiences problems. Optional
  adminMxid: '@admin:localhost'
  # The message to send to the bridge admin if the Discord token is not valid
  invalidTokenMessage: 'Your Discord bot token seems to be invalid, and the bridge cannot function. Please update it in your bridge settings and restart the bridge'
# Authentication configuration for the discord bot.
auth:
  # This MUST be a string (wrapped in quotes)
  clientID: "12345"
  botToken: "foobar"
  # You must enable "Privileged Gateway Intents" in your bot settings on discord.com (e.g. https://discord.com/developers/applications/12345/bot)
  # for this to work
  usePrivilegedIntents: false
logging:
  # What level should the logger output to the console at.
  console: "warn" #silly, verbose, info, http, warn, error, silent
@@ -48,8 +67,6 @@ logging:
      enable:
        - "DiscordBot"
database:
  userStorePath: "user-store.db"
  roomStorePath: "room-store.db"
  # You may either use SQLite or Postgresql for the bridge database, which contains
  # important mappings for events and user puppeting configurations.
  # Use the filename option for SQLite, or connString for Postgresql.
@@ -78,19 +95,28 @@ channel:
       unsetRoomAlias: true
       # Remove the room from the directory.
       unlistFromDirectory: true
       # Set the room to be unavaliable for joining without an invite.
       # Set the room to be unavailable for joining without an invite.
       setInviteOnly: true
       # Make all the discord users leave the room.
       ghostsLeave: true
limits:
    # Delay in milliseconds between discord users joining a room.
    roomGhostJoinDelay: 6000
    # Delay in milliseconds before sending messages to discord to avoid echos.
    # (Copies of a sent message may arrive from discord before we've
    # Lock timeout in milliseconds before sending messages to discord to avoid
    # echos. Default is rather high as the lock will most likely time out
    # before anyways.
    # echos = (Copies of a sent message may arrive from discord before we've
    # fininished handling it, causing us to echo it back to the room)
    discordSendDelay: 750
    discordSendDelay: 1500
    # Set a maximum of rooms to be bridged.
    # roomCount: 20
ghosts:
    # Pattern for the ghosts nick, available is :nick, :username, :tag and :id
    nickPattern: ":nick"
    # Pattern for the ghosts username, available is :username, :tag and :id
    usernamePattern: ":username#:tag"
# Prometheus-compatible metrics endpoint
metrics:
    enable: false
    port: 9001
    host: "127.0.0.1"
Numéro de ligne d'origine Numéro de ligne de diff Ligne de diff
@@ -10,6 +10,8 @@ properties:
            type: "string"
          homeserverUrl:
            type: "string"
          port:
            type: "number"
          presenceInterval:
            type: "number"
          disablePresence:
@@ -18,10 +20,28 @@ properties:
            type: "boolean"
          disableDeletionForwarding:
            type: "boolean"
          disablePortalBridging:
            type: "boolean"
          enableSelfServiceBridging:
            type: "boolean"
          disableReadReceipts:
            type: "boolean"
          disableJoinLeaveNotifications:
            type: "boolean"
          disableInviteNotifications:
            type: "boolean"
          disableRoomTopicNotifications:
            type: "boolean"
          userActivity:
            type: "object"
            required: ["minUserActiveDays", "inactiveAfterDays"]
            properties:
              minUserActiveDays:
                type: "number"
              inactiveAfterDays:
                type: "number"
          userLimit:
            type: "number"
    auth:
        type: "object"
        required: ["botToken", "clientID"]
@@ -30,6 +50,8 @@ properties:
            type: "string"
          botToken:
            type: "string"
          usePrivilegedIntents:
            type: "boolean"
    logging:
        type: "object"
        properties:
@@ -87,6 +109,8 @@ properties:
                type: "number"
            discordSendDelay:
                type: "number"
            roomCount:
                type: "number"
    channel:
        type: "object"
        properties:
@@ -116,3 +140,12 @@ properties:
                type: "string"
            usernamePattern:
                type: "string"
    metrics:
        type: "object"
        properties:
            enable:
                type: "boolean"
            port:
                type: "number"
            host:
                type: "string"
+11 −0
Numéro de ligne d'origine Numéro de ligne de diff Ligne de diff
# 1.0 Migration (from 0.5.1 or lower)

If you have been linked here, there is an issue with your config on your bridge.

Please follow the following steps:

1. If you have just created a new install OR were previously running 0.5.X,
   please remove `roomDataStore` and `userDataStore` from your config file.
2. If this is a existing install but you have not run 0.5.X (0.4.X or lower),
   please downgrade to 0.5.X to migrate your database across and then run
   this version of the bridge again.
+5 −5
Numéro de ligne d'origine Numéro de ligne de diff Ligne de diff
@@ -7,19 +7,19 @@ The default format for room aliases (which are automatically resolved, whether t

You can find these on discord in the browser where:

``https://discordapp.com/channels/282616294245662720/282616372591329281``
``https://discord.com/channels/282616294245662720/282616372591329281``

is formatted as https://discordapp.com/channels/``guildid``/``channelid``
is formatted as https://discord.com/channels/``guildid``/``channelid``

### Set privileges on bridge managed rooms

* The ``adminme`` script is provided to set Admin/Moderator or any other custom power level to a specific user.
* e.g. To set Alice to Admin on her ``example.com`` HS on default config. (``config.yaml``)
  * ``npm run adminme -- -r '!AbcdefghijklmnopqR:example.com' -u '@Alice:example.com' -p '100'``
  * Run ``npm run adminme -- -h`` for usage.
  * ``yarn adminme -r '!AbcdefghijklmnopqR:example.com' -u '@Alice:example.com' -p '100'``
  * Run ``yarn adminme -h`` for usage.

Please note that `!AbcdefghijklmnopqR:example.com` is the internal room id and will always begin with `!`.
You can find this internal id in the room settings in Riot.
You can find this internal id in the room settings in Element.

### Migrate to postgres from sqlite
* Stop the bridge.
Numéro de ligne d'origine Numéro de ligne de diff Ligne de diff
@@ -13,8 +13,8 @@ has the benefits of (not all of these may be implemented):
Discord is currently __not__ offering any way to authenticate on behalf
of a user _and_ interact on their behalf. The OAuth system does not allow
remote access beyond reading information about the users. While [developers have
expressed a wish for this](https://feedback.discordapp.com/forums/326712-discord-dream-land/suggestions/16753837-support-custom-clients)
,it is my opinion that Discord are unlikely to support this any time soon. With
expressed a wish for this](https://feedback.discordapp.com/forums/326712-discord-dream-land/suggestions/16753837-support-custom-clients),
it is my opinion that Discord are unlikely to support this any time soon. With
all this said, Discord will not be banning users or the bridge itself for acting
on the behalf of the user.

@@ -39,5 +39,5 @@ You should be able to puppet with 2FA enabled on your account

* Follow https://discordhelp.net/discord-token to find your discord token.
* Stop the bridge, if it is running.
* Run `npm run usertool -- --add` and follow the instructions.
* Run `yarn usertool --add` and follow the instructions.
* If all is well, you can start the bridge.

package-lock.json

supprimé100644 → 0
+0 −3334

Fichier supprimé.

La taille limite d'aperçu a été dépassée, l'affichage des modifications a donc été réduit.

+47 −33
Numéro de ligne d'origine Numéro de ligne de diff Ligne de diff
{
  "name": "matrix-appservice-discord",
  "version": "0.4.0",
  "version": "3.1.1",
  "description": "A bridge between Matrix and Discord",
  "main": "discordas.js",
  "engines": {
    "npm": "please-use-yarn",
    "node": ">=16 <=18"
  },
  "scripts": {
    "test": "npm run-script build && mocha --opts test/mocha.opts build/test/config.js build/test",
    "lint": "tslint --project ./tsconfig.json -t stylish",
    "coverage": "istanbul --include-all-sources cover -x build/src/discordas.js _mocha -- build/test/ -R spec",
    "test": "mocha -r ts-node/register test/config.ts test/test_*.ts test/**/test_*.ts",
    "lint": "eslint -c .eslintrc --max-warnings 200 src/**/*.ts test/**/*.ts",
    "coverage": "tsc && nyc mocha build/test/config.js build/test",
    "build": "tsc",
    "start": "npm run-script build && node ./build/src/discordas.js -p 9005 -c config.yaml",
    "postinstall": "yarn build",
    "start": "yarn build && node ./build/src/discordas.js -c config.yaml",
    "debug": "yarn build && node --inspect ./build/src/discordas.js -c config.yaml",
    "addbot": "node ./build/tools/addbot.js",
    "adminme": "node ./build/tools/adminme.js",
    "usertool": "node ./build/tools/userClientTools.js",
@@ -34,37 +40,45 @@
  },
  "homepage": "https://github.com/Half-Shot/matrix-appservice-discord#readme",
  "dependencies": {
    "@types/p-queue": "^3.0.0",
    "better-sqlite3": "^5.0.1",
    "command-line-args": "^4.0.1",
    "command-line-usage": "^4.1.0",
    "discord-markdown": "^2.0.0",
    "discord.js": "^11.4.2",
    "@deurstann/matrix-discord-parser": "1.10.7",
    "better-discord.js": "github:matrix-org/better-discord.js#5024781db755259e88abe915630b7d5b3ba5f48f",
    "better-sqlite3": "^7.1.0",
    "command-line-args": "^5.1.1",
    "command-line-usage": "^6.1.0",
    "escape-html": "^1.0.3",
    "escape-string-regexp": "^1.0.5",
    "js-yaml": "^3.13.1",
    "matrix-appservice-bridge": "matrix-org/matrix-appservice-bridge#8a7288edf1d1d1d1395a83d330d836d9c9bf1e76",
    "mime": "^1.6.0",
    "moment": "^2.22.2",
    "node-html-parser": "^1.1.11",
    "p-queue": "^3.0.0",
    "pg-promise": "^8.5.1",
    "tslint": "^5.11.0",
    "typescript": "^3.1.3",
    "winston": "^3.0.0",
    "winston-daily-rotate-file": "^3.3.0"
    "escape-string-regexp": "^4.0.0",
    "js-yaml": "^3.14.0",
    "marked": "^1.2.2",
    "matrix-appservice-bridge": "^5.0.0",
    "mime": "^2.4.6",
    "p-queue": "^6.4.0",
    "pg-promise": "^10.5.6",
    "prom-client": "^12.0.0",
    "uuid": "^8.3.1",
    "winston": "^3.2.1",
    "winston-daily-rotate-file": "^4.5.0"
  },
  "devDependencies": {
    "@types/chai": "^3.4.35",
    "@types/mocha": "^5.2.5",
    "@types/node": "^10.12.0",
    "@types/sqlite3": "^3.1.3",
    "chai": "^3.5.0",
    "eslint": "^3.8.1",
    "istanbul": "^0.4.5",
    "mocha": "^5.2.0",
    "@istanbuljs/nyc-config-typescript": "^1.0.1",
    "@types/better-sqlite3": "^5.4.1",
    "@types/chai": "^4.2.11",
    "@types/command-line-args": "^5.0.0",
    "@types/express": "^4.17.9",
    "@types/js-yaml": "^3.12.4",
    "@types/marked": "^1.1.0",
    "@types/mime": "^2.0.2",
    "@types/mocha": "^7.0.2",
    "@types/node": "^14",
    "@typescript-eslint/eslint-plugin": "^5.4.0",
    "@typescript-eslint/parser": "^5.4.0",
    "chai": "^4.2.0",
    "eslint": "^7.4.0",
    "mocha": "^8.0.1",
    "nyc": "^15.1.0",
    "proxyquire": "^1.7.11",
    "source-map-support": "^0.5.9",
    "why-is-node-running": "^2.0.3"
    "source-map-support": "^0.5.19",
    "ts-node": "^8.10.2",
    "typescript": "^4.2.3",
    "why-is-node-running": "^2.2.0"
  }
}

pyproject.toml

0 → 100644
+30 −0
Numéro de ligne d'origine Numéro de ligne de diff Ligne de diff
[tool.towncrier]
    # The name of your Python package
    filename = "CHANGELOG.md"
    directory = "changelog.d"
    issue_format = "[\\#{issue}](https://github.com/matrix-org/matrix-appservice-discord/issues/{issue})"

    [[tool.towncrier.type]]
        directory = "feature"
        name = "Features"
        showcontent = true

    [[tool.towncrier.type]]
        directory = "bugfix"
        name = "Bugfixes"
        showcontent = true

    [[tool.towncrier.type]]
        directory = "doc"
        name = "Improved Documentation"
        showcontent = true

    [[tool.towncrier.type]]
        directory = "removal"
        name = "Deprecations and Removals"
        showcontent = true

    [[tool.towncrier.type]]
        directory = "misc"
        name = "Internal Changes"
        showcontent = true

scripts/changelog.sh

0 → 100755
+3 −0
Numéro de ligne d'origine Numéro de ligne de diff Ligne de diff
#!/bin/bash
VERSION=`python3 -c "import json; f = open('./package.json', 'r'); v = json.loads(f.read())['version']; f.close(); print(v)"`
towncrier build --version $VERSION $1
+46 −0
Numéro de ligne d'origine Numéro de ligne de diff Ligne de diff
#!/usr/bin/env bash
#
# A script which checks that an appropriate news file has been added on this
# branch.


echo -e "+++ \033[32mChecking newsfragment\033[m"

set -e

# make sure that origin/develop is up to date
git remote set-branches --add origin develop
git fetch -q origin develop

pr="$PULL_REQUEST_NUMBER"

# Print a link to the contributing guide if the user makes a mistake
CONTRIBUTING_GUIDE_TEXT="!! Please see the contributing guide for help writing your changelog entry:
https://github.com/matrix-org/matrix-appservice-bridge/blob/develop/CONTRIBUTING.md#%EF%B8%8F-pull-requests"

# If check-newsfragment returns a non-zero exit code, print the contributing guide and exit
python3 -m towncrier.check --compare-with=origin/develop || (echo -e "$CONTRIBUTING_GUIDE_TEXT" >&2 && exit 1)

echo
echo "--------------------------"
echo

matched=0
for f in $(git diff --diff-filter=d --name-only FETCH_HEAD... -- changelog.d | grep -xv changelog.d/git.keep); do
    # check that any added newsfiles on this branch end with a full stop.
    lastchar=$(tr -d '\n' < "$f" | tail -c 1)
    if [ "$lastchar" != '.' ] && [ "$lastchar" != '!' ]; then
        echo -e "\e[31mERROR: newsfragment $f does not end with a '.' or '!'\e[39m" >&2
        echo -e "$CONTRIBUTING_GUIDE_TEXT" >&2
        exit 1
    fi

    # see if this newsfile corresponds to the right PR
    [[ -n "$pr" && "$f" == changelog.d/"$pr".* ]] && matched=1
done

if [[ -n "$pr" && "$matched" -eq 0 ]]; then
    echo -e "\e[31mERROR: Did not find a news fragment with the right number: expected changelog.d/$pr.*.\e[39m" >&2
    echo -e "$CONTRIBUTING_GUIDE_TEXT" >&2
    exit 1
fi
+659 −212

Fichier modifié.

La taille limite d'aperçu a été dépassée, l'affichage des modifications a donc été réduit.

Numéro de ligne d'origine Numéro de ligne de diff Ligne de diff
@@ -14,13 +14,13 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import * as Discord from "discord.js";
import * as Discord from "better-discord.js";
import { DiscordBot } from "./bot";
import { Util } from "./util";
import { DiscordBridgeConfig } from "./config";
import { Bridge } from "matrix-appservice-bridge";
import { DiscordBridgeConfig, DiscordBridgeConfigChannelDeleteOptions } from "./config";
import { Log } from "./log";
import { DbRoomStore, IRoomStoreEntry } from "./db/roomstore";
import { Appservice } from "matrix-bot-sdk";

const log = new Log("ChannelSync");

@@ -58,7 +58,7 @@ export interface IChannelState {

export class ChannelSyncroniser {
    constructor(
        private bridge: Bridge,
        private bridge: Appservice,
        private config: DiscordBridgeConfig,
        private bot: DiscordBot,
        private roomStore: DbRoomStore,
@@ -81,7 +81,7 @@ export class ChannelSyncroniser {
    public async OnGuildUpdate(guild: Discord.Guild, force = false) {
        log.verbose(`Got guild update for guild ${guild.id}`);
        const channelStates: IChannelState[] = [];
        for (const [_, channel] of guild.channels) {
        for (const [_, channel] of guild.channels.cache) {
            if (channel.type !== "text") {
                continue; // not supported for now
            }
@@ -105,6 +105,20 @@ export class ChannelSyncroniser {
        }
    }

    public async OnUnbridge(channel: Discord.Channel, roomId: string) {
        try {
            const entry = (await this.roomStore.getEntriesByMatrixId(roomId))[0];
            const opts = new DiscordBridgeConfigChannelDeleteOptions();
            opts.namePrefix = null;
            opts.topicPrefix = null;
            opts.ghostsLeave = true;
            await this.handleChannelDeletionForRoom(channel as Discord.TextChannel, roomId, entry);
            log.info(`Channel ${channel.id} has been unbridged.`);
        } catch (e) {
            log.error(`Failed to unbridge channel from room: ${e}`);
        }
    }

    public async OnDelete(channel: Discord.Channel) {
        if (channel.type !== "text") {
            log.info(`Channel ${channel.id} was deleted but isn't a text channel, so ignoring.`);
@@ -130,7 +144,7 @@ export class ChannelSyncroniser {
    }

    public async OnGuildDelete(guild: Discord.Guild) {
        for (const [_, channel] of guild.channels) {
        for (const [_, channel] of guild.channels.cache) {
            try {
                await this.OnDelete(channel);
            } catch (e) {
@@ -150,6 +164,39 @@ export class ChannelSyncroniser {
        return rooms.map((room) => room.matrix!.getId() as string);
    }

    public async GetAliasFromChannel(channel: Discord.Channel): Promise<string | null> {
        let rooms: string[] = [];
        try {
            rooms = await this.GetRoomIdsFromChannel(channel);
        } catch (err) { } // do nothing, our rooms array will just be empty
        let fallbackAlias = "";
        for (const room of rooms) {
            try {
                const al = (await this.bridge.botIntent.underlyingClient.getRoomStateEvent(
                    room,
                    "m.room.canonical_alias",
                    "")
                    ).alias;
                if (al) {
                    if (this.bridge.isNamespacedAlias(al)) {
                        fallbackAlias = al;
                    } else {
                        return al; // we are done, we found an alias
                    }
                }
            } catch (err) { } // do nothing, as if we error we just roll over to the next entry
        }
        if (fallbackAlias) {
            return fallbackAlias;
        }
        const guildChannel = channel as Discord.TextChannel;
        if (!guildChannel.guild) {
            return null; // we didn't pass a guild, so we have no way of bridging this room, thus no alias
        }
        // at last, no known canonical aliases and we are a guild....so we know an alias!
        return this.bridge.getAliasForSuffix(`${guildChannel.guild.id}_${channel.id}`);
    }

    public async GetChannelUpdateState(channel: Discord.TextChannel, forceUpdate = false): Promise<IChannelState> {
        log.verbose(`State update request for ${channel.id}`);
        const channelState: IChannelState = Object.assign({}, DEFAULT_CHANNEL_STATE, {
@@ -171,7 +218,9 @@ export class ChannelSyncroniser {
        const icon = channel.guild.icon;
        let iconUrl: string | null = null;
        if (icon) {
            iconUrl = `https://cdn.discordapp.com/icons/${channel.guild.id}/${icon}.png`;
            // if discord prefixes their icon hashes with "a_" it means that they are animated
            const animatedIcon = icon.startsWith("a_");
            iconUrl = `https://cdn.discordapp.com/icons/${channel.guild.id}/${icon}.${animatedIcon ? "gif" : "png"}`;
        }
        remoteRooms.forEach((remoteRoom) => {
            const mxid = remoteRoom.matrix!.getId();
@@ -214,7 +263,7 @@ export class ChannelSyncroniser {
    }

    private async ApplyStateToChannel(channelsState: IChannelState) {
        const intent = this.bridge.getIntent();
        const intent = this.bridge.botIntent;
        for (const channelState of channelsState.mxChannels) {
            let roomUpdated = false;
            const remoteRoom = (await this.roomStore.getEntriesByMatrixId(channelState.mxid))[0];
@@ -224,14 +273,24 @@ export class ChannelSyncroniser {
            }
            if (channelState.name !== null) {
                log.verbose(`Updating channelname for ${channelState.mxid} to "${channelState.name}"`);
                await intent.setRoomName(channelState.mxid, channelState.name);
                await intent.underlyingClient.sendStateEvent(
                    channelState.mxid,
                    "m.room.name",
                    "",
                    { name: channelState.name },
                );
                remoteRoom.remote.set("discord_name", channelState.name);
                roomUpdated = true;
            }

            if (channelState.topic !== null) {
                log.verbose(`Updating channeltopic for ${channelState.mxid} to "${channelState.topic}"`);
                await intent.setRoomTopic(channelState.mxid, channelState.topic);
                await intent.underlyingClient.sendStateEvent(
                    channelState.mxid,
                    "m.room.topic",
                    "",
                    { topic: channelState.topic },
                );
                remoteRoom.remote.set("discord_topic", channelState.topic);
                roomUpdated = true;
            }
@@ -239,14 +298,21 @@ export class ChannelSyncroniser {
            if (channelState.iconUrl !== null && channelState.iconId !== null) {
                log.verbose(`Updating icon_url for ${channelState.mxid} to "${channelState.iconUrl}"`);
                if (channelsState.iconMxcUrl === null) {
                    const iconMxc = await Util.UploadContentFromUrl(
                        channelState.iconUrl,
                        intent,
                    const file = await Util.DownloadFile(channelState.iconUrl);
                    const iconMxc = await this.bridge.botIntent.underlyingClient.uploadContent(
                        file.buffer,
                        file.mimeType,
                        channelState.iconId,
                    );
                    channelsState.iconMxcUrl = iconMxc.mxcUrl;
                }
                await intent.setRoomAvatar(channelState.mxid, channelsState.iconMxcUrl);
                    channelsState.iconMxcUrl = iconMxc;
                }
                await intent.underlyingClient.sendStateEvent(
                    channelState.mxid,
                    "m.room.avatar",
                    "",
                    // TODO: "info" object for avatar
                    { url: channelsState.iconMxcUrl },
                );
                remoteRoom.remote.set("discord_iconurl", channelState.iconUrl);
                remoteRoom.remote.set("discord_iconurl_mxc", channelsState.iconMxcUrl);
                roomUpdated = true;
@@ -254,7 +320,12 @@ export class ChannelSyncroniser {

            if (channelState.removeIcon) {
                log.verbose(`Clearing icon_url for ${channelState.mxid}`);
                await intent.setRoomAvatar(channelState.mxid, null);
                await intent.underlyingClient.sendStateEvent(
                    channelState.mxid,
                    "m.room.avatar",
                    "",
                    {  },
                );
                remoteRoom.remote.set("discord_iconurl", null);
                remoteRoom.remote.set("discord_iconurl_mxc", null);
                roomUpdated = true;
@@ -269,19 +340,21 @@ export class ChannelSyncroniser {
    private async handleChannelDeletionForRoom(
        channel: Discord.TextChannel,
        roomId: string,
        entry: IRoomStoreEntry): Promise<void> {
        entry: IRoomStoreEntry,
        overrideOptions?: DiscordBridgeConfigChannelDeleteOptions): Promise<void> {
        log.info(`Deleting ${channel.id} from ${roomId}.`);
        const intent = await this.bridge.getIntent();
        const options = this.config.channel.deleteOptions;
        const intent = this.bridge.botIntent;
        const client = this.bridge.botClient;
        const options = overrideOptions || this.config.channel.deleteOptions;
        const plumbed = entry.remote!.get("plumbed");

        await this.roomStore.upsertEntry(entry);
        if (options.ghostsLeave) {
            for (const member of channel.members.array()) {
                try {
                    const mIntent = await this.bot.GetIntentFromDiscordMember(member);
                    mIntent.leave(roomId);
                    log.info(`${member.id} left ${roomId}.`);
                    const mIntent = this.bot.GetIntentFromDiscordMember(member);
                    await client.leaveRoom(roomId);
                    log.verbose(`${member.id} left ${roomId}.`);
                } catch (e) {
                    log.warn(`Failed to make ${member.id} leave `);
                }
@@ -289,18 +362,28 @@ export class ChannelSyncroniser {
        }
        if (options.namePrefix) {
            try {
                const name = await intent.getClient().getStateEvent(roomId, "m.room.name");
                const name = await client.getRoomStateEvent(roomId, "m.room.name", "");
                name.name = options.namePrefix + name.name;
                await intent.getClient().setRoomName(roomId, name.name);
                await client.sendStateEvent(
                    roomId,
                    "m.room.name",
                    "",
                    name,
                );
            } catch (e) {
                log.error(`Failed to set name of room ${roomId} ${e}`);
            }
        }
        if (options.topicPrefix) {
            try {
                const topic = await intent.getClient().getStateEvent(roomId, "m.room.topic");
                const topic = await client.getRoomStateEvent(roomId, "m.room.topic", "");
                topic.topic = options.topicPrefix + topic.topic;
                await intent.getClient().setRoomTopic(roomId, topic.topic);
                await client.sendStateEvent(
                    roomId,
                    "m.room.topic",
                    "",
                    topic,
                );
            } catch (e) {
                log.error(`Failed to set topic of room ${roomId} ${e}`);
            }
@@ -310,11 +393,15 @@ export class ChannelSyncroniser {
            if (options.unsetRoomAlias) {
                try {
                    const alias = `#_${entry.remote!.roomId}:${this.config.bridge.domain}`;
                    const canonicalAlias = await intent.getClient().getStateEvent(roomId, "m.room.canonical_alias");
                    const canonicalAlias = await client.getRoomStateEvent(
                        roomId,
                        "m.room.canonical_alias",
                        "",
                    );
                    if (canonicalAlias.alias === alias) {
                        await intent.getClient().sendStateEvent(roomId, "m.room.canonical_alias", {});
                        await client.sendStateEvent(roomId, "m.room.canonical_alias", "", {});
                    }
                    await intent.getClient().deleteAlias(alias);
                    await client.deleteRoomAlias(alias);
                } catch (e) {
                    log.error(`Couldn't remove alias of ${roomId} ${e}`);
                }
@@ -322,7 +409,7 @@ export class ChannelSyncroniser {

            if (options.unlistFromDirectory) {
                try {
                    await intent.getClient().setRoomDirectoryVisibility(roomId, "private");
                    await client.setDirectoryVisibility(roomId, "private");
                } catch (e) {
                    log.error(`Couldn't remove ${roomId} from room directory ${e}`);
                }
@@ -331,7 +418,12 @@ export class ChannelSyncroniser {

            if (options.setInviteOnly) {
                try {
                    await intent.getClient().sendStateEvent(roomId, "m.room.join_rules", {join_role: "invite"});
                    await client.sendStateEvent(
                        roomId,
                        "m.room.join_rules",
                        "",
                        {join_role: "invite"},
                    );
                } catch (e) {
                    log.error(`Couldn't set ${roomId} to private ${e}`);
                }
@@ -339,17 +431,15 @@ export class ChannelSyncroniser {

            if (options.disableMessaging) {
                try {
                    const state = await intent.getClient().getStateEvent(roomId, "m.room.power_levels");
                    const state = await client.getRoomStateEvent(roomId, "m.room.power_levels", "");
                    state.events_default = POWER_LEVEL_MESSAGE_TALK;
                    await intent.getClient().sendStateEvent(roomId, "m.room.power_levels", state);
                    await client.sendStateEvent(roomId, "m.room.power_levels", "", state);
                } catch (e) {
                    log.error(`Couldn't disable messaging for ${roomId} ${e}`);
                }
            }
        }
        // Unlist

        // Remove entry
        await this.roomStore.removeEntriesByMatrixRoomId(roomId);
    }
}
Numéro de ligne d'origine Numéro de ligne de diff Ligne de diff
@@ -16,14 +16,12 @@ limitations under the License.

import { DiscordBridgeConfigAuth } from "./config";
import { DiscordStore } from "./store";
import { Client as DiscordClient } from "discord.js";
import { Client as DiscordClient, Intents, TextChannel } from "better-discord.js";
import { Log } from "./log";
import { Util } from "./util";
import { MetricPeg } from "./metrics";

const log = new Log("ClientFactory");

const READY_TIMEOUT = 30000;

export class DiscordClientFactory {
    private config: DiscordBridgeConfigAuth;
    private store: DiscordStore;
@@ -42,13 +40,23 @@ export class DiscordClientFactory {
        // We just need to make sure we have a bearer token.
        // Create a new Bot client.
        this.botClient = new DiscordClient({
            fetchAllMembers: true,
            fetchAllMembers: this.config.usePrivilegedIntents,
            messageCacheLifetime: 5,
            sync: true,
            ws: {
                intents: this.config.usePrivilegedIntents ? Intents.ALL : Intents.NON_PRIVILEGED,
            },
        });

        const waitPromise = new Promise((resolve, reject) => {
            this.botClient.once("shardReady", resolve);
            this.botClient.once("shardError", reject);
        });

        try {
            await this.botClient.login(this.config.botToken);
            await this.botClient.login(this.config.botToken, true);
            log.info("Waiting for shardReady signal");
            await waitPromise;
            log.info("Got shardReady signal");
        } catch (err) {
            log.error("Could not login as the bot user. This is bad!", err);
            throw err;
@@ -60,16 +68,17 @@ export class DiscordClientFactory {
        const client = new DiscordClient({
            fetchAllMembers: false,
            messageCacheLifetime: 5,
            sync: false,
            ws: {
                intents: Intents.NON_PRIVILEGED,
            },
        });

        await client.login(token);
        const id = client.user.id;

        // This can be done asynchronously, because we don't need to block to return the id.
        client.destroy().catch((err) => {
            log.warn("Failed to destroy client ", id);
        });
        await client.login(token, false);
        const id = client.user?.id;
        client.destroy();
        if (!id) {
            throw Error("Client did not have a user object, cannot determine ID");
        }
        return id;
    }

@@ -90,9 +99,11 @@ export class DiscordClientFactory {
        // TODO: Select a profile based on preference, not the first one.
        const token = await this.store.getToken(discordIds[0]);
        const client = new DiscordClient({
            fetchAllMembers: true,
            fetchAllMembers: false,
            messageCacheLifetime: 5,
            sync: true,
            ws: {
                intents: Intents.NON_PRIVILEGED,
            },
        });

        const jsLog = new Log("discord.js-ppt");
@@ -101,7 +112,7 @@ export class DiscordClientFactory {
        client.on("warn", (msg) => { jsLog.warn(msg); });

        try {
            await client.login(token);
            await client.login(token, false);
            log.verbose("Logged in. Storing ", userId);
            this.clients.set(userId, client);
            return client;
@@ -110,4 +121,19 @@ export class DiscordClientFactory {
            return this.botClient;
        }
    }

    public bindMetricsToChannel(channel: TextChannel) {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const flexChan = channel as any;
        if (flexChan._xmet_send !== undefined) {
            return;
        }
        // Prefix the real functions with _xmet_
        // eslint-disable-next-line @typescript-eslint/naming-convention
        flexChan._xmet_send = channel.send;
        channel.send = (...rest) => {
            MetricPeg.get.remoteCall("channel.send");
            return flexChan._xmet_send.apply(channel, rest);
        };
    }
}
+67 −11
Numéro de ligne d'origine Numéro de ligne de diff Ligne de diff
@@ -14,6 +14,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

const ENV_PREFIX = "APPSERVICE_DISCORD";
const ENV_KEY_SEPARATOR = "_";
const ENV_VAL_SEPARATOR = ",";

import { UserActivityTrackerConfig } from 'matrix-appservice-bridge';

/** Type annotations for config/config.schema.yaml */
export class DiscordBridgeConfig {
    public bridge: DiscordBridgeConfigBridge = new DiscordBridgeConfigBridge();
@@ -24,43 +30,85 @@ export class DiscordBridgeConfig {
    public channel: DiscordBridgeConfigChannel = new DiscordBridgeConfigChannel();
    public limits: DiscordBridgeConfigLimits = new DiscordBridgeConfigLimits();
    public ghosts: DiscordBridgeConfigGhosts = new DiscordBridgeConfigGhosts();
    public metrics: DiscordBridgeConfigMetrics = new DiscordBridgeConfigMetrics();

    /**
     * Apply a set of keys and values over the default config.
     * @param _config Config keys
     * @param newConfig Config keys
     * @param configLayer Private parameter
     */
    // tslint:disable-next-line no-any
    public ApplyConfig(newConfig: {[key: string]: any}, configLayer: any = this) {
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    public applyConfig(newConfig: {[key: string]: any}, configLayer: {[key: string]: any} = this) {
          Object.keys(newConfig).forEach((key) => {
            if ( typeof(configLayer[key]) === "object" &&
                    !Array.isArray(configLayer[key])) {
                this.ApplyConfig(newConfig[key], this[key]);
                return;
            }
            if (configLayer[key] instanceof Object && !(configLayer[key] instanceof Array)) {
                this.applyConfig(newConfig[key], configLayer[key]);
            } else {
                configLayer[key] = newConfig[key];
            }
        });
    }

    /**
     * Override configuration keys defined in the supplied environment dictionary.
     * @param environment environment variable dictionary
     * @param path private parameter: config layer path determining the environment key prefix
     * @param configLayer private parameter: current layer of configuration to alter recursively
     */
    public applyEnvironmentOverrides(
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        environment: {[key: string]: any},
        path: string[] = [ENV_PREFIX],
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        configLayer: {[key: string]: any} = this,
    ) {
        Object.keys(configLayer).forEach((key) => {
            // camelCase to THICK_SNAKE
            const attributeKey = key.replace(/[A-Z]/g, (prefix) => `${ENV_KEY_SEPARATOR}${prefix}`).toUpperCase();
            const attributePath = path.concat([attributeKey]);

            if (configLayer[key] instanceof Object && !(configLayer[key] instanceof Array)) {
                this.applyEnvironmentOverrides(environment, attributePath, configLayer[key]);
            } else {
                const lookupKey = attributePath.join(ENV_KEY_SEPARATOR);
                if (lookupKey in environment) {
                    configLayer[key] = (configLayer[key] instanceof Array)
                        ? environment[lookupKey].split(ENV_VAL_SEPARATOR)
                        : environment[lookupKey];
                }
            }
        });
    }
}

class DiscordBridgeConfigBridge {
export class DiscordBridgeConfigBridge {
    public domain: string;
    public homeserverUrl: string;
    public port: number;
    public bindAddress: string;
    public presenceInterval: number = 500;
    public disablePresence: boolean;
    public disableTypingNotifications: boolean;
    public disableDiscordMentions: boolean;
    public disableDeletionForwarding: boolean;
    public enableSelfServiceBridging: boolean;
    public disablePortalBridging: boolean;
    public disableReadReceipts: boolean;
    public disableEveryoneMention: boolean = false;
    public disableHereMention: boolean = false;
    public disableJoinLeaveNotifications: boolean = false;
    public disableInviteNotifications: boolean = false;
    public disableRoomTopicNotifications: boolean = false;
    public determineCodeLanguage: boolean = false;
    public activityTracker: UserActivityTrackerConfig = UserActivityTrackerConfig.DEFAULT;
    public userLimit: number|null = null;
    public adminMxid: string|null = null;
    public invalidTokenMessage: string = 'Your Discord token is invalid';
}

export class DiscordBridgeConfigDatabase {
    public connString: string;
    public filename: string;
    // These parameters are legacy, and will stop the bridge if defined.
    public userStorePath: string;
    public roomStorePath: string;
}
@@ -68,6 +116,7 @@ export class DiscordBridgeConfigDatabase {
export class DiscordBridgeConfigAuth {
    public clientID: string;
    public botToken: string;
    public usePrivilegedIntents: boolean;
}

export class DiscordBridgeConfigLogging {
@@ -86,7 +135,7 @@ class DiscordBridgeConfigChannel {
    public deleteOptions = new DiscordBridgeConfigChannelDeleteOptions();
}

class DiscordBridgeConfigChannelDeleteOptions {
export class DiscordBridgeConfigChannelDeleteOptions {
    public namePrefix: string | null = null;
    public topicPrefix: string | null = null;
    public disableMessaging: boolean = false;
@@ -98,7 +147,8 @@ class DiscordBridgeConfigChannelDeleteOptions {

class DiscordBridgeConfigLimits {
    public roomGhostJoinDelay: number = 6000;
    public discordSendDelay: number = 750;
    public discordSendDelay: number = 1500;
    public roomCount: number = -1;
}

export class LoggingFile {
@@ -115,3 +165,9 @@ class DiscordBridgeConfigGhosts {
    public nickPattern: string = ":nick";
    public usernamePattern: string = ":username#:tag";
}

export class DiscordBridgeConfigMetrics {
    public enable: boolean = false;
    public port: number = 9001;
    public host: string = "127.0.0.1";
}
Numéro de ligne d'origine Numéro de ligne de diff Ligne de diff
@@ -60,12 +60,14 @@ export class DbEmoji implements IDbData {
            INSERT INTO emoji
            (emoji_id,name,animated,mxc_url,created_at,updated_at)
            VALUES ($emoji_id,$name,$animated,$mxc_url,$created_at,$updated_at);`, {
            /* eslint-disable @typescript-eslint/naming-convention */
            animated: Number(this.Animated),
            created_at: this.CreatedAt,
            emoji_id: this.EmojiId,
            mxc_url: this.MxcUrl,
            name: this.Name,
            updated_at: this.UpdatedAt,
            /* eslint-enable @typescript-eslint/naming-convention */
        });
    }

@@ -80,11 +82,13 @@ export class DbEmoji implements IDbData {
            updated_at = $updated_at
            WHERE
            emoji_id = $emoji_id`, {
            /* eslint-disable @typescript-eslint/naming-convention */
            animated: Number(this.Animated),
            emoji_id: this.EmojiId,
            mxc_url: this.MxcUrl,
            name: this.Name,
            updated_at: this.UpdatedAt,
            /* eslint-enable @typescript-eslint/naming-convention */
        });
    }

Numéro de ligne d'origine Numéro de ligne de diff Ligne de diff
@@ -24,7 +24,7 @@ export class DbEvent implements IDbDataMany {
    public GuildId: string;
    public ChannelId: string;
    public Result: boolean;
    // tslint:disable-next-line no-any
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    private rows: any[];

    get ResultCount(): number {
@@ -33,7 +33,7 @@ export class DbEvent implements IDbDataMany {

    public async RunQuery(store: DiscordStore, params: ISqlCommandParameters): Promise<void> {
        this.rows = [];
        // tslint:disable-next-line no-any
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        let rowsM: any[] | null = null;
        if (params.matrix_id) {
            rowsM = await store.db.All(`
@@ -55,8 +55,10 @@ export class DbEvent implements IDbDataMany {

        for (const rowM of rowsM) {
            const row = {
                /* eslint-disable @typescript-eslint/naming-convention */
                discord_id: rowM.discord_id,
                matrix_id: rowM.matrix_id,
                /* eslint-enable @typescript-eslint/naming-convention */
            };
            for (const rowD of await store.db.All(`
                    SELECT *
@@ -64,11 +66,13 @@ export class DbEvent implements IDbDataMany {
                    WHERE msg_id = $id`, {
                id: rowM.discord_id,
            })) {
                // tslint:disable-next-line no-any
                const insertRow: any = Object.assign({}, row);
                insertRow.guild_id = rowD.guild_id;
                insertRow.channel_id = rowD.channel_id;
                this.rows.push(insertRow);
                this.rows.push({
                    /* eslint-disable @typescript-eslint/naming-convention */
                    ...row,
                    guild_id: rowD.guild_id,
                    channel_id: rowD.channel_id,
                    /* eslint-enable @typescript-eslint/naming-convention */
                });
            }
        }
        this.Result = this.rows.length !== 0;
@@ -91,8 +95,10 @@ export class DbEvent implements IDbDataMany {
            INSERT INTO event_store
            (matrix_id,discord_id)
            VALUES ($matrix_id,$discord_id);`, {
            /* eslint-disable @typescript-eslint/naming-convention */
            discord_id: this.DiscordId,
            matrix_id: this.MatrixId,
            /* eslint-enable @typescript-eslint/naming-convention */
        });
        // Check if the discord item exists?
        const msgExists = await store.db.Get(`
@@ -108,9 +114,11 @@ export class DbEvent implements IDbDataMany {
            INSERT INTO discord_msg_store
            (msg_id, guild_id, channel_id)
            VALUES ($msg_id, $guild_id, $channel_id);`, {
            /* eslint-disable @typescript-eslint/naming-convention */
            channel_id: this.ChannelId,
            guild_id: this.GuildId,
            msg_id: this.DiscordId,
            /* eslint-enable @typescript-eslint/naming-convention */
        });
    }

@@ -123,13 +131,17 @@ export class DbEvent implements IDbDataMany {
            DELETE FROM event_store
            WHERE matrix_id = $matrix_id
            AND discord_id = $discord_id;`, {
            /* eslint-disable @typescript-eslint/naming-convention */
            discord_id: this.DiscordId,
            matrix_id: this.MatrixId,
            /* eslint-enable @typescript-eslint/naming-convention */
        });
        return store.db.Run(`
            DELETE FROM discord_msg_store
            WHERE msg_id = $discord_id;`, {
            /* eslint-disable @typescript-eslint/naming-convention */
            discord_id: this.DiscordId,
            /* eslint-enable @typescript-eslint/naming-convention */
        });
    }
}
Numéro de ligne d'origine Numéro de ligne de diff Ligne de diff
@@ -18,7 +18,7 @@ import { DiscordStore } from "../store";

export interface IDbData {
    Result: boolean;
    // tslint:disable-next-line no-any
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    RunQuery(store: DiscordStore, params: any): Promise<void|Error>;
    Insert(store: DiscordStore): Promise<void|Error>;
    Update(store: DiscordStore): Promise<void|Error>;
Numéro de ligne d'origine Numéro de ligne de diff Ligne de diff
@@ -25,19 +25,20 @@ const pgp: pgPromise.IMain = pgPromise({

export class Postgres implements IDatabaseConnector {
    public static ParameterizeSql(sql: string): string {
        return sql.replace(/\$((\w|\d|_)+)+/g, (k) => {
            return `\${${k.substr("$".length)}}`;
        return sql.replace(/\$((\w|\d|_)+)+/g, (k: string) => {
            return `\${${k.substring("$".length)}}`;
        });
    }

    // tslint:disable-next-line no-any
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    private db: pgPromise.IDatabase<any>;
    constructor(private connectionString: string) {

    }
    public Open() {

    public Open(): void {
        // Hide username:password
        const logConnString = this.connectionString.substr(
        const logConnString = this.connectionString.substring(
            this.connectionString.indexOf("@") || 0,
        );
        log.info(`Opening ${logConnString}`);
@@ -63,7 +64,7 @@ export class Postgres implements IDatabaseConnector {

    public async Run(sql: string, parameters?: ISqlCommandParameters): Promise<void> {
        log.silly("Run:", sql);
        return this.db.oneOrNone(Postgres.ParameterizeSql(sql), parameters).then(() => {});
        await this.db.oneOrNone(Postgres.ParameterizeSql(sql), parameters);
    }

    public async Close(): Promise<void> {
@@ -73,6 +74,5 @@ export class Postgres implements IDatabaseConnector {
    public async Exec(sql: string): Promise<void> {
        log.silly("Exec:", sql);
        await this.db.none(sql);
        return;
    }
}

Fichier modifié.

La taille limite d'aperçu a été dépassée, l'affichage des modifications a donc été réduit.

Numéro de ligne d'origine Numéro de ligne de diff Ligne de diff
@@ -15,7 +15,6 @@ limitations under the License.
*/

import { DiscordStore } from "../../store";
import { DiscordBridgeConfigDatabase } from "../../config";
export interface IDbSchema {
    description: string;
    run(store: DiscordStore): Promise<null|void|Error|Error[]>;

src/db/schema/v10.ts

0 → 100644
+54 −0
Numéro de ligne d'origine Numéro de ligne de diff Ligne de diff
/*
Copyright 2019 matrix-appservice-discord

Licensed under the Apache 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

    http://www.apache.org/licenses/LICENSE-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.
*/

import {IDbSchema} from "./dbschema";
import {DiscordStore} from "../../store";
import { Log } from "../../log";

const log = new Log("SchemaV10");

export class Schema implements IDbSchema {
    public description = "create indexes on tables";
    private readonly INDEXES = {
        idx_discord_msg_store_msgid: ["discord_msg_store", "msg_id"],
        idx_emoji_id: ["emoji", "emoji_id"],
        idx_emoji_mxc_url: ["emoji", "mxc_url"],
        idx_event_store_discord_id: ["event_store", "discord_id"],
        idx_event_store_matrix_id: ["event_store", "matrix_id"],
        idx_remote_room_data_room_id: ["remote_room_data", "room_id"],
        idx_room_entries_id: ["room_entries", "id"],
        idx_room_entries_matrix_id: ["room_entries", "matrix_id"],
        idx_room_entries_remote_id: ["room_entries", "remote_id"],
    };

    public async run(store: DiscordStore): Promise<void> {
        try {
            await Promise.all(Object.keys(this.INDEXES).map(async (indexId: string) => {
                const ids = this.INDEXES[indexId];
                return store.db.Exec(`CREATE INDEX ${indexId} ON ${ids[0]}(${ids[1]})`);
            }));
        } catch (ex) {
            log.error("Failed to apply indexes:", ex);
        }

    }

    public async rollBack(store: DiscordStore): Promise<void> {
        await Promise.all(Object.keys(this.INDEXES).map(async (indexId: string) => {
            return store.db.Exec(`DROP INDEX ${indexId}`);
        }));
    }
}
Numéro de ligne d'origine Numéro de ligne de diff Ligne de diff
/*
Copyright 2018 matrix-appservice-discord
Copyright 2021 matrix-appservice-discord

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,5 +14,30 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

// we are a test file and thus need those
/* tslint:disable:no-unused-expression max-file-line-count no-any */
import {IDbSchema} from "./dbschema";
import {DiscordStore} from "../../store";
import { Log } from "../../log";

const log = new Log("SchemaV12");

export class Schema implements IDbSchema {
    public description = "create stores for user activity tracking";

    public async run(store: DiscordStore): Promise<void> {
        try {
            await store.createTable(
                "CREATE TABLE user_activity (user_id TEXT UNIQUE, data JSON);",
                "user_activity",
            );
        } catch (ex) {
            log.error("Failed to create table:", ex);
        }

    }

    public async rollBack(store: DiscordStore): Promise<void> {
        await store.db.Exec(
            "DROP TABLE IF EXISTS user_activity;"
        );
    }
}
Numéro de ligne d'origine Numéro de ligne de diff Ligne de diff
@@ -78,8 +78,8 @@ directory.`);
            log.info("Moving ", row.userId);
            try {
                const client = await clientFactory.getClient(row.token);
                const dId = client.user.id;
                if (dId === null) {
                const dId = client.user?.id;
                if (!dId) {
                    continue;
                }
                log.verbose("INSERT INTO discord_id_token.");
Numéro de ligne d'origine Numéro de ligne de diff Ligne de diff
@@ -29,8 +29,8 @@ export class Schema implements IDbSchema {
                name TEXT NOT NULL,
                animated INTEGER NOT NULL,
                mxc_url TEXT NOT NULL,
                created_at INTEGER NOT NULL,
                updated_at INTEGER NOT NULL,
                created_at BIGINT NOT NULL,
                updated_at BIGINT NOT NULL,
                PRIMARY KEY(emoji_id)
        );`, "emoji");