refactor: generalize API library

This commit is contained in:
spiral
2022-02-01 15:24:45 -05:00
parent 6d2fa78767
commit e74b5e1c13
26 changed files with 229 additions and 487 deletions

28
src/api/errors.ts Normal file
View File

@@ -0,0 +1,28 @@
enum ErrorType {
Unknown = 0,
InvalidToken = 401,
NotFound = 404,
InternalServerError = 500,
}
interface ApiError {
code: number,
type: ErrorType,
message?: string,
data?: any,
}
export function parse(code: number, data?: string): ApiError {
var type = ErrorType[ErrorType[code]] ?? ErrorType.Unknown;
if (code >= 500) type = ErrorType.InternalServerError;
var err: ApiError = { code, type };
if (data) {
var d = JSON.parse(data);
err.message = d.message;
err.data = d;
}
return err;
}

View File

@@ -1,40 +0,0 @@
interface GroupPrivacy {
description_privacy?: string,
icon_privacy?: string,
list_privacy?: string,
visibility?: string
}
export default class Group {
id?: string;
uuid?: string;
name?: string;
display_name?: string;
description?: string;
icon?: string;
banner?: string;
color?: string;
privacy?: GroupPrivacy;
created?: string;
members?: string[];
constructor(data: Group) {
this.id = data.id;
this.uuid = data.uuid;
this.name = data.name;
this.display_name = data.display_name;
this.description = data.description;
this.icon = data.icon;
this.banner = data.banner;
this.color = data.color;
this.created = data.created;
this.members = data.members;
if (data.privacy) {
this.privacy = {}
this.privacy.description_privacy = data.privacy.description_privacy;
this.privacy.icon_privacy = data.privacy.icon_privacy;
this.privacy.list_privacy = data.privacy.list_privacy;
this.privacy.visibility = data.privacy.visibility;
}
}
}

View File

