feat(irc framework): support (client) tags

It is now possible to receive, send and process client tags
in general using the IRC framework.

This is useful for many client-oriented IRCv3 features: typing,
reacts, replies, channel contexts, etc.
This commit is contained in:
Raito Bezarius 2022-07-23 17:52:33 +02:00
parent 9f05a75c39
commit ac5eb004fe
6 changed files with 79 additions and 7 deletions

View file

@ -35,6 +35,7 @@
"../server/models/user.ts",
"../server/models/msg.ts",
"../server/models/prefix.ts",
"../server/models/client-tags.ts",
"./js/helpers/fullnamemap.json",
"./js/helpers/simplemap.json",
"../webpack.config.ts",

View file

@ -0,0 +1,36 @@
import _ from "lodash";
export enum ClientTagKey {
// https://ircv3.net/specs/client-tags/reply
DRAFT_REPLY = "draft/reply",
// https://ircv3.net/specs/client-tags/react
DRAFT_REACT = "draft/react",
// https://ircv3.net/specs/client-tags/channel-context
DRAFT_CHANNEL_CONTEXT = "draft/channel-context",
// https://ircv3.net/specs/client-tags/typing.html
TYPING = "typing",
}
export class ClientTags {
reaction?: string;
repliedTo?: string;
channelContext?: string;
rawTags: Record<string, string>;
public constructor(rawClientTags: Record<string, string>) {
this.rawTags = rawClientTags;
this.reaction = this.get(ClientTagKey.DRAFT_REACT);
this.repliedTo = this.get(ClientTagKey.DRAFT_REPLY);
this.channelContext = this.get(ClientTagKey.DRAFT_CHANNEL_CONTEXT);
}
public get(key: string): string | undefined {
return this.rawTags[`+${key}`];
}
public has(key: string): boolean {
return Object.prototype.hasOwnProperty.call(this.rawTags, `+${key}`);
}
}

View file

@ -1,6 +1,7 @@
import _ from "lodash";
import {LinkPreview} from "../plugins/irc-events/link";
import User from "./user";
import {ClientTags} from "./client-tags";
export type UserInMessage = Partial<User> & {
mode: string;
@ -18,6 +19,7 @@ export enum MessageType {
LOGIN = "login",
LOGOUT = "logout",
MESSAGE = "message",
TAGMSG = "tagmsg",
MODE = "mode",
MODE_CHANNEL = "mode_channel",
MODE_USER = "mode_user", // RPL_UMODEIS
@ -61,6 +63,8 @@ class Msg {
gecos!: string;
account!: boolean;
client_tags!: ClientTags;
// these are all just for error:
error!: string;
nick!: string;
@ -94,6 +98,7 @@ class Msg {
text: "",
type: MessageType.MESSAGE,
self: false,
client_tags: {},
});
if (this.time) {

View file

@ -16,6 +16,7 @@ function getTarget(cmd: string, args: string[], chan: Chan) {
const input: PluginInputHandler = function (network, chan, cmd, args) {
let targetName = getTarget(cmd, args, chan);
const tags = {};
if (cmd === "query") {
if (!targetName) {
@ -91,7 +92,7 @@ const input: PluginInputHandler = function (network, chan, cmd, args) {
return true;
}
network.irc.say(targetName, msg);
network.irc.say(targetName, msg, tags);
// If the IRCd does not support echo-message, simulate the message
// being sent back to us.

View file

@ -5,6 +5,7 @@ import Helper from "../../helper";
import {IrcEventHandler} from "../../client";
import Chan, {ChanType} from "../../models/chan";
import User from "../../models/user";
import {ClientTags} from "../../models/client-tags";
const nickRegExp = /(?:\x03[0-9]{1,2}(?:,[0-9]{1,2})?)?([\w[\]\\`^{|}-]+)/g;
@ -26,6 +27,11 @@ export default <IrcEventHandler>function (irc, network) {
handleMessage(data);
});
irc.on("tagmsg", function (data) {
data.type = MessageType.TAGMSG;
handleMessage(data);
});
irc.on("privmsg", function (data) {
data.type = MessageType.MESSAGE;
handleMessage(data);
@ -44,6 +50,7 @@ export default <IrcEventHandler>function (irc, network) {
target: string;
type: MessageType;
time: number;
tags: Record<string, string>;
text?: string;
from_server?: boolean;
message: string;
@ -131,14 +138,20 @@ export default <IrcEventHandler>function (irc, network) {
from: from,
highlight: highlight,
users: [],
client_tags: new ClientTags(data.tags),
});
if (showInActive) {
msg.showInActive = true;
}
// remove IRC formatting for custom highlight testing
const cleanMessage = cleanIrcMessage(data.message);
// Not all messages have bodies.
let cleanMessage = data.message;
if (data.message) {
// remove IRC formatting for custom highlight testing
cleanMessage = cleanIrcMessage(data.message);
}
// Self messages in channels are never highlighted
// Non-self messages are highlighted as soon as the nick is detected
@ -174,10 +187,19 @@ export default <IrcEventHandler>function (irc, network) {
LinkPrefetch(client, chan, msg, cleanMessage);
}
if (!data.message) {
return;
}
chan.pushMessage(client, msg, !msg.self);
// Do not send notifications if the channel is muted or for messages older than 15 minutes (znc buffer for example)
if (!chan.muted && msg.highlight && (!data.time || data.time > Date.now() - 900000)) {
if (
!chan.muted &&
msg.highlight &&
(!data.time || data.time > Date.now() - 900000) &&
msg.type !== MessageType.TAGMSG
) {
let title = chan.name;
let body = cleanMessage;

View file

@ -174,11 +174,18 @@ declare module "irc-framework" {
changeNick(nick: string): void;
sendMessage(commandName: string, target: string, message: string): string[];
sendMessage(
commandName: string,
target: string,
message: string,
tags: Record<string, string> = {}
): string[];
say(target: string, message: string): string[];
say(target: string, message: string, tags: Record<string, string> = {}): string[];
notice(target: string, message: string): string[];
notice(target: string, message: string, tags: Record<string, string> = {}): string[];
tagmsg(target: string, tags: Record<string, string> = {}): string[];
join(channel: string, key?: string): void;