Skip to content

Commit

Permalink
chore: simplify code by creating a WebsocketUtils class
Browse files Browse the repository at this point in the history
  • Loading branch information
double-beep authored May 28, 2024
1 parent 5780aae commit 4409118
Show file tree
Hide file tree
Showing 7 changed files with 184 additions and 163 deletions.
76 changes: 19 additions & 57 deletions src/UserscriptTools/ChatApi.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Store, Cached } from './Store';
import { withTimeout } from '../shared';

interface ChatWSAuthResponse {
url: string;
Expand Down Expand Up @@ -33,8 +32,6 @@ export class ChatApi {
private readonly roomId: number;
private readonly nattyId = 6817005;

private websocket: WebSocket | null = null;

public constructor(
chatUrl = 'https://chat.stackoverflow.com',
roomId = 111347
Expand Down Expand Up @@ -167,68 +164,33 @@ export class ChatApi {
});
}

public async initWS(): Promise<void> {
const l = await this.getLParam();
public async getFinalUrl(): Promise<string> {
const url = await this.getWsUrl();
const l = await this.getLParam();

const ws = `${url}?l=${l}`;
this.websocket = new WebSocket(ws);

if (Store.dryRun) {
console.log('Initialised chat WebSocket at', ws, this.websocket);
}
return `${url}?l=${l}`;
}

private closeWS(): void {
// websocket already closed
if (!this.websocket) return;

this.websocket.close();
this.websocket = null;

if (Store.dryRun) {
console.log('Chat WebSocket connection closed.');
}
}
public reportReceived(event: MessageEvent<string>): number[] {
const data = JSON.parse(event.data) as ChatWsMessage;

public async waitForReport(postId: number): Promise<void> {
if (!this.websocket || this.websocket.readyState > 1) {
this.websocket = null;
return data[`r${this.roomId}`].e
?.filter(({ event_type, user_id }) => {
// interested in new messages posted by Natty
return event_type === 1 && user_id === this.nattyId;
})
.map(item => {
const { content } = item;

if (Store.dryRun) {
console.log('Failed to connect to chat WS.');
}
if (Store.dryRun) {
console.log('New message posted by Natty on room', this.roomId, item);
}

return;
}
const matchRegex = /stackoverflow\.com\/a\/(\d+)/;
const id = matchRegex.exec(content)?.[1];

await withTimeout<void>(
10_000,
new Promise<void>(resolve => {
this.websocket?.addEventListener('message', (event: MessageEvent<string>) => {
const data = JSON.parse(event.data) as ChatWsMessage;

data[`r${this.roomId}`].e
?.filter(({ event_type, user_id }) => {
// interested in new messages posted by Natty
return event_type === 1 && user_id === this.nattyId;
})
.forEach(item => {
const { content } = item;

if (Store.dryRun) {
console.log('New message posted by Natty on room', this.roomId, item);
}

const matchRegex = /stackoverflow\.com\/a\/(\d+)/;
const id = matchRegex.exec(content)?.[1];
if (Number(id) !== postId) return;

resolve();
});
});
})
).finally(() => this.closeWS());
return Number(id);
}) || [];
}

private getChannelFKey(): Promise<string> {
Expand Down
114 changes: 35 additions & 79 deletions src/UserscriptTools/MetaSmokeAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,11 @@ import {
PostType,
delay,
getFormDataFromObject,
withTimeout,
} from '../shared';
import { Modals, Input, Buttons } from '@userscripters/stacks-helpers';
import { displayToaster, page } from '../AdvancedFlagging';
import Reporter from './Reporter';

const metasmokeReportedMessage = 'Post reported to Smokey';
const metasmokeFailureMessage = 'Failed to report post to Smokey';
import { WebsocketUtils } from './WebsocketUtils';

interface MetaSmokeApiItem {
id: number;
Expand Down Expand Up @@ -48,8 +45,18 @@ export class MetaSmokeAPI extends Reporter {
private static readonly filter = 'GGJFNNKKJFHFKJFLJLGIJMFIHNNJNINJ';
private static metasmokeIds: MetasmokeData = {};

private websocket: WebSocket | null = null;
private readonly reportMessage = 'Post reported to Smokey';
private readonly failureMessage = 'Failed to report post to Smokey';

private readonly wsUrl = 'wss://metasmoke.erwaysoftware.com/cable';
private readonly wsAuth = JSON.stringify({
identifier: JSON.stringify({
channel: 'ApiChannel',
key: MetaSmokeAPI.appKey,
events: 'posts#create'
}),
command: 'subscribe'
});

constructor(
id: number,
Expand All @@ -71,86 +78,34 @@ export class MetaSmokeAPI extends Reporter {
MetaSmokeAPI.accessToken = await MetaSmokeAPI.getUserKey();
}

private initWS(): void {
this.websocket = new WebSocket(this.wsUrl);
public reportReceived(event: MessageEvent<string>): number[] {
const data = JSON.parse(event.data) as MetasmokeWsMessage;

const auth = JSON.stringify({
identifier: JSON.stringify({
channel: 'ApiChannel',
key: MetaSmokeAPI.appKey,
events: 'posts#create'
}),
command: 'subscribe'
});

this.websocket.addEventListener('open', () => {
this.websocket?.send(auth);
});
// https://github.com/Charcoal-SE/userscripts/blob/master/sim/sim.user.js#L381-L400
if (data.type) return []; // not interested

if (Store.dryRun) {
console.log('MS WebSocket initialised.');
console.log('New post reported to Smokey', data);
}
}

private closeWS(): void {
// websocket already closed (perhaps connection failed)
if (!this.websocket || this.websocket.readyState > 1) {
this.websocket = null;
const {
object,
event_class: evClass,
event_type: type
} = data.message;

if (Store.dryRun) {
console.log('Failed to connect to metasmoke WS.');
}
// not interested
if (type !== 'create' || evClass !== 'Post') return [];

return;
}
const link = object.link;
const url = new URL(link, location.href);

this.websocket.close();
this.websocket = null;
const postId = Number(/\d+/.exec(url.pathname)?.[0]);

if (Store.dryRun) {
console.log('MS WebSocket connection closed.');
}
}

private async waitForReport(): Promise<void> {
if (!this.websocket) return;

await withTimeout(
10_000,
new Promise<void>(resolve => {
this.websocket?.addEventListener('message', (event: MessageEvent<string>) => {
const data = JSON.parse(event.data) as MetasmokeWsMessage;

// https://github.com/Charcoal-SE/userscripts/blob/master/sim/sim.user.js#L381-L400
if (data.type) return; // not interested

if (Store.dryRun) {
console.log('New post reported to Smokey', data);
}

const {
object,
event_class: evClass,
event_type: type
} = data.message;
// different sites
if (url.host !== location.host) return [];

// not interested
if (type !== 'create' || evClass !== 'Post') return;

const link = object.link;
const url = new URL(link, location.href);

const postId = Number(/\d+/.exec(url.pathname)?.[0]);

if (
url.host !== location.host // different sites
|| postId !== this.id // different posts
) return;

resolve();
});
})
).finally(() => this.closeWS());
return [ postId ];
}

private static getMetasmokeTokenPopup(): HTMLElement {
Expand Down Expand Up @@ -337,7 +292,7 @@ export class MetaSmokeAPI extends Reporter {
if (!reportRequest.ok || requestResponse !== 'OK') {
console.error(`Failed to report post ${this.smokeyId} to Smokey`, requestResponse);

throw new Error(metasmokeFailureMessage);
throw new Error(this.failureMessage);
}
}

Expand Down Expand Up @@ -371,14 +326,15 @@ export class MetaSmokeAPI extends Reporter {
// not reported, feedback is tpu AND the post isn't deleted => report it!
if (!this.smokeyId && feedback === 'tpu-' && !this.deleted) {
// see: https://chat.stackexchange.com/transcript/message/65076878
this.initWS();
const wsUtils = new WebsocketUtils(this.wsUrl, this.id, this.wsAuth);
await this.reportRedFlag();
await this.waitForReport();
await wsUtils.waitForReport(event => this.reportReceived(event));

// https://chat.stackexchange.com/transcript/message/65097399
// wait 3 seconds so that SD can start watching for post deletion
await new Promise(resolve => setTimeout(resolve, 3 * 1000));

return metasmokeReportedMessage;
return this.reportMessage;
} else if (!accessToken || !this.smokeyId) {
// user hasn't authenticated or the post hasn't been reported => don't send feedback
return '';
Expand Down
10 changes: 8 additions & 2 deletions src/UserscriptTools/NattyApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { AllFeedbacks } from '../shared';
import { page } from '../AdvancedFlagging';
import Reporter from './Reporter';
import Page from './Page';
import { WebsocketUtils } from './WebsocketUtils';

const dayMillis = 1000 * 60 * 60 * 24;
const nattyFeedbackUrl = 'https://logs.sobotics.org/napi-1.1/api/stored/';
Expand Down Expand Up @@ -92,9 +93,14 @@ export class NattyAPI extends Reporter {
// post is deleted immediately. As a result, it
// isn't reported on time to Natty.
if (StackExchange.options.user.isModerator) {
await this.chat.initWS();
// init websocket
const url = await this.chat.getFinalUrl();
const wsUtils = new WebsocketUtils(url, this.id);

await this.chat.sendMessage(this.reportMessage);
await this.chat.waitForReport(this.id);

// wait until the report is received
await wsUtils.waitForReport(event => this.chat.reportReceived(event));
} else {
await this.chat.sendMessage(this.reportMessage);
}
Expand Down
12 changes: 11 additions & 1 deletion src/UserscriptTools/Post.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { displaySuccessFlagged, displayToaster, getFlagToRaise } from '../AdvancedFlagging';
import { Flags } from '../FlagTypes';
import { getSvg, PostType, FlagNames, getFormDataFromObject, addXHRListener } from '../shared';
import { getSvg, PostType, FlagNames, getFormDataFromObject, addXHRListener, delay } from '../shared';
import { CopyPastorAPI } from './CopyPastorAPI';
import { GenericBotAPI } from './GenericBotAPI';
import { MetaSmokeAPI } from './MetaSmokeAPI';
Expand All @@ -20,6 +20,7 @@ interface StackExchangeFlagResponse {
interface StackExchangeDeleteResponse {
Success: boolean;
Message: string;
Refresh: boolean;
}

export interface Reporters {
Expand Down Expand Up @@ -148,6 +149,9 @@ export default class Post {
// flag changes the state of the post
// => reload the page
if (response.ResultChangedState) {
// wait 1 second before reloading
await delay(1000);

location.reload();
}

Expand Down Expand Up @@ -201,6 +205,12 @@ export default class Post {

throw json.Message.toLowerCase();
}

if (json.Refresh) {
await delay(1500);

location.reload();
}
}

public async comment(text: string): Promise<void> {
Expand Down
Loading

0 comments on commit 4409118

Please sign in to comment.