Skip to content

Commit

Permalink
Merge pull request #647 from Half-Shot/hs/replies
Browse files Browse the repository at this point in the history
Formatting of replies
  • Loading branch information
Half-Shot committed Aug 21, 2018
2 parents 2d84c23 + c5f5278 commit c5a048a
Show file tree
Hide file tree
Showing 6 changed files with 330 additions and 7 deletions.
5 changes: 5 additions & 0 deletions config.sample.yaml
Expand Up @@ -428,3 +428,8 @@ ircService:
# `!storepass server.name passw0rd. When a connection is made to IRC on behalf of
# the Matrix user, this password will be sent as the server password (PASS command).
passwordEncryptionKeyPath: "passkey.pem"

# Config for Matrix -> IRC bridging
matrixHandler:
# Cache this many matrix events in memory to be used for m.relates_to messages (usually replies).
eventCacheSize: 4096
2 changes: 1 addition & 1 deletion lib/bridge/IrcBridge.js
Expand Up @@ -48,7 +48,7 @@ function IrcBridge(config, registration) {
this.joinedRoomList = [];

// Dependency graph
this.matrixHandler = new MatrixHandler(this);
this.matrixHandler = new MatrixHandler(this, this.config.matrixHandler);
this.ircHandler = new IrcHandler(this);
this._clientPool = new ClientPool(this);
var dirPath = this.config.ircService.databaseUri.substring("nedb://".length);
Expand Down
122 changes: 117 additions & 5 deletions lib/bridge/MatrixHandler.js
Expand Up @@ -19,8 +19,12 @@ const MSG_PMS_DISABLED_FEDERATION = "[Bridge] Sorry, PMs are disabled on " +

const KICK_RETRY_DELAY_MS = 15000;
const KICK_DELAY_JITTER = 30000;
/* Number of events to store in memory for use in replies. */
const DEFAULT_EVENT_CACHE_SIZE = 4096;
/* Length of the source text in a formatted reply message */
const REPLY_SOURCE_MAX_LENGTH = 32;

function MatrixHandler(ircBridge) {
function MatrixHandler(ircBridge, config) {
this.ircBridge = ircBridge;
// maintain a list of room IDs which are being processed invite-wise. This is
// required because invites are processed asyncly, so you could get invite->msg
Expand All @@ -30,6 +34,10 @@ function MatrixHandler(ircBridge) {
};

this._memberTracker = null;
this._eventCache = new Map(); //eventId => {body, sender}
config = config || {}
this._eventCacheMaxSize = config.eventCacheSize === undefined ?
DEFAULT_EVENT_CACHE_SIZE : config.eventCacheSize;
this.metrics = {
//domain => {"metricname" => value}
};
Expand Down Expand Up @@ -1262,16 +1270,37 @@ MatrixHandler.prototype._onMessage = Promise.coroutine(function*(req, event) {

MatrixHandler.prototype._sendIrcAction = Promise.coroutine(
function*(req, ircRoom, ircClient, ircAction, event) {

// Send the action as is if it is not a text message
// Also, check for the existance of the getSplitMessages method.
if (event.content.msgtype !== "m.text" ||
!(ircClient.unsafeClient && ircClient.unsafeClient.getSplitMessages)) {
if (event.content.msgtype !== "m.text") {
yield this.ircBridge.sendIrcAction(ircRoom, ircClient, ircAction);
return;
}

let text = event.content.body;
let cacheBody = text;
if (event.content["m.relates_to"] && event.content["m.relates_to"]["m.in_reply_to"]) {
const reply = yield this._textForReplyEvent(event, ircRoom);
if (reply !== undefined) {
ircAction.text = text = reply.formatted;
cacheBody = reply.reply;
}
}
this._eventCache.set(event.event_id, {
body: cacheBody.substr(0, REPLY_SOURCE_MAX_LENGTH),
sender: event.sender
});

// Cache events in here so we can refer to them for replies.
if (this._eventCache.size > this._eventCacheMaxSize) {
const delKey = this._eventCache.entries().next().value[0];
this._eventCache.delete(delKey);
}

// Check for the existance of the getSplitMessages method.
if (!(ircClient.unsafeClient && ircClient.unsafeClient.getSplitMessages)) {
yield this.ircBridge.sendIrcAction(ircRoom, ircClient, ircAction);
return;
}

// Generate an array of individual messages that would be sent
let potentialMessages = ircClient.unsafeClient.getSplitMessages(ircRoom.channel, text);
Expand Down Expand Up @@ -1492,6 +1521,89 @@ MatrixHandler.prototype._onUserQuery = Promise.coroutine(function*(req, userId)
yield this.ircBridge.getMatrixUser(ircUser);
});

MatrixHandler.prototype._textForReplyEvent = Promise.coroutine(function*(event, ircRoom) {
const REPLY_REGEX = /> <(@.*:.*)>(.*)\n\n(.*)/;
const REPLY_NAME_MAX_LENGTH = 12;
const eventId = event.content["m.relates_to"]["m.in_reply_to"].event_id;
const match = REPLY_REGEX.exec(event.content.body);
if (match.length !== 4) {
return;
}

let rplName;
let rplSource;
const rplText = match[3];
if (!this._eventCache.has(eventId)) {
// Fallback to fetching from the homeserver.
try {
const eventContent = yield this.ircBridge.getAppServiceBridge().getIntent().getEvent(
event.room_id, eventId
);
rplName = eventContent.sender;
if (typeof(eventContent.content.body) !== "string") {
throw Error("'body' was not a string.");
}
const isReply = event.content["m.relates_to"] &&
event.content["m.relates_to"]["m.in_reply_to"];
if (isReply) {
const sourceMatch = REPLY_REGEX.exec(eventContent.content.body);
rplSource = sourceMatch.length === 4 ? sourceMatch[3] : event.content.body;
}
else {
rplSource = eventContent.content.body;
}
rplSource = rplSource.substr(0, REPLY_SOURCE_MAX_LENGTH);
this._eventCache.set(eventId, {sender: rplName, body: rplSource});
}
catch (err) {
// If we couldn't find the event, then frankly we can't
// trust it and we won't treat it as a reply.
return {
formatted: rplText,
reply: rplText,
};
}
}
else {
rplName = this._eventCache.get(eventId).sender;
rplSource = this._eventCache.get(eventId).body;
}

// Get the first non-blank line from the source.
const lines = rplSource.split('\n').filter((line) => !/^\s*$/.test(line))
if (lines.length > 0) {
// Cut to a maximum length.
rplSource = lines[0].substr(0, REPLY_SOURCE_MAX_LENGTH);
// Ellipsis if needed.
if (lines[0].length > REPLY_SOURCE_MAX_LENGTH) {
rplSource = rplSource + "...";
}
// Wrap in formatting
rplSource = ` "${rplSource}"`;
}
else {
// Don't show a source because we couldn't format one.
rplSource = "";
}

// Fetch the sender's IRC nick.
const sourceClient = this.ircBridge.getIrcUserFromCache(ircRoom.server, rplName);
if (sourceClient) {
rplName = sourceClient.nick;
}
else {
// Somehow we failed, so fallback to localpart.
rplName = rplName.substr(1,
Math.min(REPLY_NAME_MAX_LENGTH, rplName.indexOf(":") - 1)
);
}

return {
formatted: `<${rplName}${rplSource}> ${rplText}`,
reply: rplText,
};
});

MatrixHandler.prototype._incrementMetric = function(serverDomain, metricName) {
let metricSet = this.metrics[serverDomain];
if (!metricSet) {
Expand Down
5 changes: 5 additions & 0 deletions lib/config/schema.yml
Expand Up @@ -78,6 +78,11 @@ properties:
type: "number"
passwordEncryptionKeyPath:
type: "string"
matrixHandler:
type: "object"
properties:
eventCacheSize:
type: "integer"
servers:
type: "object"
# all properties must follow the following
Expand Down
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -30,7 +30,7 @@
"extend": "^2.0.0",
"irc": "matrix-org/node-irc#c9abb427bec5016d94a2abf3e058cc62de09ea5a",
"js-yaml": "^3.2.7",
"matrix-appservice-bridge": "1.5.0a",
"matrix-appservice-bridge": "matrix-org/matrix-appservice-bridge#4cba3711ef0fad357a547e0eaca982d652c46f24",
"nedb": "^1.1.2",
"nopt": "^3.0.1",
"prom-client": "^6.3.0",
Expand Down

0 comments on commit c5a048a

Please sign in to comment.