chore: update prettier tab width for consistency (#175)

This commit is contained in:
41666 2021-03-13 22:54:34 -05:00 committed by GitHub
parent a931f8c69c
commit f24d2fcc99
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
247 changed files with 7224 additions and 7375 deletions

View file

@ -1,6 +1,6 @@
import {
evaluatePermission,
permissions as Permissions,
evaluatePermission,
permissions as Permissions,
} from '@roleypoly/misc-utils/hasPermission';
import { SessionData, UserGuildPermissions } from '@roleypoly/types';
import KSUID from 'ksuid';
@ -9,207 +9,204 @@ import { allowedCallbackHosts, apiPublicURI, rootUsers } from './config';
import { Sessions, WrappedKVNamespace } from './kv';
export const formData = (obj: Record<string, any>): string => {
return Object.keys(obj)
.map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(obj[key])}`)
.join('&');
return Object.keys(obj)
.map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(obj[key])}`)
.join('&');
};
export const addCORS = (init: ResponseInit = {}) => ({
...init,
headers: {
...(init.headers || {}),
'access-control-allow-origin': '*',
'access-control-allow-methods': '*',
'access-control-allow-headers': '*',
},
...init,
headers: {
...(init.headers || {}),
'access-control-allow-origin': '*',
'access-control-allow-methods': '*',
'access-control-allow-headers': '*',
},
});
export const respond = (obj: Record<string, any>, init: ResponseInit = {}) =>
new Response(JSON.stringify(obj), addCORS(init));
new Response(JSON.stringify(obj), addCORS(init));
export const resolveFailures = (
handleWith: () => Response,
handler: (request: Request) => Promise<Response> | Response
handleWith: () => Response,
handler: (request: Request) => Promise<Response> | Response
) => async (request: Request): Promise<Response> => {
try {
return handler(request);
} catch (e) {
console.error(e);
return (
handleWith() || respond({ error: 'internal server error' }, { status: 500 })
);
}
try {
return handler(request);
} catch (e) {
console.error(e);
return handleWith() || respond({ error: 'internal server error' }, { status: 500 });
}
};
export const parsePermissions = (
permissions: bigint,
owner: boolean = false
permissions: bigint,
owner: boolean = false
): UserGuildPermissions => {
if (owner || evaluatePermission(permissions, Permissions.ADMINISTRATOR)) {
return UserGuildPermissions.Admin;
}
if (owner || evaluatePermission(permissions, Permissions.ADMINISTRATOR)) {
return UserGuildPermissions.Admin;
}
if (evaluatePermission(permissions, Permissions.MANAGE_ROLES)) {
return UserGuildPermissions.Manager;
}
if (evaluatePermission(permissions, Permissions.MANAGE_ROLES)) {
return UserGuildPermissions.Manager;
}
return UserGuildPermissions.User;
return UserGuildPermissions.User;
};
export const getSessionID = (request: Request): { type: string; id: string } | null => {
const sessionID = request.headers.get('authorization');
if (!sessionID) {
return null;
}
const sessionID = request.headers.get('authorization');
if (!sessionID) {
return null;
}
const [type, id] = sessionID.split(' ');
if (type !== 'Bearer') {
return null;
}
const [type, id] = sessionID.split(' ');
if (type !== 'Bearer') {
return null;
}
return { type, id };
return { type, id };
};
export const userAgent =
'DiscordBot (https://github.com/roleypoly/roleypoly, git-main) (+https://roleypoly.com)';
'DiscordBot (https://github.com/roleypoly/roleypoly, git-main) (+https://roleypoly.com)';
export enum AuthType {
Bearer = 'Bearer',
Bot = 'Bot',
Bearer = 'Bearer',
Bot = 'Bot',
}
export const discordFetch = async <T>(
url: string,
auth: string,
authType: AuthType = AuthType.Bearer,
init?: RequestInit
url: string,
auth: string,
authType: AuthType = AuthType.Bearer,
init?: RequestInit
): Promise<T | null> => {
const response = await fetch('https://discord.com/api/v8' + url, {
...(init || {}),
headers: {
...(init?.headers || {}),
authorization: `${AuthType[authType]} ${auth}`,
'user-agent': userAgent,
},
const response = await fetch('https://discord.com/api/v8' + url, {
...(init || {}),
headers: {
...(init?.headers || {}),
authorization: `${AuthType[authType]} ${auth}`,
'user-agent': userAgent,
},
});
if (response.status >= 400) {
console.error('discordFetch failed', {
url,
authType,
payload: await response.text(),
});
}
if (response.status >= 400) {
console.error('discordFetch failed', {
url,
authType,
payload: await response.text(),
});
}
if (response.ok) {
return (await response.json()) as T;
} else {
return null;
}
if (response.ok) {
return (await response.json()) as T;
} else {
return null;
}
};
export const cacheLayer = <Identity, Data>(
kv: WrappedKVNamespace,
keyFactory: (identity: Identity) => string,
missHandler: (identity: Identity) => Promise<Data | null>,
ttlSeconds?: number
kv: WrappedKVNamespace,
keyFactory: (identity: Identity) => string,
missHandler: (identity: Identity) => Promise<Data | null>,
ttlSeconds?: number
) => async (
identity: Identity,
options: { skipCachePull?: boolean } = {}
identity: Identity,
options: { skipCachePull?: boolean } = {}
): Promise<Data | null> => {
const key = keyFactory(identity);
const key = keyFactory(identity);
if (!options.skipCachePull) {
const value = await kv.get<Data>(key);
if (value) {
return value;
}
if (!options.skipCachePull) {
const value = await kv.get<Data>(key);
if (value) {
return value;
}
}
const fallbackValue = await missHandler(identity);
if (!fallbackValue) {
return null;
}
const fallbackValue = await missHandler(identity);
if (!fallbackValue) {
return null;
}
await kv.put(key, fallbackValue, ttlSeconds);
await kv.put(key, fallbackValue, ttlSeconds);
return fallbackValue;
return fallbackValue;
};
const NotAuthenticated = (extra?: string) =>
respond(
{
error: extra || 'not authenticated',
},
{ status: 403 }
);
respond(
{
error: extra || 'not authenticated',
},
{ status: 403 }
);
export const withSession = (
wrappedHandler: (session: SessionData) => Handler
wrappedHandler: (session: SessionData) => Handler
): Handler => async (request: Request): Promise<Response> => {
const sessionID = getSessionID(request);
if (!sessionID) {
return NotAuthenticated('missing authentication');
}
const sessionID = getSessionID(request);
if (!sessionID) {
return NotAuthenticated('missing authentication');
}
const session = await Sessions.get<SessionData>(sessionID.id);
if (!session) {
return NotAuthenticated('authentication expired or not found');
}
const session = await Sessions.get<SessionData>(sessionID.id);
if (!session) {
return NotAuthenticated('authentication expired or not found');
}
return await wrappedHandler(session)(request);
return await wrappedHandler(session)(request);
};
export const setupStateSession = async <T>(data: T): Promise<string> => {
const stateID = (await KSUID.random()).string;
const stateID = (await KSUID.random()).string;
await Sessions.put(`state_${stateID}`, { data }, 60 * 5);
await Sessions.put(`state_${stateID}`, { data }, 60 * 5);
return stateID;
return stateID;
};
export const getStateSession = async <T>(stateID: string): Promise<T | undefined> => {
const stateSession = await Sessions.get<{ data: T }>(`state_${stateID}`);
const stateSession = await Sessions.get<{ data: T }>(`state_${stateID}`);
return stateSession?.data;
return stateSession?.data;
};
export const isRoot = (userID: string): boolean => rootUsers.includes(userID);
export const onlyRootUsers = (handler: Handler): Handler =>
withSession((session) => (request: Request) => {
if (isRoot(session.user.id)) {
return handler(request);
}
return respond(
{
error: 'not_found',
},
{
status: 404,
}
);
});
export const getQuery = (request: Request): { [x: string]: string } => {
const output: { [x: string]: string } = {};
for (let [key, value] of new URL(request.url).searchParams.entries()) {
output[key] = value;
withSession((session) => (request: Request) => {
if (isRoot(session.user.id)) {
return handler(request);
}
return output;
return respond(
{
error: 'not_found',
},
{
status: 404,
}
);
});
export const getQuery = (request: Request): { [x: string]: string } => {
const output: { [x: string]: string } = {};
for (let [key, value] of new URL(request.url).searchParams.entries()) {
output[key] = value;
}
return output;
};
export const isAllowedCallbackHost = (host: string): boolean => {
return (
host === apiPublicURI ||
allowedCallbackHosts.includes(host) ||
allowedCallbackHosts
.filter((callbackHost) => callbackHost.includes('*'))
.find((wildcard) =>
new RegExp(wildcard.replace('*', '[a-z0-9-]+')).test(host)
) !== null
);
return (
host === apiPublicURI ||
allowedCallbackHosts.includes(host) ||
allowedCallbackHosts
.filter((callbackHost) => callbackHost.includes('*'))
.find((wildcard) => new RegExp(wildcard.replace('*', '[a-z0-9-]+')).test(host)) !==
null
);
};

View file

@ -1,7 +1,7 @@
export const Bounce = (url: string): Response =>
new Response(null, {
status: 303,
headers: {
location: url,
},
});
new Response(null, {
status: 303,
headers: {
location: url,
},
});

View file

@ -1,163 +1,163 @@
import { evaluatePermission, permissions } from '@roleypoly/misc-utils/hasPermission';
import {
Features,
Guild,
GuildData as GuildDataT,
OwnRoleInfo,
Role,
RoleSafety,
Features,
Guild,
GuildData as GuildDataT,
OwnRoleInfo,
Role,
RoleSafety,
} from '@roleypoly/types';
import { AuthType, cacheLayer, discordFetch } from './api-tools';
import { botClientID, botToken } from './config';
import { GuildData, Guilds } from './kv';
type APIGuild = {
// Only relevant stuff
id: string;
name: string;
icon: string;
roles: APIRole[];
// Only relevant stuff
id: string;
name: string;
icon: string;
roles: APIRole[];
};
type APIRole = {
id: string;
name: string;
color: number;
position: number;
permissions: string;
managed: boolean;
id: string;
name: string;
color: number;
position: number;
permissions: string;
managed: boolean;
};
export const getGuild = cacheLayer(
Guilds,
(id: string) => `guilds/${id}`,
async (id: string) => {
const guildRaw = await discordFetch<APIGuild>(
`/guilds/${id}`,
botToken,
AuthType.Bot
);
Guilds,
(id: string) => `guilds/${id}`,
async (id: string) => {
const guildRaw = await discordFetch<APIGuild>(
`/guilds/${id}`,
botToken,
AuthType.Bot
);
if (!guildRaw) {
return null;
}
if (!guildRaw) {
return null;
}
const botMemberRoles =
(await getGuildMemberRoles({
serverID: id,
userID: botClientID,
})) || [];
const botMemberRoles =
(await getGuildMemberRoles({
serverID: id,
userID: botClientID,
})) || [];
const highestRolePosition = botMemberRoles.reduce<number>((highest, roleID) => {
const role = guildRaw.roles.find((guildRole) => guildRole.id === roleID);
if (!role) {
return highest;
}
const highestRolePosition = botMemberRoles.reduce<number>((highest, roleID) => {
const role = guildRaw.roles.find((guildRole) => guildRole.id === roleID);
if (!role) {
return highest;
}
// If highest is a bigger number, it stays the highest.
if (highest > role.position) {
return highest;
}
// If highest is a bigger number, it stays the highest.
if (highest > role.position) {
return highest;
}
return role.position;
}, 0);
return role.position;
}, 0);
const roles = guildRaw.roles.map<Role>((role) => ({
id: role.id,
name: role.name,
color: role.color,
managed: role.managed,
position: role.position,
permissions: role.permissions,
safety: calculateRoleSafety(role, highestRolePosition),
}));
const roles = guildRaw.roles.map<Role>((role) => ({
id: role.id,
name: role.name,
color: role.color,
managed: role.managed,
position: role.position,
permissions: role.permissions,
safety: calculateRoleSafety(role, highestRolePosition),
}));
// Filters the raw guild data into data we actually want
const guild: Guild & OwnRoleInfo = {
id: guildRaw.id,
name: guildRaw.name,
icon: guildRaw.icon,
roles,
highestRolePosition,
};
// Filters the raw guild data into data we actually want
const guild: Guild & OwnRoleInfo = {
id: guildRaw.id,
name: guildRaw.name,
icon: guildRaw.icon,
roles,
highestRolePosition,
};
return guild;
},
60 * 60 * 2 // 2 hour TTL
return guild;
},
60 * 60 * 2 // 2 hour TTL
);
type GuildMemberIdentity = {
serverID: string;
userID: string;
serverID: string;
userID: string;
};
type APIMember = {
// Only relevant stuff, again.
roles: string[];
// Only relevant stuff, again.
roles: string[];
};
const guildMemberRolesIdentity = ({ serverID, userID }: GuildMemberIdentity) =>
`guilds/${serverID}/members/${userID}/roles`;
`guilds/${serverID}/members/${userID}/roles`;
export const getGuildMemberRoles = cacheLayer<GuildMemberIdentity, Role['id'][]>(
Guilds,
guildMemberRolesIdentity,
async ({ serverID, userID }) => {
const discordMember = await discordFetch<APIMember>(
`/guilds/${serverID}/members/${userID}`,
botToken,
AuthType.Bot
);
Guilds,
guildMemberRolesIdentity,
async ({ serverID, userID }) => {
const discordMember = await discordFetch<APIMember>(
`/guilds/${serverID}/members/${userID}`,
botToken,
AuthType.Bot
);
if (!discordMember) {
return null;
}
if (!discordMember) {
return null;
}
return discordMember.roles;
},
60 * 5 // 5 minute TTL
return discordMember.roles;
},
60 * 5 // 5 minute TTL
);
export const updateGuildMemberRoles = async (
identity: GuildMemberIdentity,
roles: Role['id'][]
identity: GuildMemberIdentity,
roles: Role['id'][]
) => {
await Guilds.put(guildMemberRolesIdentity(identity), roles, 60 * 5);
await Guilds.put(guildMemberRolesIdentity(identity), roles, 60 * 5);
};
export const getGuildData = async (id: string): Promise<GuildDataT> => {
const guildData = await GuildData.get<GuildDataT>(id);
const guildData = await GuildData.get<GuildDataT>(id);
if (!guildData) {
return {
id,
message: '',
categories: [],
features: Features.None,
};
}
if (!guildData) {
return {
id,
message: '',
categories: [],
features: Features.None,
};
}
return guildData;
return guildData;
};
const calculateRoleSafety = (role: Role | APIRole, highestBotRolePosition: number) => {
let safety = RoleSafety.Safe;
let safety = RoleSafety.Safe;
if (role.managed) {
safety |= RoleSafety.ManagedRole;
}
if (role.managed) {
safety |= RoleSafety.ManagedRole;
}
if (role.position > highestBotRolePosition) {
safety |= RoleSafety.HigherThanBot;
}
if (role.position > highestBotRolePosition) {
safety |= RoleSafety.HigherThanBot;
}
const permBigInt = BigInt(role.permissions);
if (
evaluatePermission(permBigInt, permissions.ADMINISTRATOR) ||
evaluatePermission(permBigInt, permissions.MANAGE_ROLES)
) {
safety |= RoleSafety.DangerousPermissions;
}
const permBigInt = BigInt(role.permissions);
if (
evaluatePermission(permBigInt, permissions.ADMINISTRATOR) ||
evaluatePermission(permBigInt, permissions.MANAGE_ROLES)
) {
safety |= RoleSafety.DangerousPermissions;
}
return safety;
return safety;
};

View file

@ -1,87 +1,87 @@
export class WrappedKVNamespace {
constructor(private kvNamespace: KVNamespace) {}
constructor(private kvNamespace: KVNamespace) {}
async get<T>(key: string): Promise<T | null> {
const data = await this.kvNamespace.get(key, 'text');
if (!data) {
return null;
}
return JSON.parse(data) as T;
async get<T>(key: string): Promise<T | null> {
const data = await this.kvNamespace.get(key, 'text');
if (!data) {
return null;
}
async put<T>(key: string, value: T, ttlSeconds?: number) {
await this.kvNamespace.put(key, JSON.stringify(value), {
expirationTtl: ttlSeconds,
});
}
return JSON.parse(data) as T;
}
list = this.kvNamespace.list;
getWithMetadata = this.kvNamespace.getWithMetadata;
delete = this.kvNamespace.delete;
async put<T>(key: string, value: T, ttlSeconds?: number) {
await this.kvNamespace.put(key, JSON.stringify(value), {
expirationTtl: ttlSeconds,
});
}
list = this.kvNamespace.list;
getWithMetadata = this.kvNamespace.getWithMetadata;
delete = this.kvNamespace.delete;
}
class EmulatedKV implements KVNamespace {
constructor() {
console.warn('EmulatedKV used. Data will be lost.');
constructor() {
console.warn('EmulatedKV used. Data will be lost.');
}
private data: Map<string, any> = new Map();
async get<T>(key: string): Promise<T | null> {
if (!this.data.has(key)) {
return null;
}
private data: Map<string, any> = new Map();
return this.data.get(key);
}
async get<T>(key: string): Promise<T | null> {
if (!this.data.has(key)) {
return null;
}
async getWithMetadata<T, Metadata = unknown>(
key: string
): KVValueWithMetadata<T, Metadata> {
return {
value: await this.get<T>(key),
metadata: {} as Metadata,
};
}
return this.data.get(key);
async put(key: string, value: string | ReadableStream<any> | ArrayBuffer | FormData) {
this.data.set(key, value);
}
async delete(key: string) {
this.data.delete(key);
}
async list(options?: {
prefix?: string;
limit?: number;
cursor?: string;
}): Promise<{
keys: { name: string; expiration?: number; metadata?: unknown }[];
list_complete: boolean;
cursor: string;
}> {
let keys: { name: string }[] = [];
for (let key of this.data.keys()) {
if (options?.prefix && !key.startsWith(options.prefix)) {
continue;
}
keys.push({ name: key });
}
async getWithMetadata<T, Metadata = unknown>(
key: string
): KVValueWithMetadata<T, Metadata> {
return {
value: await this.get<T>(key),
metadata: {} as Metadata,
};
}
async put(key: string, value: string | ReadableStream<any> | ArrayBuffer | FormData) {
this.data.set(key, value);
}
async delete(key: string) {
this.data.delete(key);
}
async list(options?: {
prefix?: string;
limit?: number;
cursor?: string;
}): Promise<{
keys: { name: string; expiration?: number; metadata?: unknown }[];
list_complete: boolean;
cursor: string;
}> {
let keys: { name: string }[] = [];
for (let key of this.data.keys()) {
if (options?.prefix && !key.startsWith(options.prefix)) {
continue;
}
keys.push({ name: key });
}
return {
keys,
cursor: '0',
list_complete: true,
};
}
return {
keys,
cursor: '0',
list_complete: true,
};
}
}
const kvOrLocal = (namespace: KVNamespace | null): KVNamespace =>
namespace || new EmulatedKV();
namespace || new EmulatedKV();
const self = (global as any) as Record<string, any>;