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 };
|