Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Metrics #480

Merged
merged 50 commits into from
Jun 13, 2019
Merged
Show file tree
Hide file tree
Changes from 49 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
639623e
Add support for metrics
Half-Shot May 22, 2019
c4ae6be
Dammit metrics.ts
Half-Shot May 22, 2019
3d9a677
Enable the metrics
Half-Shot May 22, 2019
4397227
Wait for bridge to start first
Half-Shot May 22, 2019
663eebc
Expire old requests in metrics
Half-Shot May 22, 2019
d617346
Store prefixes
Half-Shot May 22, 2019
11a548f
Merge branch 'develop' into release/0.5.0
Sorunome May 26, 2019
2370b4d
Add support for remote send calls in metrics
Half-Shot May 31, 2019
95cb6af
Linting
Half-Shot May 31, 2019
3a90721
Add copyright header
Half-Shot May 31, 2019
04f3007
Bind metrics safely
Half-Shot May 31, 2019
b0810a8
Fix docker image for appservice bridge dependency
turt2live Jun 2, 2019
a78de0b
Merge branch 'travis/fix-docker' of https://github.com/t2bot/matrix-a…
Sorunome Jun 2, 2019
e57769e
Merge branch 'develop' into release/0.5.0
Sorunome Jun 2, 2019
c6e41d9
Merge branch 'release/0.5.0'
Sorunome Jun 4, 2019
0267969
fix version
Sorunome Jun 4, 2019
8c09564
fix some test assertion calls
pacien Jun 4, 2019
45d2bf8
add config override using env variables
pacien Jun 4, 2019
9827b98
add note for config override using env vars
pacien Jun 5, 2019
28ecd27
update p-queue lib
pacien Jun 6, 2019
d02603c
disambiguate promise type
pacien Jun 7, 2019
831f922
Merge pull request #489 from pacien/pqueue-ts-fix
Half-Shot Jun 7, 2019
90ea8c8
Merge pull request #485 from pacien/config-from-env
Half-Shot Jun 7, 2019
de2e92d
Add non-warning to readme for installation
Half-Shot Jun 7, 2019
dab3561
Merge pull request #492 from Half-Shot/hs/readme-install-warnings
Half-Shot Jun 7, 2019
6ed4eb1
add support for animated discord guild icons
Sorunome Jun 13, 2019
fda7d34
remove unneeded type setting
Sorunome Jun 13, 2019
d2642ef
Merge branch 'soru/animated-guild-icons' into develop
Sorunome Jun 13, 2019
a6cbac2
fix some test assertion calls
pacien Jun 4, 2019
eb89fc1
add config override using env variables
pacien Jun 4, 2019
37d76af
add note for config override using env vars
pacien Jun 5, 2019
9a47c09
update p-queue lib
pacien Jun 6, 2019
ef1872d
disambiguate promise type
pacien Jun 7, 2019
c3a142a
Add non-warning to readme for installation
Half-Shot Jun 7, 2019
1b9d062
add support for animated discord guild icons
Sorunome Jun 13, 2019
1391753
remove unneeded type setting
Sorunome Jun 13, 2019
0fdb9dc
Merge branch 'develop' of github.com:Half-Shot/matrix-appservice-disc…
Half-Shot Jun 13, 2019
980a1a2
Add support for metrics
Half-Shot May 22, 2019
b336eda
Dammit metrics.ts
Half-Shot May 22, 2019
bdc84f7
Enable the metrics
Half-Shot May 22, 2019
c9cad35
Wait for bridge to start first
Half-Shot May 22, 2019
641edc7
Expire old requests in metrics
Half-Shot May 22, 2019
5f245e1
Store prefixes
Half-Shot May 22, 2019
062d5b1
Add support for remote send calls in metrics
Half-Shot May 31, 2019
2243030
Linting
Half-Shot May 31, 2019
cff2bc1
Add copyright header
Half-Shot May 31, 2019
55a1248
Bind metrics safely
Half-Shot May 31, 2019
f64340b
More binds
Half-Shot Jun 13, 2019
8405046
Merge branch 'hs/metrics' of github.com:Half-Shot/matrix-appservice-d…
Half-Shot Jun 13, 2019
c3eaaa8
Add a note about metrics
Half-Shot Jun 13, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
FROM node:alpine 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 apk add --no-cache -t build-deps make gcc g++ python ca-certificates libc-dev wget git
RUN cd /tmp/src \
&& npm install \
&& npm run build
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ The bridge supports any version of Node.js >= v10.X, including all [current rele

### Setup the bridge

* Run ``npm install`` to grab the dependencies.
* Run ``npm install`` to grab the dependencies. `npm` may complain about peer dependencies, but you can safely ignore these.
* Run ``npm run build`` to build the typescript into javascript.
* 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.
Expand Down Expand Up @@ -104,6 +104,7 @@ should show up in the network list on Riot and other clients.

* For the bot to appear online on Discord you need to run the bridge itself.
* ``npm 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)