@@ -1,253 +1,43 @@
import axios, { AxiosInstance, Method, AxiosResponse, AxiosRequestConfig, Axios } from 'axios';
import Sys from './system';
import Member from './member';
import Group from './group';
import axios from 'axios';
const baseUrl = () => localStorage.isBeta ? "https://api.beta.pluralkit.me" : "https://api.pluralkit.me";
type FieldError = {
message: string
const methods = ['get', 'post', 'delete', 'patch', 'put'];
const noop = () => {};
export default function() {
const route = [];
const handler = {
get(_, name) {
if (route.length == 0 && name != "private")
route.push("v2");
if (methods.includes(name)) {
return ({ data = undefined, auth = true, token = null, query = null } = {}) => new Promise((res, rej) => axios({
url: baseUrl() + "/" + route.join("/") + (query ? `?${Object.keys(query).map(x => `${x}=${query[x]}`).join("&")}` : ""),
method: name,
headers: {
authorization: token ?? (auth ? localStorage.getItem("pk-token") : undefined),
"content-type": name == "get" ? undefined : "application/json"
},
data: !!data ? JSON.stringify(data) : undefined,
validateStatus: () => true,
}).then((resp) => res(parseData(resp.status, resp.data))).catch(rej));
}
route.push(name);
return new Proxy(noop, handler);
},
apply(target, _, args) {
route.push(...args.filter(x => x != null));
return new Proxy(noop, handler);
}
}
return new Proxy(noop, handler);
}
export default class PKAPI {
ROUTES = {
GET_SYSTEM: (sid?: string) => sid ? `/systems/${sid}` : `/systems/@me`,
GET_MEMBER_LIST: (sid?: string) => sid ? `/systems/${sid}/members` : `/systems/@me/members`,
GET_MEMBER: (mid: string) => `/members/${mid}`,
GET_GROUP_LIST: (sid?: string, members?: boolean) => `${sid ? `/systems/${sid}/groups` : `/systems/@me/groups`}` + `${members ? `?with_members=true` : ""}`,
PATCH_SYSTEM: () => `/systems/@me`,
PATCH_GROUP: (gid: string) => `/groups/${gid}`,
PATCH_MEMBER: (mid: string) => `/members/${mid}`,
POST_MEMBER: () => `/members`,
POST_MEMBER_GROUP: (mid: string, removing: boolean) => !removing ? `/members/${mid}/groups/add` : `/members/${mid}/groups/remove`,
POST_GROUP_MEMBER: (gid: string, removing: boolean) => !removing ? `/groups/${gid}/members/add` : `/groups/${gid}/members/remove`,
DELETE_MEMBER: (mid: string) => `/members/${mid}`,
DELETE_GROUP: (gid: string) => `/groups/${gid}`
}
baseUrl: string;
instance: AxiosInstance
constructor(baseUrl?: string) {
this.baseUrl = baseUrl || 'https://api.pluralkit.me';
this.instance = axios.create({
baseURL: this.baseUrl + '/v2'
})
}
async getSystem(options: { token?: string, id?: any}) {
if (!options.token && !options.id) {
throw new Error("Must pass a token or id.")
}
var system: Sys;
var res: AxiosResponse;
try {
res = await this.handle(this.ROUTES.GET_SYSTEM(options.id ? options.id : ""), 'GET', {token: !options.id ? options.token : ""});
if (res.status === 200) system = new Sys(res.data);
else this.handleErrors(res);
} catch (error) {
throw new Error(error.message);
}
return system;
}
async patchSystem(options: {token: string, data: any}) {
var body = new Sys(options.data);
var system: Sys;
var res: AxiosResponse;
try {
res = await this.handle(this.ROUTES.PATCH_SYSTEM(), 'PATCH', {token: options.token, body: body});
if (res.status === 200) system = new Sys(res.data);
else this.handleErrors(res);
} catch (error) {
throw new Error(error.message);
}
return system;
}
async getMemberList(options: { token?: string, id?: any}) {
if (!options.token && !options.id) {
throw new Error("Must pass a token or id.")
}
var members: Member[] = [];
var res: AxiosResponse;
try {
res = await this.handle(this.ROUTES.GET_MEMBER_LIST(options.id ? options.id : ""), 'GET', {token: !options.id ? options.token : ""});
if (res.status === 200) {
let resObject: any = res.data;
resObject.forEach(m => {
let member = new Member(m);
members.push(member);
})
}
else this.handleErrors(res);
} catch (error) {
throw new Error(error.message);
}
return members;
}
async getMember(options: {id: any}) {
if (!options.id) {
throw new Error("Must pass an id.")
}
var member: Member;
var res: AxiosResponse;
try {
res = await this.handle(this.ROUTES.GET_MEMBER(options.id), 'GET', {});
if (res.status === 200) member = new Member(res.data);
else this.handleErrors(res);
} catch (error) {
throw new Error(error.message);
}
return member;
}
async patchMember(options: {token: string, id: any, data: any}) {
var body = new Member(options.data);
var member: Member;
var res: AxiosResponse;
try {
res = await this.handle(this.ROUTES.PATCH_MEMBER(options.id), 'PATCH', {token: options.token, body: body});
if (res.status === 200) member = new Member(res.data);
else this.handleErrors(res);
} catch (error) {
throw new Error(error.message);
}
return member;
}
async postMember(options: {token: any, data: any}) {
if (!options.token) throw new Error("Must pass a token.");
var body = new Member(options.data);
var member: Member;
var res: AxiosResponse;
try {
res = await this.handle(this.ROUTES.POST_MEMBER(), 'POST', {token: options.token, body: body});
if (res.status === 200) member = new Member(res.data);
else this.handleErrors(res);
} catch (error) {
throw new Error(error.message);
}
return member;
}
async postMemberGroups(options: {token: string, id: string, data: any, removing?: boolean}) {
var res: AxiosResponse;
try {
res = await this.handle(this.ROUTES.POST_MEMBER_GROUP(options.id, options.removing), 'POST', {token: options.token, body: options.data});
if (res.status !== 204) this.handleErrors(res);
} catch (error) {
throw new Error(error.message);
}
}
async deleteMember(options: {token: string, id: string}) {
var res: AxiosResponse;
try {
res = await this.handle(this.ROUTES.DELETE_MEMBER(options.id), 'DELETE', {token: options.token});
if (res.status !== 204 ) this.handleErrors(res);
} catch (error) {
throw new Error(error.message);
}
}
async getGroupList(options: {token?: string, id?: any, members?: boolean}) {
if (!options.token && !options.id) {
throw new Error("Must pass a token or id.");
}
if (!options.members) options.members = false;
var groups: Group[] = [];
var res: AxiosResponse;
try {
res = await this.handle(this.ROUTES.GET_GROUP_LIST(options.id ? options.id : "", options.members), 'GET', {token: !options.id ? options.token : ""});
if (res.status === 200) {
let resObject: any = res.data;
resObject.forEach(g => {
let group = new Group(g);
groups.push(group);
})
}
else this.handleErrors(res);
} catch (error) {
throw new Error(error.message);
}
return groups;
}
async patchGroup(options: {token: string, id: any, data: any}) {
var body = new Group(options.data);
var group: Group;
var res: AxiosResponse;
try {
res = await this.handle(this.ROUTES.PATCH_GROUP(options.id), 'PATCH', {token: options.token, body: body});
if (res.status === 200) group = new Group(res.data);
else this.handleErrors(res);
} catch (error) {
throw new Error(error.message);
}
return group;
}
async postGroupMembers(options: {token: string, id: string, data: any, removing?: boolean}) {
var res: AxiosResponse;
try {
res = await this.handle(this.ROUTES.POST_GROUP_MEMBER(options.id, options.removing), 'POST', {token: options.token, body: options.data});
if (res.status !== 204) this.handleErrors(res);
} catch (error) {
throw new Error(error.message);
}
}
async deleteGroup(options: {token: string, id: string}) {
var res: AxiosResponse;
try {
res = await this.handle(this.ROUTES.DELETE_GROUP(options.id), 'DELETE', {token: options.token});
if (res.status !== 204 ) this.handleErrors(res);
} catch (error) {
throw new Error(error.message);
}
}
handleErrors(res: any) {
if (res.status === 500) throw new Error("500: Internal server error.");
else if (res.status === 401) throw new Error("401: Your token is invalid.");
else {
let errorObject: any = res.data
if (errorObject.code) {
if (errorObject.code === 40001) {
var message: string;
for (var key in errorObject.errors) {
var val = errorObject.errors[key];
}
} else {
throw new Error(errorObject.message);
}
}
}
}
async handle(url: string, method: Method, options: {token?: string, body?: object}) {
var headers = {}
var request: AxiosRequestConfig = {url, method, headers}
if(options.token) request.headers["Authorization"] = options.token;
if (options.body) {
request.headers["Content-Type"] = "application/json";
request.data = JSON.stringify(options.body);
}
try {
var res = await this.instance(request);
} catch (error) {
res = error.response;
}
return res;
}
import * as errors from './errors';
function parseData(code: number, data: any) {
if (code == 200) return data;
if (code == 204) return;
throw errors.parse(code, data);
}

View File

@@ -1,60 +0,0 @@
interface MemberPrivacy {
visibility?: string,
description_privacy?: string,
name_privacy?: string,
birthday_privacy?: string,
pronoun_privacy?: string,
avatar_privacy?: string,
metadata_privacy?: string
}
type proxytag = {
prefix?: string,
suffix?: string
}
export default class Member {
id?: string;
uuid?: string;
name?: string;
display_name?: string;
color?: string;
birthday?: string;
pronouns?: string;
avatar_url?: string;
banner?: string;
description?: string;
created?: string;
keep_proxy?: boolean
system?: string;
proxy_tags?: Array<proxytag>;
privacy?: MemberPrivacy
constructor(data: Member) {
this.id = data.id;
this.uuid = data.uuid;
this.name = data.name;
this.display_name = data.display_name;
this.color = data.color;
this.birthday = data.birthday;
this.pronouns = data.pronouns;
this.avatar_url = data.avatar_url;
this.banner = data.banner;
this.description = data.description;
this.created = data.created;
this.system = data.system;
this.proxy_tags = data.proxy_tags;
this.keep_proxy = data.keep_proxy;
if (data.privacy) {
this.privacy = {
visibility: data.privacy.visibility,
description_privacy: data.privacy.description_privacy,
name_privacy: data.privacy.name_privacy,
birthday_privacy: data.privacy.birthday_privacy,
pronoun_privacy: data.privacy.pronoun_privacy,
avatar_privacy: data.privacy.avatar_privacy,
metadata_privacy: data.privacy.metadata_privacy
}
}
}
}

View File

@@ -1,43 +0,0 @@
interface SystemPrivacy {
description_privacy?: string,
member_list_privacy?: string,
front_privacy?: string,
front_history_privacy?: string,
group_list_privacy?: string
}
export default class Sys {
id?: string;
uuid?: string;
name?: string;
description?: string;
tag?: string;
avatar_url?: string;
banner?: string;
timezone?: string;
created?: string;
privacy?: SystemPrivacy;
color?: string;
constructor(data: Sys) {
this.id = data.id;
this.uuid = data.uuid;
this.name = data.name;
this.description = data.description;
this.tag = data.tag;
this.avatar_url = data.avatar_url;
this.banner = data.banner;
this.timezone = data.timezone;
this.created = data.created;
this.color = data.color;
if (data.privacy) {
this.privacy = {
description_privacy: data.privacy.description_privacy,
member_list_privacy: data.privacy.member_list_privacy,
front_privacy: data.privacy.front_privacy,
front_history_privacy: data.privacy.front_history_privacy,
group_list_privacy: data.privacy.group_list_privacy
}
}
}
}

75
src/api/types.ts Normal file
View File

@@ -0,0 +1,75 @@
interface SystemPrivacy {
description_privacy?: string,
member_list_privacy?: string,
front_privacy?: string,
front_history_privacy?: string,
group_list_privacy?: string
}
export interface System {
id?: string;
uuid?: string;
name?: string;
description?: string;
tag?: string;
avatar_url?: string;
banner?: string;
timezone?: string;
created?: string;
privacy?: SystemPrivacy;
color?: string;
}
interface MemberPrivacy {
visibility?: string,
description_privacy?: string,
name_privacy?: string,
birthday_privacy?: string,
pronoun_privacy?: string,
avatar_privacy?: string,
metadata_privacy?: string
}
interface proxytag {
prefix?: string,
suffix?: string
}
export interface Member {
id?: string;
uuid?: string;
name?: string;
display_name?: string;
color?: string;
birthday?: string;
pronouns?: string;
avatar_url?: string;
banner?: string;
description?: string;
created?: string;
keep_proxy?: boolean
system?: string;
proxy_tags?: Array<proxytag>;
privacy?: MemberPrivacy
}
export interface GroupPrivacy {
description_privacy?: string,
icon_privacy?: string,
list_privacy?: string,
visibility?: string
}
export interface Group {
id?: string;
uuid?: string;
name?: string;
display_name?: string;
description?: string;
icon?: string;
banner?: string;
color?: string;
privacy?: GroupPrivacy;
created?: string;
members?: string[];
}