Add v1.0
This commit is contained in:
parent
6a5f642fb4
commit
48dffc112a
21 changed files with 1327 additions and 0 deletions
17
src/endpoints/login.ts
Normal file
17
src/endpoints/login.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import { db } from "@/main";
|
||||
import { ServerRoute } from "@hapi/hapi";
|
||||
|
||||
const data: ServerRoute = {
|
||||
method: `POST`, path: `/login`,
|
||||
async handler(request, h) {
|
||||
const { access } = request.auth.credentials as { username: string, access: string[] };
|
||||
|
||||
let channels = access.filter(x => x != "*");
|
||||
if (access.includes(`*`)) {
|
||||
channels.push(...Object.keys(db).filter(c => !channels.includes(c)));
|
||||
};
|
||||
|
||||
return h.response(channels).code(200);
|
||||
},
|
||||
};
|
||||
export default data;
|
||||
27
src/endpoints/management/create_channel.ts
Normal file
27
src/endpoints/management/create_channel.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import { ServerRoute } from "@hapi/hapi";
|
||||
import boom from "@hapi/boom";
|
||||
import { db } from "@/main";
|
||||
import Joi from "joi";
|
||||
|
||||
const data: ServerRoute = {
|
||||
method: `POST`, path: `/manage`,
|
||||
options: {
|
||||
validate: {
|
||||
payload: Joi.object({
|
||||
channel: Joi.string().alphanum(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
async handler(request, h) {
|
||||
const { channel } = request.params;
|
||||
|
||||
if (!db[channel]) {
|
||||
throw boom.notFound(`Invalid channel`);
|
||||
};
|
||||
|
||||
db[channel].lurkers = {};
|
||||
|
||||
return h.response().code(200);
|
||||
},
|
||||
};
|
||||
export default data;
|
||||
38
src/endpoints/management/create_message.ts
Normal file
38
src/endpoints/management/create_message.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import { ServerRoute } from "@hapi/hapi";
|
||||
import boom from "@hapi/boom";
|
||||
import { db } from "@/main";
|
||||
import { v4 } from "uuid";
|
||||
import Joi from "joi";
|
||||
|
||||
const data: ServerRoute = {
|
||||
method: [`POST`, `PUT`], path: `/manage/{channel}/message`,
|
||||
options: {
|
||||
validate: {
|
||||
payload: Joi.object({
|
||||
lurk: Joi.array().items(Joi.string().min(1)).min(1),
|
||||
unlurk: Joi.array().items(Joi.string().min(1)).min(1),
|
||||
}),
|
||||
params: Joi.object({
|
||||
channel: Joi.string().alphanum(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
async handler(request, h) {
|
||||
const { channel } = request.params;
|
||||
const data = request.payload as lurk_message;
|
||||
const id = v4();
|
||||
|
||||
if (!db[channel]) {
|
||||
throw boom.notFound(`Invalid channel`);
|
||||
};
|
||||
|
||||
db[channel].messages[id] = data;
|
||||
|
||||
return h.response({
|
||||
lurk: data.lurk,
|
||||
unlurk: data.unlurk,
|
||||
id,
|
||||
}).code(200);
|
||||
},
|
||||
};
|
||||
export default data;
|
||||
33
src/endpoints/management/delete_message.ts
Normal file
33
src/endpoints/management/delete_message.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import { ServerRoute } from "@hapi/hapi";
|
||||
import boom from "@hapi/boom";
|
||||
import { db } from "@/main";
|
||||
import Joi from "joi";
|
||||
|
||||
const data: ServerRoute = {
|
||||
method: `DELETE`, path: `/manage/{channel}/message/{id}`,
|
||||
options: {
|
||||
validate: {
|
||||
params: Joi.object({
|
||||
channel: Joi.string().alphanum(),
|
||||
id: Joi.string().uuid(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
async handler(request, h) {
|
||||
const { channel, id } = request.params;
|
||||
|
||||
if (!db[channel]) {
|
||||
throw boom.notFound(`Invalid channel`);
|
||||
};
|
||||
|
||||
if (!db[channel].messages[id]) {
|
||||
throw boom.notFound(`Invalid ID`);
|
||||
};
|
||||
|
||||
let message = db[channel].messages[id];
|
||||
delete db[channel].messages[id];
|
||||
|
||||
return h.response(message).code(200);
|
||||
},
|
||||
};
|
||||
export default data;
|
||||
27
src/endpoints/management/list_messages.ts
Normal file
27
src/endpoints/management/list_messages.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import { ServerRoute } from "@hapi/hapi";
|
||||
import boom from "@hapi/boom";
|
||||
import { db } from "@/main";
|
||||
|
||||
const data: ServerRoute = {
|
||||
method: `GET`, path: `/manage/{channel}`,
|
||||
async handler(request, h) {
|
||||
const { channel } = request.params;
|
||||
|
||||
if (!db[channel]) {
|
||||
throw boom.notFound(`Invalid channel`);
|
||||
};
|
||||
|
||||
let messages = [];
|
||||
for (const messageId in db[channel].messages) {
|
||||
let message = db[channel].messages[messageId];
|
||||
messages.push({
|
||||
id: messageId,
|
||||
lurk: message.lurk,
|
||||
unlurk: message.unlurk,
|
||||
});
|
||||
};
|
||||
|
||||
return h.response(messages).code(200);
|
||||
},
|
||||
};
|
||||
export default data;
|
||||
27
src/endpoints/management/reset_lurkers.ts
Normal file
27
src/endpoints/management/reset_lurkers.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import { ServerRoute } from "@hapi/hapi";
|
||||
import boom from "@hapi/boom";
|
||||
import { db } from "@/main";
|
||||
import Joi from "joi";
|
||||
|
||||
const data: ServerRoute = {
|
||||
method: `DELETE`, path: `/manage/{channel}/lurkers`,
|
||||
options: {
|
||||
validate: {
|
||||
params: Joi.object({
|
||||
channel: Joi.string().alphanum(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
async handler(request, h) {
|
||||
const { channel } = request.params;
|
||||
|
||||
if (!db[channel]) {
|
||||
throw boom.notFound(`Invalid channel`);
|
||||
};
|
||||
|
||||
db[channel].lurkers = {};
|
||||
|
||||
return h.response().code(200);
|
||||
},
|
||||
};
|
||||
export default data;
|
||||
34
src/endpoints/management/update_message.ts
Normal file
34
src/endpoints/management/update_message.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import { ServerRoute } from "@hapi/hapi";
|
||||
import boom from "@hapi/boom";
|
||||
import { db } from "@/main";
|
||||
import { v4 } from "uuid";
|
||||
import Joi from "joi";
|
||||
|
||||
const data: ServerRoute = {
|
||||
method: `PATCH`, path: `/manage/{channel}/message/{id}`,
|
||||
options: {
|
||||
validate: {
|
||||
payload: Joi.object({
|
||||
lurk: Joi.array().items(Joi.string().min(1)).min(1),
|
||||
unlurk: Joi.array().items(Joi.string().min(1)).min(1),
|
||||
}),
|
||||
params: Joi.object({
|
||||
channel: Joi.string().alphanum(),
|
||||
id: Joi.string().uuid(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
async handler(request, h) {
|
||||
const { channel, id } = request.params;
|
||||
const data = request.payload as lurk_message;
|
||||
|
||||
if (!db[channel]) {
|
||||
throw boom.notFound(`Invalid channel`);
|
||||
};
|
||||
|
||||
db[channel].messages[id] = data;
|
||||
|
||||
return h.response(`Updated message set with ID: ${id}`).code(200);
|
||||
},
|
||||
};
|
||||
export default data;
|
||||
46
src/endpoints/public/lurk.ts
Normal file
46
src/endpoints/public/lurk.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import { ServerRoute } from "@hapi/hapi";
|
||||
import { formatMessage } from "@/utils";
|
||||
import boom from "@hapi/boom";
|
||||
import { db } from "@/main";
|
||||
import Joi from "joi";
|
||||
|
||||
const data: ServerRoute = {
|
||||
method: `GET`, path: `/{channel}/lurk`,
|
||||
options: {
|
||||
auth: false,
|
||||
validate: {
|
||||
params: Joi.object({
|
||||
channel: Joi.string().alphanum(),
|
||||
}),
|
||||
query: Joi.object({
|
||||
user: Joi.string(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
async handler(request, h) {
|
||||
const { channel } = request.params;
|
||||
const { user } = request.query;
|
||||
|
||||
if (!db[channel]) {
|
||||
throw boom.notFound(`Invalid channel`);
|
||||
};
|
||||
|
||||
const messages = db[channel].messages;
|
||||
const messageIds = Object.keys(messages);
|
||||
const messageId = messageIds[Math.floor(Math.random() * messageIds.length)];
|
||||
const message = messages[messageId];
|
||||
let lurkMessage = message.lurk[Math.floor(Math.random() * message.lurk.length)];
|
||||
|
||||
db[channel].lurkers[user] = messageId;
|
||||
|
||||
let twitchMessage = formatMessage(
|
||||
lurkMessage,
|
||||
{
|
||||
user,
|
||||
}
|
||||
);
|
||||
|
||||
return h.response(twitchMessage).code(200);
|
||||
},
|
||||
};
|
||||
export default data;
|
||||
46
src/endpoints/public/unlurk.ts
Normal file
46
src/endpoints/public/unlurk.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import { ServerRoute } from "@hapi/hapi";
|
||||
import { formatMessage } from "@/utils";
|
||||
import boom from "@hapi/boom";
|
||||
import { db } from "@/main";
|
||||
import Joi from "joi";
|
||||
|
||||
const data: ServerRoute = {
|
||||
method: `GET`, path: `/{channel}/unlurk`,
|
||||
options: {
|
||||
auth: false,
|
||||
validate: {
|
||||
params: Joi.object({
|
||||
channel: Joi.string().alphanum(),
|
||||
}),
|
||||
query: Joi.object({
|
||||
user: Joi.string(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
async handler(request, h) {
|
||||
const { channel } = request.params;
|
||||
const { user } = request.query;
|
||||
|
||||
if (!db[channel]) {
|
||||
throw boom.notFound(`Invalid channel`);
|
||||
};
|
||||
|
||||
const messages = db[channel].messages;
|
||||
const messageId = db[channel].lurkers[user];
|
||||
const message = messages[messageId];
|
||||
let lurkMessage = message.unlurk[Math.floor(Math.random() * message.unlurk.length)];
|
||||
|
||||
delete db[channel].lurkers[user];
|
||||
|
||||
let twitchMessage = formatMessage(
|
||||
lurkMessage,
|
||||
{
|
||||
user,
|
||||
channel,
|
||||
}
|
||||
);
|
||||
|
||||
return h.response(twitchMessage).code(200);
|
||||
},
|
||||
};
|
||||
export default data;
|
||||
95
src/main.ts
Normal file
95
src/main.ts
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
// Filepath alias resolution
|
||||
import "module-alias/register";
|
||||
|
||||
// Begin personal code
|
||||
import { Server, Request } from "@hapi/hapi";
|
||||
import basic from "@hapi/basic";
|
||||
import path from "path";
|
||||
import glob from "glob";
|
||||
import toml from "toml";
|
||||
import fs from "fs";
|
||||
|
||||
const isDev = process.env.NODE_ENV?.startsWith(`dev`);
|
||||
|
||||
// load the config
|
||||
if (!fs.existsSync(`config.toml`)) {
|
||||
console.log(`Please fill out the config and then try starting the server again.`);
|
||||
process.exit(1);
|
||||
};
|
||||
export const config: config = toml.parse(fs.readFileSync(`config.toml`, `utf-8`));
|
||||
|
||||
|
||||
// Load the database
|
||||
if (!fs.existsSync(`data/db.json`)) {
|
||||
console.log(`Can't find database file, creating default`);
|
||||
fs.writeFileSync(`data/db.json`, `{}`);
|
||||
};
|
||||
export var db: database = JSON.parse(fs.readFileSync(`data/db.json`, `utf-8`));
|
||||
|
||||
|
||||
function saveDB() {
|
||||
console.log(`Saving database`);
|
||||
fs.writeFileSync(`data/db.json`, JSON.stringify(db, null, `\t`));
|
||||
process.exit(0);
|
||||
};
|
||||
|
||||
process.on(`SIGINT`, saveDB);
|
||||
process.on(`SIGTERM`, saveDB);
|
||||
process.on(`uncaughtException`, saveDB);
|
||||
|
||||
async function init() {
|
||||
|
||||
const server = new Server({
|
||||
port: config.server.port,
|
||||
routes: {
|
||||
cors: {
|
||||
origin: [
|
||||
isDev ? `*` : `oliver.akins.me/Twitch-Lurk-Message-API/`,
|
||||
],
|
||||
credentials: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await server.register(basic);
|
||||
server.auth.strategy(`basic`, `basic`, {
|
||||
async validate(request: Request, username: string, password: string) {
|
||||
let isValid = false;
|
||||
let user: account|undefined;
|
||||
for (const account of config.accounts) {
|
||||
if (username == account.username) {
|
||||
user = account;
|
||||
isValid = (
|
||||
password == account.password
|
||||
&& (
|
||||
request.params?.channel == null
|
||||
|| account.access.includes(`*`)
|
||||
|| account.access.includes(request.params.channel)
|
||||
)
|
||||
);
|
||||
break;
|
||||
};
|
||||
};
|
||||
|
||||
return { isValid, credentials: { username, access: user?.access } };
|
||||
},
|
||||
});
|
||||
server.auth.default(`basic`);
|
||||
|
||||
// Register all the routes
|
||||
let files = glob.sync(
|
||||
`endpoints/**/!(*.map)`,
|
||||
{ cwd: __dirname, nodir: true}
|
||||
);
|
||||
for (var file of files) {
|
||||
let route = (await import(path.join(__dirname, file))).default;
|
||||
console.log(`Registering route: ${route.method} ${route.path}`);
|
||||
server.route(route);
|
||||
};
|
||||
|
||||
server.start().then(() => {
|
||||
console.log(`Server listening on ${config.server.host}:${config.server.port}`);
|
||||
});
|
||||
};
|
||||
|
||||
init();
|
||||
14
src/types/config.d.ts
vendored
Normal file
14
src/types/config.d.ts
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
interface account {
|
||||
username: string;
|
||||
password: string;
|
||||
access: string[];
|
||||
}
|
||||
|
||||
|
||||
interface config {
|
||||
server: {
|
||||
host: string;
|
||||
port: number;
|
||||
};
|
||||
accounts: account[];
|
||||
}
|
||||
11
src/types/database.d.ts
vendored
Normal file
11
src/types/database.d.ts
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
interface lurk_message {
|
||||
lurk: string[];
|
||||
unlurk: string[];
|
||||
}
|
||||
|
||||
interface database {
|
||||
[index: string]: {
|
||||
lurkers: {[index: string]: string}
|
||||
messages: {[index: string]: lurk_message}
|
||||
};
|
||||
}
|
||||
4
src/utils.ts
Normal file
4
src/utils.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
export function formatMessage(message: string, meta: any): string {
|
||||
return message
|
||||
.replace(/\$\(user\)/, meta.user);
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue