This commit is contained in:
Max Leiter 2022-06-02 20:02:24 -07:00
parent 4c3fcf0e36
commit 4eea858a14
No known key found for this signature in database
GPG key ID: A3512F2F2F17EBDA
7 changed files with 178 additions and 88 deletions

View file

@ -8,6 +8,7 @@
highlight: message.highlight || focused, highlight: message.highlight || focused,
'previous-source': isPreviousSource, 'previous-source': isPreviousSource,
}, },
batchType,
]" ]"
:data-type="message.type" :data-type="message.type"
:data-command="message.command" :data-command="message.command"
@ -97,6 +98,12 @@
</div> </div>
</template> </template>
<style scoped css>
.batch-type-chathistory {
opacity: 0.6;
}
</style>
<script lang="ts"> <script lang="ts">
import {computed, defineComponent, PropType} from "vue"; import {computed, defineComponent, PropType} from "vue";
import dayjs from "dayjs"; import dayjs from "dayjs";
@ -153,6 +160,14 @@ export default defineComponent({
return "message-" + props.message.type; return "message-" + props.message.type;
}); });
const batchType = computed(() => {
if (props.message.batch) {
return `batch-type-${props.message.batch?.type}`;
}
return undefined;
});
const isAction = () => { const isAction = () => {
return typeof MessageTypes["message-" + props.message.type] !== "undefined"; return typeof MessageTypes["message-" + props.message.type] !== "undefined";
}; };
@ -163,6 +178,7 @@ export default defineComponent({
messageTimeLocale, messageTimeLocale,
messageComponent, messageComponent,
isAction, isAction,
batchType,
}; };
}, },
}); });

View file

