lib/shiori_converter.ts
import * as ShioriJK from "shiorijk";
/**
* SHIORI/2.x/3.x/4.x Converter
*/
// eslint-disable-next-line import/export
export class ShioriConverter {
/**
* convert request to specified version
* @param {ShioriJK.Message.Request} request - request
* @param {string} version - protocol version
* @return {ShioriJK.Message.Request} specified version request
*/
static requestTo(request: ShioriJK.Message.Request, version: ShioriConverter.ShioriVersion) {
if (!request) throw new ShioriConverter.RequestNotSetError();
if ((request.request_line.version as string) === "4.0") {
if (version === "4.0") {
return request;
}
if (version === "3.0") {
return ShioriConverter.request4to3(request);
}
return ShioriConverter.request4to2(request);
}
if (request.request_line.version === "3.0") {
if (version === "4.0") {
return ShioriConverter.request3to4(request);
}
if (version === "3.0") {
return request;
}
return ShioriConverter.request3to2(request);
}
if (version === "4.0") {
return ShioriConverter.request2to4(request);
}
if (version === "3.0") {
return ShioriConverter.request2to3(request);
}
return request;
}
/**
* convert response to specified version
* @param {ShioriJK.Message.Request} request - request
* @param {ShioriJK.Message.Response} response - response
* @param {string} version - protocol version
* @return {ShioriJK.Message.Response} specified version response
*/
static responseTo(
request: ShioriJK.Message.Request,
response: ShioriJK.Message.Response,
version: ShioriConverter.ShioriVersion,
) {
if (!request) throw new ShioriConverter.RequestNotSetError();
if (!response) throw new ShioriConverter.ResponseNotSetError();
if ((response.status_line.version as string) === "4.0") {
if (version === "4.0") {
return response;
}
if (version === "3.0") {
return ShioriConverter.response4to3(request, response);
}
return ShioriConverter.response4to2(request, response);
}
if (response.status_line.version === "3.0") {
if (version === "4.0") {
return ShioriConverter.response3to4(request, response);
}
if (version === "3.0") {
return response;
}
return ShioriConverter.response3to2(request, response);
}
if (version === "4.0") {
return ShioriConverter.response2to4(request, response);
}
if (version === "3.0") {
return ShioriConverter.response2to3(request, response);
}
return response;
}
/**
* convert SHIORI/4.x request to SHIORI/3.x
* @param {ShioriJK.Message.Request} _request - request
* @return {ShioriJK.Message.Request} SHIORI/3.x request
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
static request4to3(_request: ShioriJK.Message.Request): ShioriJK.Message.Request {
throw new ShioriConverter.NotImplementedError();
}
/**
* convert SHIORI/4.x request to SHIORI/2.x
* @param {ShioriJK.Message.Request} request - request
* @return {ShioriJK.Message.Request} SHIORI/2.x request
*/
static request4to2(request: ShioriJK.Message.Request) {
return ShioriConverter.request3to2(ShioriConverter.request4to3(request));
}
/**
* SHIORI/3.x request method to SHIORI/2.x request method
* @param {ShioriJK.Message.Request} request - request
* @return {string} method
*/
static method3to2(request: ShioriJK.Message.Request) {
const id = request.headers.header.ID;
if (id === "version") {
return "GET Version";
}
if (id === "OnTeach") {
return "TEACH";
}
if (id === "ownerghostname") {
return "NOTIFY OwnerGhostName";
}
if (id === "otherghostname") {
return "NOTIFY OtherGhostName";
}
if (id === "OnTranslate") {
return "TRANSLATE Sentence";
}
if (request.request_line.method === "NOTIFY") {
// No SHIORI 2.x Event
} else if (id && id.match(/^[a-z]/)) {
return "GET String"; // default SHIORI/2.5
} else {
return "GET Sentence"; // default SHIORI/2.2
}
return undefined;
}
/**
* convert SHIORI/3.x request to SHIORI/2.x
* @param {ShioriJK.Message.Request} request - request
* @return {ShioriJK.Message.Request} SHIORI/2.x request
*/
static request3to2(request: ShioriJK.Message.Request) {
/*
SHIORI/2.x互換変換
- GET : Sentence : OnCommunicate はGET Sentence SHIORI/2.3に変換され、ヘッダの位置が変更されます。
- GET : TEACH : OnTeach はTEACH SHIORI/2.4に変換され、ヘッダの位置が変更されます。
*/
const method = ShioriConverter.method3to2(request);
if (!method) {
return undefined; // No SHIORI 2.x Event
}
const id = request.headers.header.ID;
const ignoreHeaders = ["ID"];
let referenceOffset = 0;
const headers = new ShioriJK.Headers.Request();
const convRequest = new ShioriJK.Message.Request({
request_line: new ShioriJK.RequestLine({
method,
protocol: request.request_line.protocol,
version: method === "TEACH" ? "2.4" : "2.6",
}),
headers,
});
if (method === "GET Sentence" && id != null) {
if (id === "OnCommunicate") {
// SHIORI/2.3b
headers.header.Sender = request.headers.header.Reference0;
ignoreHeaders.push("Sender");
headers.header.Sentence = request.headers.header.Reference1;
headers.header.Age = request.headers.header.Age || "0";
// ref0,1のためにヘッダをずらす
referenceOffset = -2;
} else {
// SHIORI/2.2
headers.header.Event = id;
}
} else if (method === "GET String" && id != null) {
// SHIORI/2.5
ignoreHeaders.pop(); // headers.header["ID"] = id;
} else if (method === "TEACH") {
// SHIORI/2.4
headers.header.Word = request.headers.header.Reference0;
// ref0のためにヘッダをずらす
referenceOffset = -1;
} else if (method === "NOTIFY OwnerGhostName") {
// SHIORI/2.0 NOTIFY
headers.header.Ghost = request.headers.header.Reference0;
ignoreHeaders.push("Reference0");
} else if (method === "NOTIFY OtherGhostName") {
// SHIORI/2.3 NOTIFY
const ghosts: string[] = [];
for (const name of Object.keys(request.headers.header)) {
const value = request.headers.header[name];
if (name.match(/^Reference\d+$/)) {
ghosts.push(`${value}`);
} else {
headers.header[name] = `${value}`;
}
}
const ghostsHeaders = ghosts.map(ghost => `GhostEx: ${ghost}\r\n`).join("");
delete headers.header.ID;
// ShioriJK.Headersが同一名複数ヘッダに対応していないため
return `${convRequest.request_line}\r\n${headers}${ghostsHeaders}\r\n`;
} else if (method === "TRANSLATE Sentence") {
// SHIORI/2.6
headers.header.Sentence = request.headers.header.Reference0;
// ref0のためにヘッダをずらす
referenceOffset = -1;
} else {
// include GET Version
return undefined;
}
if (referenceOffset) {
for (const name in request.headers.header) {
if (ignoreHeaders.indexOf(name) !== -1) continue;
const value = request.headers.header[name];
const result = name.match(/^Reference(\d+)$/);
if (result) {
const index = Number(result[1]) + referenceOffset;
if (index >= 0) headers.header[`Reference${index}`] = `${value}`;
} else {
headers.header[name] = `${value}`;
}
}
} else {
for (const name in request.headers.header) {
if (ignoreHeaders.indexOf(name) !== -1) continue;
const value = request.headers.header[name];
headers.header[name] = `${value}`;
}
}
return convRequest;
}
/**
* convert SHIORI/2.x request to SHIORI/3.x
* @param {ShioriJK.Message.Request} _request - request
* @return {ShioriJK.Message.Request} SHIORI/3.x request
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
static request2to3(_request: ShioriJK.Message.Request): ShioriJK.Message.Request {
throw new ShioriConverter.NotImplementedError();
}
/**
* convert SHIORI/3.x request to SHIORI/4.x
* @param {ShioriJK.Message.Request} _request - request
* @return {ShioriJK.Message.Request} SHIORI/4.x request
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
static request3to4(_request: ShioriJK.Message.Request): ShioriJK.Message.Request {
throw new ShioriConverter.NotImplementedError();
}
/**
* convert SHIORI/2.x request to SHIORI/4.x
* @param {ShioriJK.Message.Request} request - request
* @return {ShioriJK.Message.Request} SHIORI/4.x request
*/
static request2to4(request: ShioriJK.Message.Request) {
return ShioriConverter.request3to4(ShioriConverter.request2to3(request));
}
/**
* convert SHIORI/4.x response to SHIORI/3.x
* @param {ShioriJK.Message.Request} _request - request
* @param {ShioriJK.Message.Response} _response - response
* @return {ShioriJK.Message.Response} SHIORI/3.x response
*/
static response4to3(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_request: ShioriJK.Message.Request,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_response: ShioriJK.Message.Response,
): ShioriJK.Message.Response {
throw new ShioriConverter.NotImplementedError();
}
/**
* convert SHIORI/4.x response to SHIORI/2.x
* @param {ShioriJK.Message.Request} request - request
* @param {ShioriJK.Message.Response} response - response
* @return {ShioriJK.Message.Response} SHIORI/2.x response
*/
static response4to2(request: ShioriJK.Message.Request, response: ShioriJK.Message.Response) {
return ShioriConverter.response3to2(request, ShioriConverter.response4to3(request, response));
}
/**
* convert SHIORI/3.x response to SHIORI/2.x
* @param {ShioriJK.Message.Request} _request - request
* @param {ShioriJK.Message.Response} _response - response
* @return {ShioriJK.Message.Response} SHIORI/2.x response
*/
static response3to2(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_request: ShioriJK.Message.Request,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_response: ShioriJK.Message.Response,
): ShioriJK.Message.Response {
throw new ShioriConverter.NotImplementedError();
}
/**
* convert SHIORI/2.x response to SHIORI/3.x
* @param {ShioriJK.Message.Request} request - request
* @param {ShioriJK.Message.Response} response - response
* @return {ShioriJK.Message.Response} SHIORI/3.x response
*/
static response2to3(request: ShioriJK.Message.Request, response: ShioriJK.Message.Response) {
const convRequest = ShioriConverter.requestTo(request, "2.6");
let valueHeaderName: ShioriConverter.Shiori2ValueHeader;
if (convRequest instanceof ShioriJK.Message.Request) {
switch (convRequest.request_line.method) {
case "GET String":
valueHeaderName = "String";
break;
case "GET Word":
valueHeaderName = "Word";
break;
case "GET Status":
valueHeaderName = "Status";
break;
default:
valueHeaderName = "Sentence";
break;
}
} else if (convRequest) {
// NOTIFY OtherGhostName
valueHeaderName = "String"; // NOTIFYなのでないはずだがとりあえず
} else {
valueHeaderName = "Sentence";
}
const headers = new ShioriJK.Headers.Response();
if (response.headers.header[valueHeaderName] != null) {
headers.header.Value = response.headers.header[valueHeaderName];
}
for (const name of Object.keys(response.headers.header)) {
const value = response.headers.header[name];
// for Communicate
const result = name.match(/^Reference(\d+)$/);
if (result) {
headers.header[`Reference${Number(result[1]) + 1}`] = value;
} else if (name === "To") {
headers.header.Reference0 = value;
} else if (name !== valueHeaderName && name !== "Age") {
headers.header[name] = value;
}
}
return new ShioriJK.Message.Response({
status_line: new ShioriJK.StatusLine({
code: response.status_line.code,
protocol: response.status_line.protocol,
version: "3.0",
}),
headers,
});
}
/**
* convert SHIORI/3.x response to SHIORI/4.x
* @param {ShioriJK.Message.Request} _request - request
* @param {ShioriJK.Message.Response} _response - response
* @return {ShioriJK.Message.Response} SHIORI/4.x response
*/
static response3to4(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_request: ShioriJK.Message.Request,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_response: ShioriJK.Message.Response,
): ShioriJK.Message.Response {
throw new ShioriConverter.NotImplementedError();
}
/**
* convert SHIORI/2.x response to SHIORI/4.x
* @param {ShioriJK.Message.Request} request - request
* @param {ShioriJK.Message.Response} response - response
* @return {ShioriJK.Message.Response} SHIORI/4.x response
*/
static response2to4(
request: ShioriJK.Message.Request,
response: ShioriJK.Message.Response,
): ShioriJK.Message.Response {
return ShioriConverter.response3to4(request, ShioriConverter.response2to3(request, response));
}
}
// eslint-disable-next-line no-redeclare, import/export
export namespace ShioriConverter {
/** Shiori Protocol version */
export type ShioriVersion = "2.6" | "3.0" | "4.0";
/** SHIORI/2.x's value header name */
export type Shiori2ValueHeader = "String" | "Word" | "Status" | "Sentence";
/**
* Request not found error
*/
export class RequestNotSetError extends Error {}
/**
* Response not found error
*/
export class ResponseNotSetError extends Error {}
/**
* Sorry, not implemented!
*/
export class NotImplementedError extends Error {}
}