283 lines
8.9 KiB
JavaScript
Raw Normal View History

2025-12-04 20:29:35 +01:00
/**
* @fileoverview DevRant API Client for Rantii
* @author retoor <retoor@molodetz.nl>
* @description HTTP client for communicating with the DevRant API
* @keywords api, client, devrant, http, fetch
*/
2025-12-04 20:44:39 +01:00
const API_BASE_URL = '/api/';
2025-12-04 20:29:35 +01:00
const APP_ID = 3;
class ApiClient {
constructor() {
this.tokenId = null;
this.tokenKey = null;
this.userId = null;
}
setAuth(tokenId, tokenKey, userId) {
this.tokenId = tokenId;
this.tokenKey = tokenKey;
this.userId = userId;
}
clearAuth() {
this.tokenId = null;
this.tokenKey = null;
this.userId = null;
}
isAuthenticated() {
return this.tokenId !== null && this.tokenKey !== null;
}
buildParams(params = {}) {
const result = { app: APP_ID, ...params };
if (this.isAuthenticated()) {
result.token_id = this.tokenId;
result.token_key = this.tokenKey;
result.user_id = this.userId;
}
return result;
}
buildUrl(endpoint) {
const path = endpoint.startsWith('/') ? endpoint.slice(1) : endpoint;
return API_BASE_URL + path;
}
async request(method, endpoint, params = {}, timeout = 30000) {
const url = this.buildUrl(endpoint);
const allParams = this.buildParams(params);
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
const config = {
method,
headers: {},
signal: controller.signal
};
try {
let fullUrl = url;
if (method === 'GET' || method === 'DELETE') {
const queryString = new URLSearchParams(allParams).toString();
fullUrl = queryString ? `${url}?${queryString}` : url;
} else {
config.headers['Content-Type'] = 'application/x-www-form-urlencoded';
config.body = new URLSearchParams(allParams).toString();
}
const response = await fetch(fullUrl, config);
clearTimeout(timeoutId);
if (!response.ok && response.status >= 500) {
return { success: false, error: `Server error: ${response.status}` };
}
const data = await response.json();
return data;
} catch (error) {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
return { success: false, error: 'Request timed out' };
}
return { success: false, error: error.message };
}
}
async get(endpoint, params = {}) {
return this.request('GET', endpoint, params);
}
async post(endpoint, params = {}) {
return this.request('POST', endpoint, params);
}
async delete(endpoint, params = {}) {
return this.request('DELETE', endpoint, params);
}
async login(username, password) {
const response = await this.post('/users/auth-token', {
username,
password
});
if (response.success && response.auth_token) {
this.setAuth(
response.auth_token.id,
response.auth_token.key,
response.auth_token.user_id
);
return {
success: true,
authToken: response.auth_token
};
}
return { success: false, error: response.error || 'Login failed' };
}
async getRants(sort = 'recent', limit = 20, skip = 0) {
const response = await this.get('/devrant/rants', { sort, limit, skip });
if (response.success) {
return { success: true, rants: response.rants || [] };
}
return { success: false, rants: [], error: response.error };
}
async getRant(rantId) {
const response = await this.get(`/devrant/rants/${rantId}`);
if (response.success) {
return {
success: true,
rant: response.rant,
comments: response.comments || []
};
}
return { success: false, error: response.error };
}
async voteRant(rantId, vote, reason = null) {
const params = { vote };
if (reason !== null) {
params.reason = reason;
}
const response = await this.post(`/devrant/rants/${rantId}/vote`, params);
return { success: response.success, rant: response.rant };
}
async postComment(rantId, comment) {
const response = await this.post(`/devrant/rants/${rantId}/comments`, {
comment,
plat: 2
});
return { success: response.success, comment: response.comment };
}
async updateComment(commentId, comment) {
const response = await this.post(`/comments/${commentId}`, { comment });
return { success: response.success };
}
async deleteComment(commentId) {
const response = await this.delete(`/comments/${commentId}`);
return { success: response.success };
}
async voteComment(commentId, vote, reason = null) {
const params = { vote };
if (reason !== null) {
params.reason = reason;
}
const response = await this.post(`/comments/${commentId}/vote`, params);
return { success: response.success, comment: response.comment };
}
async getComment(commentId) {
const response = await this.get(`/comments/${commentId}`);
if (response.success) {
return { success: true, comment: response.comment };
}
return { success: false, error: response.error };
}
async getUserId(username) {
const response = await this.get('/get-user-id', { username });
if (response.success) {
return { success: true, userId: response.user_id };
}
return { success: false, error: response.error };
}
async getProfile(userId) {
const response = await this.get(`/users/${userId}`);
if (response.success) {
return { success: true, profile: response.profile };
}
return { success: false, error: response.error };
}
async getProfileByUsername(username) {
const userIdResponse = await this.getUserId(username);
if (!userIdResponse.success) {
return { success: false, error: userIdResponse.error };
}
return this.getProfile(userIdResponse.userId);
}
async search(term) {
const response = await this.get('/devrant/search', { term });
if (response.success) {
return { success: true, rants: response.results || [] };
}
return { success: false, rants: [], error: response.error };
}
async getNotifications() {
const response = await this.get('/users/me/notif-feed', { ext_prof: 1 });
if (response.success) {
const items = response.data?.items || [];
const users = response.data?.username_map || {};
const notifications = items.map(item => ({
...item,
username: users[item.uid] || item.username || 'Someone'
}));
return {
success: true,
notifications,
unread: response.data?.unread || { total: 0 }
};
}
return { success: false, notifications: [], error: response.error };
}
async clearNotifications() {
const response = await this.delete('/users/me/notif-feed');
return { success: response.success };
}
async postRant(text, tags = '', type = 1) {
const response = await this.post('/devrant/rants', {
rant: text,
tags,
type,
plat: 2
});
return { success: response.success, rantId: response.rant_id };
}
async deleteRant(rantId) {
const response = await this.delete(`/devrant/rants/${rantId}`);
return { success: response.success };
}
async getWeeklyRants(sort = 'recent', limit = 20, skip = 0) {
const response = await this.get('/devrant/weekly-rants', { sort, limit, skip });
if (response.success) {
return { success: true, rants: response.rants || [] };
}
return { success: false, rants: [], error: response.error };
}
async getCollabs(sort = 'recent', limit = 20, skip = 0) {
const response = await this.get('/devrant/collabs', { sort, limit, skip });
if (response.success) {
return { success: true, rants: response.rants || [] };
}
return { success: false, rants: [], error: response.error };
}
async getStories(sort = 'recent', limit = 20, skip = 0) {
const response = await this.get('/devrant/story-rants', { sort, limit, skip });
if (response.success) {
return { success: true, rants: response.rants || [] };
}
return { success: false, rants: [], error: response.error };
}
}
export { ApiClient, API_BASE_URL, APP_ID };