Expand Down
30 changes: 19 additions & 11 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "matrix-appservice-discord",
"version": "0.5.0-rc2",
"version": "0.5.0",
"description": "A bridge between Matrix and Discord",
"main": "discordas.js",
"scripts": {
Expand Down Expand Up @@ -38,15 +38,16 @@
"command-line-args": "^4.0.1",
"command-line-usage": "^4.1.0",
"discord-markdown": "^2.0.0",
"discord.js": "^11.5.0",
"discord.js": "^11.5.1",
"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",
"node-html-parser": "^1.1.11",
"p-queue": "^5.0.0",
"p-queue": "^6.0.1",
"pg-promise": "^8.5.1",
"prom-client": "^11.3.0",
"tslint": "^5.11.0",
"typescript": "^3.1.3",
"winston": "^3.0.0",
Expand Down
25 changes: 21 additions & 4 deletions src/bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import * as Discord from "discord.js";
import * as mime from "mime";
import { IMatrixEvent, IMatrixMediaInfo } from "./matrixtypes";
import { DiscordCommandHandler } from "./discordcommandhandler";
import { MetricPeg } from "./metrics";

const log = new Log("DiscordBot");

Expand Down Expand Up @@ -145,7 +146,7 @@ export class DiscordBot {
}

this.channelLocks[channel.id] = {i: null, r: null, p: null};
const p = new Promise((resolve) => {
const p = new Promise<{}>((resolve) => {
const i = setTimeout(() => {
log.warn(`Lock on channel ${channel.id} expired. Discord is lagging behind?`);
this.unlockChannel(channel);
Expand Down Expand Up @@ -240,6 +241,7 @@ export class DiscordBot {
client.on("messageDelete", async (msg: Discord.Message) => {
try {
await this.waitUnlock(msg.channel);
this.clientFactory.bindMetricsToChannel(msg.channel as Discord.TextChannel);
this.discordMessageQueue[msg.channel.id] = (async () => {
await (this.discordMessageQueue[msg.channel.id] || Promise.resolve());
try {
Expand All @@ -260,6 +262,7 @@ export class DiscordBot {
promiseArr.push(async () => {
try {
await this.waitUnlock(msg.channel);
this.clientFactory.bindMetricsToChannel(msg.channel as Discord.TextChannel);
await this.DeleteDiscordMessage(msg);
} catch (err) {
log.error("Caught while handling 'messageDeleteBulk'", err);
Expand All @@ -274,6 +277,7 @@ export class DiscordBot {
client.on("messageUpdate", async (oldMessage: Discord.Message, newMessage: Discord.Message) => {
try {
await this.waitUnlock(newMessage.channel);
this.clientFactory.bindMetricsToChannel(newMessage.channel as Discord.TextChannel);
this.discordMessageQueue[newMessage.channel.id] = (async () => {
await (this.discordMessageQueue[newMessage.channel.id] || Promise.resolve());
try {
Expand All @@ -288,12 +292,15 @@ export class DiscordBot {
});
client.on("message", async (msg: Discord.Message) => {
try {
MetricPeg.get.registerRequest(msg.id);
await this.waitUnlock(msg.channel);
this.clientFactory.bindMetricsToChannel(msg.channel as Discord.TextChannel);
this.discordMessageQueue[msg.channel.id] = (async () => {
await (this.discordMessageQueue[msg.channel.id] || Promise.resolve());
try {
await this.OnMessage(msg);
} catch (err) {
MetricPeg.get.requestOutcome(msg.id, true, "fail");
log.error("Caught while handing 'message'", err);
}
})();
Expand Down Expand Up @@ -389,6 +396,7 @@ export class DiscordBot {
}
const channel = guild.channels.get(room);
if (channel && channel.type === "text") {
this.ClientFactory.bindMetricsToChannel(channel as Discord.TextChannel);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't we always bind the channel on message processing? Or even better, right when the message enters via the event listeners?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, because we only call the send function when sending stuff that came via matrix. Though I guess we should also bind in the event listeners too.

const lookupResult = new ChannelLookupResult();
lookupResult.channel = channel as Discord.TextChannel;
lookupResult.botUser = this.bot.user.id === client.user.id;
Expand Down Expand Up @@ -448,6 +456,7 @@ export class DiscordBot {
// NOTE: Don't send replies to discord if we are a puppet.
msg = await chan.send(embed.description, opts);
} else if (hook) {
MetricPeg.get.remoteCall("hook.send");
msg = await hook.send(embed.description, {
avatarURL: embed!.author!.icon_url,
embeds: embedSet.replyEmbed ? [embedSet.replyEmbed] : undefined,
Expand Down Expand Up @@ -539,6 +548,7 @@ export class DiscordBot {
if (guild) {
const channel = client.channels.get(entry.remote!.get("discord_channel") as string);
if (channel) {
this.ClientFactory.bindMetricsToChannel(channel as Discord.TextChannel);
return channel;
}
throw Error("Channel given in room entry not found");
Expand Down Expand Up @@ -738,11 +748,13 @@ export class DiscordBot {
if (indexOfMsg !== -1) {
log.verbose("Got repeated message, ignoring.");
delete this.sentMessages[indexOfMsg];
MetricPeg.get.requestOutcome(msg.id, true, "dropped");
return; // Skip *our* messages
}
const chan = msg.channel as Discord.TextChannel;
if (msg.author.id === this.bot.user.id) {
// We don't support double bridging.
MetricPeg.get.requestOutcome(msg.id, true, "dropped");
return;
}
// Test for webhooks
Expand All @@ -751,13 +763,15 @@ export class DiscordBot {
.filterArray((h) => h.name === "_matrix").pop();
if (webhook && msg.webhookID === webhook.id) {
// Filter out our own webhook messages.
MetricPeg.get.requestOutcome(msg.id, true, "dropped");
return;
}
}

// check if it is a command to process by the bot itself
if (msg.content.startsWith("!matrix")) {
await this.discordCommandHandler.Process(msg);
MetricPeg.get.requestOutcome(msg.id, true, "success");
return;
}

Expand All @@ -766,14 +780,15 @@ export class DiscordBot {
let rooms;
try {
rooms = await this.channelSync.GetRoomIdsFromChannel(msg.channel);
if (rooms === null) {
throw Error();
}
} catch (err) {
log.verbose("No bridged rooms to send message to. Oh well.");
MetricPeg.get.requestOutcome(msg.id, true, "dropped");
return null;
}
try {
if (rooms === null) {
return null;
}
const intent = this.GetIntentFromDiscordMember(msg.author, msg.webhookID);
// Check Attachements
await Util.AsyncForEach(msg.attachments.array(), async (attachment) => {
Expand Down Expand Up @@ -854,7 +869,9 @@ export class DiscordBot {
await afterSend(res);
}
});
MetricPeg.get.requestOutcome(msg.id, true, "success");
} catch (err) {
MetricPeg.get.requestOutcome(msg.id, true, "fail");
log.verbose("Failed to send message into room.", err);
}
}
Expand Down
4 changes: 3 additions & 1 deletion src/channelsyncroniser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,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();
Expand Down
21 changes: 17 additions & 4 deletions src/clientfactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, TextChannel } from "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;
Expand Down Expand Up @@ -110,4 +108,19 @@ export class DiscordClientFactory {
return this.botClient;
}
}

public bindMetricsToChannel(channel: TextChannel) {
// tslint:disable-next-line:no-any
const flexChan = channel as any;
if (flexChan._xmet_send !== undefined) {
return;
}
// Prefix the real functions with _xmet_
flexChan._xmet_send = channel.send;
// tslint:disable-next-line:only-arrow-functions
channel.send = function() {
MetricPeg.get.remoteCall("channel.send");
return flexChan._xmet_send.apply(channel, arguments);
};
}
}
49 changes: 42 additions & 7 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ 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 = ",";

/** Type annotations for config/config.schema.yaml */
export class DiscordBridgeConfig {
public bridge: DiscordBridgeConfigBridge = new DiscordBridgeConfigBridge();
Expand All @@ -27,18 +31,48 @@ export class DiscordBridgeConfig {

/**
* 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) {
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(
// tslint:disable-next-line no-any
environment: {[key: string]: any},
path: string[] = [ENV_PREFIX],
// tslint:disable-next-line no-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];
}
}
configLayer[key] = newConfig[key];
});
}
}
Expand All @@ -56,6 +90,7 @@ class DiscordBridgeConfigBridge {
public disableEveryoneMention: boolean = false;
public disableHereMention: boolean = false;
public disableJoinLeaveNotifications: boolean = false;
public enableMetrics: boolean = false;
}

export class DiscordBridgeConfigDatabase {
Expand Down
Loading