spworlds
Version:
Library for SPWorlds API
155 lines (154 loc) • 6.87 kB
JavaScript
;
// Copyright (c) 2022 Matvey Ryabchikov
// Copyright (c) 2024-2025 Mihandr
// MIT License
Object.defineProperty(exports, "__esModule", { value: true });
exports.SPWorlds = void 0;
const crypto_1 = require("crypto");
const DEFAULT_API_ENDPOINT = 'https://spworlds.ru';
class SPWorlds {
authorization;
token;
apiTimeout;
APIEndpoint;
requestAPI = async (method, path, body) => {
try {
const res = await fetch(`${this.APIEndpoint}/api/public/${path}`, {
method,
body: body ? JSON.stringify(body) : null,
headers: {
Authorization: this.authorization,
'Content-Type': 'application/json'
},
signal: this.apiTimeout ? AbortSignal.timeout(this.apiTimeout) : undefined
});
if (![200, 201, 404].includes(res.status))
throw new Error(`Ошибка при запросе к API ${res.status} ${res.statusText}`);
return await res.json();
}
catch (err) {
if (err.name === 'AbortError') {
throw new Error(`Превышено время ожидания запроса (${this.apiTimeout}мс)`);
}
throw err;
}
};
/**
* Класс для работы с SPWorlds API
* @param id ID карты
* @param token Токен карты
* @param apiTimeout Таймаут запроса к API
* @param APIEndpoint Вы можете указать другой API сервер (например https://spworlds.org). По умолчанию: https://spworlds.ru
*/
constructor({ id, token, apiTimeout, APIEndpoint = DEFAULT_API_ENDPOINT }) {
this.authorization = `Bearer ${Buffer.from(`${id}:${token}`).toString('base64')}`;
this.token = token;
this.apiTimeout = apiTimeout;
this.APIEndpoint = APIEndpoint;
}
/**
* Проверка доступности SPWorlds API
* @returns Состояние API
*/
ping = async () => {
return !!(await this.getCardInfo().catch(() => false));
};
/**
* Создание банковского перевода
* @param receiver Номер карты получателя
* @param amount Сумма перевода
* @param comment Комментарий к переводу
* @returns Баланс карты
*/
createTransaction = async ({ receiver, amount, comment }) => {
return this.requestAPI('POST', 'transactions', {
receiver,
amount,
comment
});
};
/**
* Получение информации о карте
* @returns Баланс и подключенный webhook
*/
getCardInfo = async () => {
return this.requestAPI('GET', 'card', null);
};
/**
* Получение информации о владельце токена
* @returns Никнейм, статус, роли, город, банковские карты и время создания
*/
getCardOwner = async () => {
return this.requestAPI('GET', 'accounts/me', null);
};
/**
* Получение карт пользователя
* @param Nickname Никнейм пользователя
* @returns Массив с банковскими картами
*/
getCards = async (nickname) => {
return this.requestAPI('GET', `accounts/${nickname}/cards`, null);
};
/**
* Получение ника игрока
* @param discordId ID пользователя Discord
* @returns Никнейм и Minecraft UUID игрока
*/
findUser = async (discordId) => {
return this.requestAPI('GET', `users/${discordId}`, null).then((res) => {
if (res.username || res.uuid)
return res;
return null;
});
};
/**
* Изменение вебхука карты
* На вебхук будут отправляться все новые транзакции связанные с картой
* @param url Ссылка на вебхук
* @returns ID карты и обновленный webhook
*/
setWebhook = async (url) => {
return this.requestAPI('PUT', 'card/webhook', {
url: url
});
};
/**
* Создание запроса на оплату
* @param items Товары транзакции
* @param redirectUrl URL страницы, на которую попадет пользователь после оплаты
* @param webhookUrl URL, куда наш сервер направит запрос, чтобы оповестить ваш сервер об успешной оплате
* @param data Cюда можно поместить любые полезные данных. Ограничение - 100 символов.
* @returns Ссылка на страницу оплаты, на которую нужно перенаправить пользователя
*/
initPayment = async ({ items, redirectUrl, webhookUrl, data }) => {
if (data && data.length > 100)
throw new Error('Data не может быть длиннее 100 символов');
for (const item of items) {
if (item.name.length < 3 || item.name.length > 64)
throw new Error('Название товара не может быть меньше 3 и больше 64 символов');
if (item.count < 1 || item.count > 9999)
throw new Error('Количество товара не может быть меньше 1 и больше 9999');
if (item.price < 1 || item.price > 1728)
throw new Error('Цена товара не может быть меньше 1 и больше 1728');
if (item.comment && item.comment.length > 100)
throw new Error('Комментарий не может быть длиннее 100 символов');
}
return this.requestAPI('POST', 'payments', {
items,
redirectUrl,
webhookUrl,
data
});
};
/**
* Проверка валидности вебхука
* @param body Все данные, пришедшие по вебхуку
* @param hashHeader Значение хедера `X-Body-Hash`
*/
validateHash = (body, hashHeader) => {
return (0, crypto_1.timingSafeEqual)(Buffer.from((0, crypto_1.createHmac)('sha256', this.token)
.update(typeof body === 'string' ? body : JSON.stringify(body))
.digest('base64')), Buffer.from(hashHeader));
};
}
exports.SPWorlds = SPWorlds;