From ab678f1ef50c77b4b0fcde8ac2de068760282809 Mon Sep 17 00:00:00 2001 From: Raito Bezarius Date: Sat, 23 Jul 2022 17:52:33 +0200 Subject: [PATCH] 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. --- client/tsconfig.json | 1 + server/models/client-tags.ts | 36 +++++++++++++++++++++++++ server/models/msg.ts | 6 +++++ server/plugins/irc-events/message.ts | 33 ++++++++++++++--------- server/types/modules/irc-framework.d.ts | 18 +++++++++++-- 5 files changed, 80 insertions(+), 14 deletions(-) create mode 100644 server/models/client-tags.ts diff --git a/client/tsconfig.json b/client/tsconfig.json index 4054c4f1..d47b0107 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -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", diff --git a/server/models/client-tags.ts b/server/models/client-tags.ts new file mode 100644 index 00000000..7e66858d --- /dev/null +++ b/server/models/client-tags.ts @@ -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; + + public constructor(rawClientTags: Record) { + 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}`); + } +} diff --git a/server/models/msg.ts b/server/models/msg.ts index 6a1d027c..f3f6da40 100644 --- a/server/models/msg.ts +++ b/server/models/msg.ts @@ -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 & { 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; @@ -87,6 +91,8 @@ class Msg { }); } + this.client_tags = new ClientTags({}); + _.defaults(this, attr, { from: {}, id: 0, diff --git a/server/plugins/irc-events/message.ts b/server/plugins/irc-events/message.ts index 393f247d..d8772cce 100644 --- a/server/plugins/irc-events/message.ts +++ b/server/plugins/irc-events/message.ts @@ -5,8 +5,22 @@ 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; +type MessageData = { + nick: string; + hostname: string; + ident: string; + target: string; + type: MessageType; + time: number; + tags: Record; + text?: string; + from_server?: boolean; + message: string; + group?: string; +}; export default function (irc, network) { const client = this; @@ -26,6 +40,11 @@ export default function (irc, network) { handleMessage(data); }); + irc.on("tagmsg", function (data) { + data.type = MessageType.TAGMSG; + // TODO: handleTagMessage(data); + }); + irc.on("privmsg", function (data) { data.type = MessageType.MESSAGE; handleMessage(data); @@ -37,18 +56,7 @@ export default function (irc, network) { handleMessage(data); }); - function handleMessage(data: { - nick: string; - hostname: string; - ident: string; - target: string; - type: MessageType; - time: number; - text?: string; - from_server?: boolean; - message: string; - group?: string; - }) { + function handleMessage(data: MessageData) { let chan: Chan | undefined; let from: User; let highlight = false; @@ -131,6 +139,7 @@ export default function (irc, network) { from: from, highlight: highlight, users: [], + client_tags: new ClientTags(data.tags), }); if (showInActive) { diff --git a/server/types/modules/irc-framework.d.ts b/server/types/modules/irc-framework.d.ts index 71802891..581572bc 100644 --- a/server/types/modules/irc-framework.d.ts +++ b/server/types/modules/irc-framework.d.ts @@ -31,7 +31,7 @@ declare module "irc-framework" { message: string; nick: string; reply: (message: string) => void; - tags: {[key: string]: string}; + tags: Record; target: string; time?: any; type: "privmsg" | "action" | "notice" | "wallops"; @@ -44,6 +44,7 @@ declare module "irc-framework" { ident: string; nick: string; time?: any; + tags: Record; } export interface KickEventArgs { kicked: string; @@ -176,10 +177,23 @@ declare module "irc-framework" { sendMessage(commandName: string, target: string, message: string): string[]; + sendMessage( + commandName: string, + target: string, + message: string, + tags: Record = {} + ): string[]; + say(target: string, message: string): string[]; + say(target: string, message: string, tags: Record): string[]; + notice(target: string, message: string): string[]; + notice(target: string, message: string, tags: Record): string[]; + + tagmsg(target: string, tags: Record = {}): string[]; + join(channel: string, key?: string): void; part(channel: string, message?: string): void; @@ -292,7 +306,7 @@ declare module "irc-framework" { reply(e: any): any; - tags: Record; + tags: Record; // any time?: any;