@ -74,6 +74,13 @@ class Msg {
statusmsgGroup!: string; statusmsgGroup!: string;
params!: string[]; params!: string[];
batch?: {
id: number;
type: string;
params: string[];
commands: any[];
};
constructor(attr?: Partial<Msg>) { constructor(attr?: Partial<Msg>) {
// Some properties need to be copied in the Msg object instead of referenced // Some properties need to be copied in the Msg object instead of referenced
if (attr) { if (attr) {

View file

@ -29,6 +29,11 @@ export default <IrcEventHandler>function (irc, network) {
} else if (data.nick === irc.user.nick) { } else if (data.nick === irc.user.nick) {
chan.state = ChanState.JOINED; chan.state = ChanState.JOINED;
// Don't react to our own join
if (data.batch?.type !== "chathistory") {
network.irc.chatHistory.latest(chan.name);
}
client.emit("channel:state", { client.emit("channel:state", {
chan: chan.id, chan: chan.id,
state: chan.state, state: chan.state,

View file

@ -41,6 +41,8 @@ export default <IrcEventHandler>function (irc, network) {
type: MessageType; type: MessageType;
time: number; time: number;
text: string; text: string;
// TODO: type
batch?: any;
message: string; message: string;
group?: string; group?: string;
}) { }) {
@ -50,6 +52,10 @@ export default <IrcEventHandler>function (irc, network) {
let showInActive = false; let showInActive = false;
const self = data.nick === irc.user.nick; const self = data.nick === irc.user.nick;
if (data.batch?.type === "chathistory") {
return;
}
// Some servers send messages without any nickname // Some servers send messages without any nickname
if (!data.nick) { if (!data.nick) {
data.from_server = true; data.from_server = true;
@ -126,6 +132,7 @@ export default <IrcEventHandler>function (irc, network) {
from: from, from: from,
highlight: highlight, highlight: highlight,
users: [], users: [],
batch: data.batch || undefined,
}); });
if (showInActive) { if (showInActive) {

View file

@ -1,14 +1,48 @@
import EventEmitter from "events"; import EventEmitter from "events";
import { Client, IRCMiddleware } from "irc-framework"; import {Client, IRCMiddleware} from "irc-framework";
import { isDate } from "lodash"; import {isDate} from "lodash";
const initChatHistoryCommands = (irc: Client) => { const initChatHistoryCommands = (irc: Client) => {
const isEnabled = () => irc.network.supports('draft/chathistory') || irc.network.supports('chathistory'); const waitForResponse = async <T>(
firstResponse: string,
response: string,
handler: (data: T) => void,
timeout = 5000
) => {
return new Promise((resolve, reject) => {
let id: string;
irc.chatHistory.eventbus.on(firstResponse, (data) => {
id = data.id;
});
// TODO: improve typing
const handleData = (
data: T & {
id: string;
}
) => {
console.log(`[response:${response}] [id:${data.id}] [key:${id}]`);
if (data.id === id) {
console.log(id, "handled.");
resolve(handler(data));
}
};
irc.chatHistory.eventbus.on(response, handleData);
// setTimeout(() => {
// console.log(`[${response}] [${key}] timeout`);
// irc.chatHistory.eventbus.off(response, handleData);
// reject(new Error("timeout"));
// }, timeout);
});
};
const isEnabled = () =>
irc.network.supports("draft/chathistory") || irc.network.supports("chathistory");
const messageLimit = () => const messageLimit = () =>
irc.network.options["DRAFT/CHATHISTORY"] irc.network.options["DRAFT/CHATHISTORY"] || irc.network.options.CHATHISTORY || 100;
|| irc.network.options.CHATHISTORY || 100;
const getTimestamp = (ts: string | Date) => { const getTimestamp = (ts: string | Date) => {
if (isDate(ts)) { if (isDate(ts)) {
@ -16,82 +50,97 @@ const initChatHistoryCommands = (irc: Client) => {
} }
return ts; return ts;
}
const commands: typeof Client.prototype.chatHistory = {
async latest(target: string, timestamp = "*", limit = messageLimit()) {
// CHATHISTORY LATEST #channel * 50
if (!isEnabled()) {
return;
}
irc.raw(`CHATHISTORY LATEST ${target} ${getTimestamp(timestamp)} ${limit}`);
}
}; };
irc.chatHistory = commands; irc.chatHistory.latest = async (target, timestamp = "*", limit = messageLimit()) => {
} // CHATHISTORY LATEST #channel * 50
if (!isEnabled()) {
const initCallbacks = (irc: Client) => {
const batches: Map<number, ((data?: any) => Promise<void>)> = new Map();
irc.chatHistory.batches = batches;
irc.chatHistory.addCallback = (id, callback) => {
batches.set(id, callback);
}
irc.chatHistory.runCallback = async (id: number) => {
if (batches.has(id)) {
// TODO: investigate typing
await batches.get(id)!();
}
}
}
type BatchResponse = {
id: number,
type: 'chathistory',
params: string[],
commands: string[],
}
const initListeners = (irc: Client) => {
irc.on("batch start chathistory", (data: BatchResponse) => {
irc.chatHistory.batches.set(data.id, new Promise((resolve, reject) => {
irc.chatHistory.addCallback(data.id, () => {
resolve();
});
}
})
irc.on("batch end chathistory", (data: BatchResponse) => {
if (!irc.chatHistory) {
return; return;
} }
const processedTimestamp = getTimestamp(timestamp);
// irc.chatHistory.runCallbacks(data.id); irc.raw(`CHATHISTORY LATEST ${target} ${processedTimestamp} ${limit}`);
}); return waitForResponse<BatchResponse>("batch:start", "batch:end", (data) => data.commands);
} };
irc.chatHistory.before = (target, timestamp, limit = messageLimit()) => {
// CHATHISTORY BEFORE <target> <timestamp=YYYY-MM-DDThh:mm:ss.sssZ | msgid=1234> <limit>
if (!isEnabled()) {
return;
}
const processedTimestamp = getTimestamp(timestamp);
irc.raw(`CHATHISTORY BEFORE ${target} ${processedTimestamp} ${limit}`);
return waitForResponse<BatchResponse>("batch:start", "batch:end", (data) => data.commands);
};
irc.chatHistory.after = (target, timestamp, limit = messageLimit()) => {
// CHATHISTORY AFTER <target> <timestamp=YYYY-MM-DDThh:mm:ss.sssZ | msgid=1234> <limit>
if (!isEnabled()) {
return;
}
const processedTimestamp = getTimestamp(timestamp);
irc.raw(`CHATHISTORY AFTER ${target} ${processedTimestamp} ${limit}`);
return waitForResponse<BatchResponse>("batch:start", "batch:end", (data) => data.commands);
};
irc.chatHistory.around = (target, timestamp, limit = messageLimit()) => {
// CHATHISTORY AROUND <target> <timestamp=YYYY-MM-DDThh:mm:ss.sssZ | msgid=1234> <limit>
if (!isEnabled()) {
return;
}
const processedTimestamp = getTimestamp(timestamp);
irc.raw(`CHATHISTORY AROUND ${target} ${processedTimestamp} ${limit}`);
return waitForResponse<BatchResponse>("batch:start", "batch:end", (data) => data.commands);
};
irc.chatHistory.between = (target, timestamp1, timestamp2, limit = messageLimit()) => {
// CHATHISTORY BETWEEN <target> <timestamp1=YYYY-MM-DDThh:mm:ss.sssZ | msgid=1234> <timestamp2=YYYY-MM-DDThh:mm:ss.sssZ | msgid=1234> <limit>
if (!isEnabled()) {
return;
}
const processedTimestamp = getTimestamp(timestamp1);
const processedTimestamp2 = getTimestamp(timestamp2);
irc.raw(
`CHATHISTORY BETWEEN ${target} ${processedTimestamp} ${processedTimestamp2} ${limit}`
);
return waitForResponse<BatchResponse>("batch:start", "batch:end", (data) => data.commands);
};
};
type BatchResponse = {
id: string;
type: "chathistory";
params: string[];
commands: string[];
};
function ChatHistoryMiddleware(): IRCMiddleware { function ChatHistoryMiddleware(): IRCMiddleware {
return function (irc, raw_events, parsed_events) { return function (irc, raw_events, parsed_events) {
irc.requestCap(['draft/chathistory']); //@ts-ignore
irc.chatHistory = {
eventbus: new EventEmitter(),
};
irc.chatHistory.batches = new Map<string, Promise<void>>(); irc.requestCap(["draft/chathistory", "draft/event-playback", "draft/chathistory-targets"]);
irc.on("batch start chathistory", (data: BatchResponse) => {
irc.on("batch end chathistory", (data: BatchResponse) => { console.log("batch start chathistory");
irc.chatHistory.listener.emit("chathistory", data); irc.chatHistory.eventbus.emit("batch:start", data);
});
irc.on("batch end chathistory", (data: BatchResponse) => {
console.log("batch end chathistory", data);
irc.chatHistory.eventbus.emit("batch:end", data);
}); });
initCallbacks(irc);
initListeners(irc);
initChatHistoryCommands(irc); initChatHistoryCommands(irc);
// initChatHistoryListeners(client); irc.chatHistory.eventbus = new EventEmitter();
parsed_events.use(theMiddleware); parsed_events.use(theMiddleware);
} };
function theMiddleware(command: string, event: any, client: Client, next: () => void) { function theMiddleware(command: string, event: any, client: Client, next: () => void) {
next(); next();

View file

@ -7,17 +7,19 @@ type LatestTimestamp = "*" | Timestamp;
declare module "irc-framework" { declare module "irc-framework" {
interface Client { interface Client {
chatHistory: { chatHistory: {
batches: Map<number, Promise<void>>; eventbus: EventEmitter;
addCallback: (id: number, callback: (data?: void | PromiseLike<void>) => Promise<void>) => void; before(target: string, timestamp: Timestamp, limit?: number): void;
async runCallback(id: number): void; after(target: string, timestamp: Timestamp, limit?: number): void;
latest(target: string, timestamp?: LatestTimestamp, limit?: number): void;
async before(target: string, timestamp: Timestamp, limit: number): Promise<any>; around(target: string, timestamp: Timestamp, limit?: number): void;
async after(target: string, timestamp: Timestamp, limit: number): Promise<any>; between(
async latest(target: string, timestamp: LatestTimestamp, limit: number): Promise<any>; target: string,
async around(target: string, timestamp: Timestamp, limit: number): Promise<any>; timestamp1: Timestamp,
async between(target: string, timestamp1: Timestamp, timestamp2: Timestamp, limit: number): Promise<any>; timestamp2: Timestamp,
async targets(timestamp1: Timestamp, timestamp2: Timestamp, limit: number): Promise<any>; limit?: number
async targets(target: string, timestamp: Timestamp): Promise<any>; ): void;
} targets(timestamp1: Timestamp, timestamp2: Timestamp, limit?: number): void;
targets(target: string, timestamp: Timestamp): void;
};
} }
} }

View file

@ -5,7 +5,7 @@
// https://raw.githubusercontent.com/eternagame/HTML-Chat/vue-rewrite/src/app/types/modules/irc-framework/irc-framework.d.ts // https://raw.githubusercontent.com/eternagame/HTML-Chat/vue-rewrite/src/app/types/modules/irc-framework/irc-framework.d.ts
// TODO: Fix this // TODO: Fix this
declare module "irc-framework" { declare module "irc-framework" {
import { EventEmitter } from "eventemitter3"; import {EventEmitter} from "eventemitter3";
// import { DuplexStream } from 'stream'; // import { DuplexStream } from 'stream';
import Connection from "irc-framework/src/transports/websocket"; import Connection from "irc-framework/src/transports/websocket";
@ -23,7 +23,11 @@ declare module "irc-framework" {
end: () => void; end: () => void;
}; };
export type IRCMiddleware = (client: Client, raw_events: Array<string>, parsed_events: any) => void; export type IRCMiddleware = (
client: Client,
raw_events: Array<string>,
parsed_events: any
) => void;
export interface MessageEventArgs { export interface MessageEventArgs {
account?: any; account?: any;
@ -33,7 +37,7 @@ declare module "irc-framework" {
message: string; message: string;
nick: string; nick: string;
reply: (message: string) => void; reply: (message: string) => void;
tags: { [key: string]: string }; tags: {[key: string]: string};
target: string; target: string;
time?: any; time?: any;
type: "privmsg" | "action"; // TODO type: "privmsg" | "action"; // TODO
@ -47,6 +51,7 @@ declare module "irc-framework" {
ident: string; ident: string;
nick: string; nick: string;
time?: any; time?: any;
batch?: any;
} }
export interface KickEventArgs { export interface KickEventArgs {
kicked: string; kicked: string;
@ -72,6 +77,7 @@ declare module "irc-framework" {
time?: any; time?: any;
channel?: string; channel?: string;
kicked?: string; kicked?: string;
batch?: any;
} }
interface Mode { interface Mode {
mode: string; mode: string;
@ -113,7 +119,7 @@ declare module "irc-framework" {
PREFIX: any; PREFIX: any;
CHANMODES: string; CHANMODES: string;
NICKLEN: string; NICKLEN: string;
'DRAFT/CHATHISTORY': number; "DRAFT/CHATHISTORY": number;
CHATHISTORY: number; CHATHISTORY: number;
}; };
cap: { cap: {
@ -145,8 +151,6 @@ declare module "irc-framework" {
// TODO // TODO
/** Request */ requestCap(capability: string[]): void; /** Request */ requestCap(capability: string[]): void;
use(a: IRCMiddleware): any; use(a: IRCMiddleware): any;
connect(connect_options?: Record<string, unknown>): void; connect(connect_options?: Record<string, unknown>): void;
@ -233,7 +237,7 @@ declare module "irc-framework" {
match_regex: string, match_regex: string,
cb: (event: Event) => any, cb: (event: Event) => any,
message_type: string message_type: string
): { stop: () => void }; ): {stop: () => void};
matchNotice(match_regex: string, cb: (event: Event) => any): void; matchNotice(match_regex: string, cb: (event: Event) => any): void;