Initial commit
This commit is contained in:
commit
64b5af1219
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
/node_modules/
|
||||||
|
/.out/
|
||||||
|
deployment.json
|
||||||
|
*.js
|
||||||
|
*.d.ts
|
||||||
|
*.tsbuildinfo
|
146
package-lock.json
generated
Normal file
146
package-lock.json
generated
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
{
|
||||||
|
"name": "service-syncplay",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"lockfileVersion": 1,
|
||||||
|
"requires": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@squeebot/core": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@squeebot/core/-/core-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-VtXtEFVYEAGckWhPdpPIKRyVblaGWEn/7/RidkMW4zwBVuiwZrCdzSw/QA00nP9E1R1BPgym+J7YjJA450mDug==",
|
||||||
|
"requires": {
|
||||||
|
"dateformat": "^4.0.0",
|
||||||
|
"fs-extra": "^9.0.1",
|
||||||
|
"semver": "^7.3.2",
|
||||||
|
"tar": "^6.0.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@types/node": {
|
||||||
|
"version": "14.14.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.13.tgz",
|
||||||
|
"integrity": "sha512-vbxr0VZ8exFMMAjCW8rJwaya0dMCDyYW2ZRdTyjtrCvJoENMpdUHOT/eTzvgyA5ZnqRZ/sI0NwqAxNHKYokLJQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"at-least-node": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg=="
|
||||||
|
},
|
||||||
|
"chownr": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="
|
||||||
|
},
|
||||||
|
"dateformat": {
|
||||||
|
"version": "4.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.3.1.tgz",
|
||||||
|
"integrity": "sha512-xhq1wI5BQ0TMJDvio0BLP8lNeYlhAvmh/7H52H9n6kfzqSmRpIhH5KEIjJ7onFEAh5CQVrAP2MAG8wZ6j0BKzQ=="
|
||||||
|
},
|
||||||
|
"fs-extra": {
|
||||||
|
"version": "9.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz",
|
||||||
|
"integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==",
|
||||||
|
"requires": {
|
||||||
|
"at-least-node": "^1.0.0",
|
||||||
|
"graceful-fs": "^4.2.0",
|
||||||
|
"jsonfile": "^6.0.1",
|
||||||
|
"universalify": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fs-minipass": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
|
||||||
|
"requires": {
|
||||||
|
"minipass": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"graceful-fs": {
|
||||||
|
"version": "4.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
|
||||||
|
"integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw=="
|
||||||
|
},
|
||||||
|
"jsonfile": {
|
||||||
|
"version": "6.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
|
||||||
|
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
|
||||||
|
"requires": {
|
||||||
|
"graceful-fs": "^4.1.6",
|
||||||
|
"universalify": "^2.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"universalify": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lru-cache": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||||
|
"requires": {
|
||||||
|
"yallist": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minipass": {
|
||||||
|
"version": "3.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz",
|
||||||
|
"integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==",
|
||||||
|
"requires": {
|
||||||
|
"yallist": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minizlib": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
|
||||||
|
"requires": {
|
||||||
|
"minipass": "^3.0.0",
|
||||||
|
"yallist": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mkdirp": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="
|
||||||
|
},
|
||||||
|
"semver": {
|
||||||
|
"version": "7.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz",
|
||||||
|
"integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==",
|
||||||
|
"requires": {
|
||||||
|
"lru-cache": "^6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tar": {
|
||||||
|
"version": "6.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/tar/-/tar-6.0.5.tgz",
|
||||||
|
"integrity": "sha512-0b4HOimQHj9nXNEAA7zWwMM91Zhhba3pspja6sQbgTpynOJf+bkjBnfybNYzbpLbnwXnbyB4LOREvlyXLkCHSg==",
|
||||||
|
"requires": {
|
||||||
|
"chownr": "^2.0.0",
|
||||||
|
"fs-minipass": "^2.0.0",
|
||||||
|
"minipass": "^3.0.0",
|
||||||
|
"minizlib": "^2.1.1",
|
||||||
|
"mkdirp": "^1.0.3",
|
||||||
|
"yallist": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"typescript": {
|
||||||
|
"version": "4.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.3.tgz",
|
||||||
|
"integrity": "sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg=="
|
||||||
|
},
|
||||||
|
"universalify": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug=="
|
||||||
|
},
|
||||||
|
"yallist": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
package.json
Normal file
20
package.json
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"name": "service-syncplay",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc",
|
||||||
|
"watch": "tsc -w"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@squeebot/core": "^3.1.0",
|
||||||
|
"typescript": "^4.1.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^14.14.13"
|
||||||
|
}
|
||||||
|
}
|
14
squeebot.repo.json
Normal file
14
squeebot.repo.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"name": "service-syncplay",
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "syncplay",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "syncplaystatus",
|
||||||
|
"version": "1.0.0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"typescript": true
|
||||||
|
}
|
9
syncplay/plugin.json
Normal file
9
syncplay/plugin.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"main": "plugin.js",
|
||||||
|
"name": "syncplay",
|
||||||
|
"description": "Syncplay Service for Squeebot 3",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"tags": ["service", "syncplay"],
|
||||||
|
"dependencies": ["control?"],
|
||||||
|
"npmDependencies": []
|
||||||
|
}
|
362
syncplay/plugin.ts
Normal file
362
syncplay/plugin.ts
Normal file
@ -0,0 +1,362 @@
|
|||||||
|
import {
|
||||||
|
Plugin,
|
||||||
|
Configurable,
|
||||||
|
EventListener,
|
||||||
|
InjectService
|
||||||
|
} from '@squeebot/core/lib/plugin';
|
||||||
|
|
||||||
|
import { logger } from '@squeebot/core/lib/core';
|
||||||
|
import {
|
||||||
|
EMessageType,
|
||||||
|
Formatter,
|
||||||
|
IMessage,
|
||||||
|
IMessageTarget,
|
||||||
|
Protocol
|
||||||
|
} from '@squeebot/core/lib/types';
|
||||||
|
|
||||||
|
import util from 'util';
|
||||||
|
import crypto from 'crypto';
|
||||||
|
import net, { Socket } from 'net';
|
||||||
|
|
||||||
|
const PROTOCOL_VERSION = '1.6.7';
|
||||||
|
|
||||||
|
interface SyncplayFeatures {
|
||||||
|
isolateRooms?: boolean;
|
||||||
|
readiness?: boolean;
|
||||||
|
managedRooms?: boolean;
|
||||||
|
chat?: boolean;
|
||||||
|
maxChatMessageLength?: number;
|
||||||
|
maxUsernameLength?: number;
|
||||||
|
maxRoomNameLength?: number;
|
||||||
|
maxFilenameLength?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SyncplayHello {
|
||||||
|
username?: string;
|
||||||
|
room?: {
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
version?: string;
|
||||||
|
realversion?: string;
|
||||||
|
motd?: string;
|
||||||
|
features?: SyncplayFeatures;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SyncplayUser {
|
||||||
|
position: number;
|
||||||
|
file: any;
|
||||||
|
controller: boolean;
|
||||||
|
isReady: boolean;
|
||||||
|
features: {
|
||||||
|
sharedPlaylists: boolean;
|
||||||
|
chat: boolean;
|
||||||
|
featureList: boolean;
|
||||||
|
readiness: boolean;
|
||||||
|
managedRooms: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class SyncplayMessage implements IMessage {
|
||||||
|
public time: Date = new Date();
|
||||||
|
public resolved = false;
|
||||||
|
public direct = false;
|
||||||
|
public guest = false;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public type: EMessageType,
|
||||||
|
public data: any,
|
||||||
|
public source: Protocol,
|
||||||
|
public sender: IMessageTarget,
|
||||||
|
public target?: IMessageTarget) {}
|
||||||
|
|
||||||
|
public get fullSenderID(): string {
|
||||||
|
return this.source.fullName + '/' + this.sender.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get fullRoomID(): string {
|
||||||
|
if (!this.target) {
|
||||||
|
return this.source.fullName;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.source.fullName + '/' + this.target.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get text(): string {
|
||||||
|
return this.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public resolve(...args: any[]): void {
|
||||||
|
this.resolved = true;
|
||||||
|
this.source.resolve(this, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public reject(error: Error): void {
|
||||||
|
this.resolved = true;
|
||||||
|
this.source.resolve(this, error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public mention(user: IMessageTarget): string {
|
||||||
|
return user.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SyncplayProtocol extends Protocol {
|
||||||
|
public format: Formatter = new Formatter();
|
||||||
|
public type = 'SyncplayProtocol';
|
||||||
|
|
||||||
|
public users: {[key: string]: SyncplayUser} = {};
|
||||||
|
public info: SyncplayHello = {};
|
||||||
|
public socket: Socket | null = null;
|
||||||
|
public file = '';
|
||||||
|
|
||||||
|
private fetchList(): void {
|
||||||
|
if (!this.socket || !this.running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.write({ List: null });
|
||||||
|
setTimeout(() => this.fetchList(), 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleServerLine(obj: any, raw: any): void {
|
||||||
|
// Save information from hello
|
||||||
|
if (obj.Hello) {
|
||||||
|
this.info = obj.Hello;
|
||||||
|
this.fetchList();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return pings
|
||||||
|
if (obj.State && obj.State.ping) {
|
||||||
|
this.write({
|
||||||
|
State: {
|
||||||
|
ignoringOnTheFly: obj.State.ignoringOnTheFly,
|
||||||
|
ping: {
|
||||||
|
clientRtt: 0,
|
||||||
|
clientLatencyCalculation: Date.now() / 1000,
|
||||||
|
latencyCalculation: obj.State.ping.latencyCalculation
|
||||||
|
},
|
||||||
|
playstate: {
|
||||||
|
paused: obj.State.playstate.paused,
|
||||||
|
position: obj.State.playstate.position
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle chat
|
||||||
|
if (obj.Chat && obj.Chat.message && obj.Chat.username !== this.config.syncplay.name) {
|
||||||
|
const newMessage = new SyncplayMessage(
|
||||||
|
EMessageType.message,
|
||||||
|
obj.Chat.message,
|
||||||
|
this,
|
||||||
|
{ name: obj.Chat.username, id: obj.Chat.username },
|
||||||
|
{ name: this.config.syncplay.room, id: this.config.syncplay.room });
|
||||||
|
this.plugin.stream.emitTo('channel', 'message', newMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set file
|
||||||
|
if (obj.Set && obj.Set.file) {
|
||||||
|
this.file = obj.Set.file;
|
||||||
|
this.write({
|
||||||
|
Set: { file: obj.Set.file }
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// List users
|
||||||
|
const room = this.config.syncplay.room;
|
||||||
|
if (obj.List && obj.List[room]) {
|
||||||
|
this.users = obj.List[room];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Forward errors
|
||||||
|
if (obj.Error) {
|
||||||
|
this.emit('error', new Error(obj.Error.message));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public start(...args: any[]): void {
|
||||||
|
this.me = {
|
||||||
|
name: this.config.syncplay.name,
|
||||||
|
id: this.config.syncplay.name
|
||||||
|
};
|
||||||
|
|
||||||
|
const opts = {
|
||||||
|
host: this.config.syncplay.host,
|
||||||
|
port: this.config.syncplay.port
|
||||||
|
};
|
||||||
|
|
||||||
|
let password: string | null = this.config.syncplay.password;
|
||||||
|
if (password != null && password !== '') {
|
||||||
|
password = crypto.createHash('md5').update(password).digest('hex');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.socket = net.connect(opts, () => {
|
||||||
|
this.write({
|
||||||
|
Hello: {
|
||||||
|
username: this.config.syncplay.name,
|
||||||
|
password,
|
||||||
|
room: { name: this.config.syncplay.room },
|
||||||
|
version: PROTOCOL_VERSION
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
let buffer: any = '';
|
||||||
|
this.socket.on('data', (chunk) => {
|
||||||
|
buffer += chunk;
|
||||||
|
const data = buffer.split('\r\n');
|
||||||
|
buffer = data.pop();
|
||||||
|
|
||||||
|
data.forEach((line: string) => {
|
||||||
|
// Parse the line
|
||||||
|
const parsed = JSON.parse(line);
|
||||||
|
|
||||||
|
// Handle the line
|
||||||
|
this.handleServerLine(parsed, line);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.socket.on('close', (data) => this.stop(true));
|
||||||
|
this.socket.on('error', (data: Error) => {
|
||||||
|
this.emit('error', data);
|
||||||
|
this.stop(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.running = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
write(obj: any): void {
|
||||||
|
if (!this.socket || !this.running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const toSend = JSON.stringify(obj);
|
||||||
|
this.socket.write(toSend + '\r\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
public stop(force = false): void {
|
||||||
|
if (!this.running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.running = false;
|
||||||
|
this.stopped = true;
|
||||||
|
|
||||||
|
if (this.socket) {
|
||||||
|
this.socket.destroy();
|
||||||
|
this.socket = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (force) {
|
||||||
|
this.failed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.emit('stopped');
|
||||||
|
}
|
||||||
|
|
||||||
|
public resolve(msg: any, ...data: any[]): void {
|
||||||
|
let response = util.format(data[0], ...data.slice(1));
|
||||||
|
if (!response || !this.socket) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(data[0])) {
|
||||||
|
try {
|
||||||
|
response = this.format.compose(data[0]);
|
||||||
|
} catch (e) {
|
||||||
|
logger.error('[%s] Failed to compose message:', this.fullName, e.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send lines and max length exceeding messages separately
|
||||||
|
if (!this.info || !this.info.features) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxlen = this.info.features.maxChatMessageLength as number;
|
||||||
|
const splitup = response.split('\n');
|
||||||
|
const toSend = [];
|
||||||
|
for (const line of splitup) {
|
||||||
|
for (let j = 0, len = line.length; j < len; j += maxlen) {
|
||||||
|
toSend.push(line.substring(j, j + maxlen));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toSend.forEach((line: string) => this.write({ Chat: line }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
{
|
||||||
|
name: 'syncplay',
|
||||||
|
syncplay: {
|
||||||
|
name: 'Squeebot',
|
||||||
|
host: 'syncplay.pl',
|
||||||
|
port: 8999,
|
||||||
|
room: '',
|
||||||
|
password: null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
@InjectService(SyncplayProtocol)
|
||||||
|
@Configurable({instances: []})
|
||||||
|
class SyncplayServicePlugin extends Plugin {
|
||||||
|
initialize(): void {
|
||||||
|
const protoList = this.validateConfiguration();
|
||||||
|
this.startAll(protoList);
|
||||||
|
}
|
||||||
|
|
||||||
|
private startAll(list: any[]): void {
|
||||||
|
for (const ins of list) {
|
||||||
|
const newProto = new SyncplayProtocol(this, ins);
|
||||||
|
logger.log('[%s] Starting Syncplay service "%s".', this.name, ins.name);
|
||||||
|
this.monitor(newProto);
|
||||||
|
this.service?.use(newProto, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private monitor(proto: Protocol): void {
|
||||||
|
proto.on('running', () => this.emit('protocolNew', proto));
|
||||||
|
proto.on('stopped', () => this.emit('protocolExit', proto));
|
||||||
|
}
|
||||||
|
|
||||||
|
private validateConfiguration(): any[] {
|
||||||
|
if (!this.config.config.instances) {
|
||||||
|
throw new Error('Configuration incomplete!');
|
||||||
|
}
|
||||||
|
|
||||||
|
const instances = this.config.config.instances;
|
||||||
|
const runnables: any[] = [];
|
||||||
|
for (const ins of instances) {
|
||||||
|
if (ins.enabled === false) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!ins.name || !ins.syncplay || !ins.syncplay.name ||
|
||||||
|
!ins.syncplay.host || !ins.syncplay.room) {
|
||||||
|
throw new Error('Invalid instance configuration!');
|
||||||
|
}
|
||||||
|
|
||||||
|
runnables.push(ins);
|
||||||
|
}
|
||||||
|
|
||||||
|
return runnables;
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventListener('pluginUnload')
|
||||||
|
public unloadEventHandler(plugin: string | Plugin): void {
|
||||||
|
if (plugin === this.name || plugin === this) {
|
||||||
|
this.config.save().then(() =>
|
||||||
|
this.service?.stopAll().then(() =>
|
||||||
|
this.emit('pluginUnloaded', this)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = SyncplayServicePlugin;
|
9
syncplaystatus/plugin.json
Normal file
9
syncplaystatus/plugin.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"main": "plugin.js",
|
||||||
|
"name": "syncplaystatus",
|
||||||
|
"description": "Display status of a Syncplay protocol connection",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"tags": ["syncplay", "commands", "status"],
|
||||||
|
"dependencies": ["syncplay", "simplecommands"],
|
||||||
|
"npmDependencies": []
|
||||||
|
}
|
119
syncplaystatus/plugin.ts
Normal file
119
syncplaystatus/plugin.ts
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
import {
|
||||||
|
Plugin,
|
||||||
|
Configurable,
|
||||||
|
EventListener,
|
||||||
|
DependencyLoad,
|
||||||
|
DependencyUnload
|
||||||
|
} from '@squeebot/core/lib/plugin';
|
||||||
|
|
||||||
|
import { IMessage, MessageResolver } from '@squeebot/core/lib/types';
|
||||||
|
import { fullIDMatcher } from '@squeebot/core/lib/common';
|
||||||
|
|
||||||
|
interface Monitor {
|
||||||
|
rooms: string[];
|
||||||
|
protocol: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Monitor:
|
||||||
|
{
|
||||||
|
rooms: ['irc/icynet/#diamond'],
|
||||||
|
protocol: 'syncplay'
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Configurable({
|
||||||
|
monitors: []
|
||||||
|
})
|
||||||
|
class SPSPlugin extends Plugin {
|
||||||
|
public syncPlugin: Plugin | null = null;
|
||||||
|
|
||||||
|
@EventListener('pluginUnload')
|
||||||
|
public unloadEventHandler(plugin: string | Plugin): void {
|
||||||
|
if (plugin === this.name || plugin === this) {
|
||||||
|
this.config.save().then(() =>
|
||||||
|
this.emit('pluginUnloaded', this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@DependencyLoad('simplecommands')
|
||||||
|
addCommands(cmd: any): void {
|
||||||
|
const rooms: string[] = [];
|
||||||
|
this.config.get('monitors', []).forEach(
|
||||||
|
(monitor: Monitor) => rooms.push(...monitor.rooms));
|
||||||
|
|
||||||
|
if (!rooms.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.registerCommand({
|
||||||
|
name: 'syncplay',
|
||||||
|
plugin: this.name,
|
||||||
|
execute: async (msg: IMessage, msr: MessageResolver, spec: any, prefix: string, ...simplified: any[]): Promise<boolean> => {
|
||||||
|
if (!msg.fullRoomID || !this.syncPlugin) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
let monitor: Monitor | undefined;
|
||||||
|
this.config.get('monitors', []).forEach((mon: Monitor) => {
|
||||||
|
let matched = false;
|
||||||
|
for (const r of mon.rooms) {
|
||||||
|
if (fullIDMatcher(msg.fullRoomID as string, r)) {
|
||||||
|
matched = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (matched) {
|
||||||
|
monitor = mon;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!monitor) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const syncServ = this.syncPlugin.service?.getProtocolByName(monitor.protocol) as any;
|
||||||
|
if (!syncServ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const keys = [];
|
||||||
|
|
||||||
|
keys.push(['field', 'Syncplay', { color: 'orange', type: 'title' }]);
|
||||||
|
|
||||||
|
const realViews = Object.keys(syncServ.users).length - 1;
|
||||||
|
const cfg = syncServ.config.syncplay;
|
||||||
|
|
||||||
|
if (simplified[0] && simplified[0].toLowerCase() === 'users') {
|
||||||
|
const w = Object.keys(syncServ.users).filter((x) => x !== cfg.name);
|
||||||
|
|
||||||
|
keys.push(['field', realViews > 0 ?
|
||||||
|
w.join(', ') : 'No users', { label: 'Users online' }]);
|
||||||
|
|
||||||
|
msg.resolve(keys);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
keys.push(['field', cfg.room, { label: 'Room', type: 'description' }]);
|
||||||
|
keys.push(['field', `${cfg.host}:${cfg.port}`, { label: 'Address' }]);
|
||||||
|
keys.push(['field', realViews, { label: 'Users' }]);
|
||||||
|
|
||||||
|
msg.resolve(keys);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
usage: '[users]',
|
||||||
|
description: 'Show Syncplay status',
|
||||||
|
source: rooms,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@DependencyLoad('syncplay')
|
||||||
|
syncplayReady(plugin: any): void {
|
||||||
|
this.syncPlugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@DependencyUnload('syncplay')
|
||||||
|
syncplayUnready(): void {
|
||||||
|
this.syncPlugin = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = SPSPlugin;
|
1
tsconfig.json
Normal file
1
tsconfig.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"compilerOptions":{"downlevelIteration":true,"esModuleInterop":true,"experimentalDecorators":true,"forceConsistentCasingInFileNames":true,"skipLibCheck":true,"sourceMap":false,"strict":true,"target":"es5"}}
|
122
tslint.json
Normal file
122
tslint.json
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
{
|
||||||
|
"extends": "tslint:recommended",
|
||||||
|
"rules": {
|
||||||
|
"align": {
|
||||||
|
"options": [
|
||||||
|
"parameters",
|
||||||
|
"statements"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"array-type": false,
|
||||||
|
"arrow-return-shorthand": true,
|
||||||
|
"curly": true,
|
||||||
|
"deprecation": {
|
||||||
|
"severity": "warning"
|
||||||
|
},
|
||||||
|
"eofline": true,
|
||||||
|
"import-spacing": true,
|
||||||
|
"indent": {
|
||||||
|
"options": [
|
||||||
|
"spaces"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"max-classes-per-file": false,
|
||||||
|
"max-line-length": [
|
||||||
|
true,
|
||||||
|
140
|
||||||
|
],
|
||||||
|
"member-ordering": [
|
||||||
|
true,
|
||||||
|
{
|
||||||
|
"order": [
|
||||||
|
"static-field",
|
||||||
|
"instance-field",
|
||||||
|
"static-method",
|
||||||
|
"instance-method"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"no-console": [
|
||||||
|
true,
|
||||||
|
"debug",
|
||||||
|
"info",
|
||||||
|
"time",
|
||||||
|
"timeEnd",
|
||||||
|
"trace"
|
||||||
|
],
|
||||||
|
"no-empty": false,
|
||||||
|
"no-inferrable-types": [
|
||||||
|
true,
|
||||||
|
"ignore-params"
|
||||||
|
],
|
||||||
|
"no-non-null-assertion": false,
|
||||||
|
"no-redundant-jsdoc": true,
|
||||||
|
"no-switch-case-fall-through": true,
|
||||||
|
"no-var-requires": false,
|
||||||
|
"object-literal-key-quotes": [
|
||||||
|
true,
|
||||||
|
"as-needed"
|
||||||
|
],
|
||||||
|
"quotemark": [
|
||||||
|
true,
|
||||||
|
"single"
|
||||||
|
],
|
||||||
|
"semicolon": {
|
||||||
|
"options": [
|
||||||
|
"always"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"space-before-function-paren": {
|
||||||
|
"options": {
|
||||||
|
"anonymous": "never",
|
||||||
|
"asyncArrow": "always",
|
||||||
|
"constructor": "never",
|
||||||
|
"method": "never",
|
||||||
|
"named": "never"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"typedef": [
|
||||||
|
true,
|
||||||
|
"call-signature"
|
||||||
|
],
|
||||||
|
"forin": false,
|
||||||
|
"ban-types": {
|
||||||
|
"function": false
|
||||||
|
},
|
||||||
|
"typedef-whitespace": {
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"call-signature": "nospace",
|
||||||
|
"index-signature": "nospace",
|
||||||
|
"parameter": "nospace",
|
||||||
|
"property-declaration": "nospace",
|
||||||
|
"variable-declaration": "nospace"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"call-signature": "onespace",
|
||||||
|
"index-signature": "onespace",
|
||||||
|
"parameter": "onespace",
|
||||||
|
"property-declaration": "onespace",
|
||||||
|
"variable-declaration": "onespace"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"variable-name": {
|
||||||
|
"options": [
|
||||||
|
"ban-keywords",
|
||||||
|
"check-format",
|
||||||
|
"allow-pascal-case"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"whitespace": {
|
||||||
|
"options": [
|
||||||
|
"check-branch",
|
||||||
|
"check-decl",
|
||||||
|
"check-operator",
|
||||||
|
"check-separator",
|
||||||
|
"check-type",
|
||||||
|
"check-typecast"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user