campfire/src/lib/api.js
2025-07-14 18:45:38 +01:00

558 lines
18 KiB
JavaScript

const errors = {
AUTHENTICATION_FAILED: "AUTHENTICATION_FAILED",
};
/**
* GET /api/v1/instance
* @param {string} host - The domain of the target server.
*/
export async function getInstance(host) {
const data = await fetch(`https://${host}/api/v1/instance`)
.then(res => res.json())
.catch(error => console.error(error));
return data ? data : false;
}
/**
* POST /api/v1/apps
* Attempts to create an application for a given server host.
* @param {string} host - The domain of the target server.
*/
export async function createApp(host) {
let form = new FormData();
form.append("client_name", "Campfire");
form.append("redirect_uris", `${location.origin}/callback`);
form.append("scopes", "read write push");
form.append("website", "https://campfire.bliss.town");
const res = await fetch(`https://${host}/api/v1/apps`, {
method: "POST",
body: form,
})
.then(res => res.json())
.catch(error => {
console.error(error);
return false;
});
if (!res || !res.client_id) return false;
return {
id: res.client_id,
secret: res.client_secret,
};
}
/**
* Returns the OAuth authorization url for the target server.
* @param {string} host - The domain of the target server.
* @param {string} app_id - The application id for the target server.
*/
export function getOAuthUrl(host, app_id) {
return `https://${host}/oauth/authorize` +
`?client_id=${app_id}` +
"&scope=read+write+push" +
`&redirect_uri=${location.origin}/callback` +
"&response_type=code";
}
/**
* POST /oauth/token
* Attempts to generate an OAuth token.
* Returns false on failure.
* @param {string} host - The domain of the target server.
* @param {string} client_id - The application id.
* @param {string} secret - The application secret.
* @param {string} code - The authorization code provided by OAuth.
*/
export async function getToken(host, client_id, secret, code) {
let form = new FormData();
form.append("client_id", client_id);
form.append("client_secret", secret);
form.append("redirect_uri", `${location.origin}/callback`);
form.append("grant_type", "authorization_code");
form.append("code", code);
form.append("scope", "read write push");
const res = await fetch(`https://${host}/oauth/token`, {
method: "POST",
body: form,
})
.then(res => res.json())
.catch(error => {
console.error(error);
return false;
});
if (!res || !res.access_token) return false;
return res.access_token;
}
/**
* POST /oauth/revoke
* Attempts to revoke an OAuth token.
* Returns false on failure.
* @param {string} host - The domain of the target server.
* @param {string} client_id - The application id.
* @param {string} secret - The application secret.
* @param {string} token - The application token.
*/
export async function revokeToken(host, client_id, secret, token) {
let form = new FormData();
form.append("client_id", client_id);
form.append("client_secret", secret);
form.append("token", token);
const res = await fetch(`https://${host}/oauth/revoke`, {
method: "POST",
body: form,
})
.catch(error => {
console.error(error);
return false;
});
if (!res.ok) return false;
return true;
}
/**
* GET /api/v1/accounts/verify_credentials
* This endpoint returns information about the client account,
* and other useful data.
* Returns false on failure.
* @param {string} host - The domain of the target server.
* @param {string} token - The application token.
*/
export async function verifyCredentials(host, token) {
let url = `https://${host}/api/v1/accounts/verify_credentials`;
const data = await fetch(url, {
method: 'GET',
headers: { "Authorization": "Bearer " + token }
}).then(res => res.json());
return data;
}
/**
* GET /api/v1/streaming/health
* Checks if the server's streaming service is alive
*/
export async function getStreamingHealth(host) {
let url = `https://${host}/api/v1/streaming/health`;
const res = await fetch(url, {
method: 'GET'
});
return res.ok;
}
/**
* GET /api/v1/notifications
* @param {string} host - The domain of the target server.
* @param {string} token - The application token.
* @param {string} min_id - If provided, only shows notifications after this ID.
* @param {string} max_id - If provided, only shows notifications before this ID.
* @param {string} limit - The maximum number of notifications to retrieve (default 40).
* @param {string} types - A list of notification types to filter to.
*/
export async function getNotifications(host, token, min_id, max_id, limit, types) {
let url = `https://${host}/api/v1/notifications`;
let params = new URLSearchParams();
if (min_id) params.append("min_id", min_id);
if (max_id) params.append("max_id", max_id);
if (limit) params.append("limit", limit);
if (types) params.append("types", types.join(','));
const params_string = params.toString();
if (params_string) url += '?' + params_string;
const data = await fetch(url, {
method: 'GET',
headers: { "Authorization": "Bearer " + token }
}).then(res => res.json());
return data;
}
/**
* GET /api/v1/follow_requests
* @param {string} host - The domain of the target server.
* @param {string} token - The application token.
* @param {string} min_id - If provided, only shows follow requests since this ID.
* @param {string} max_id - If provided, only shows follow requests before this ID.
* @param {string} limit - The maximum number of follow requests to retrieve (default 40, max 80).
*/
export async function getFollowRequests(host, token, since_id, max_id, limit) {
let url = `https://${host}/api/v1/follow_requests`;
let params = new URLSearchParams();
if (since_id) params.append("since_id", since_id);
if (max_id) params.append("max_id", max_id);
if (limit) params.append("limit", limit);
const params_string = params.toString();
if (params_string) url += '?' + params_string;
const data = await fetch(url, {
method: 'GET',
headers: { "Authorization": "Bearer " + token }
}).then(res => res.json());
return data;
}
/**
* POST /api/v1/follow_requests/:account_id/authorize
* @param {string} host - The domain of the target server.
* @param {string} token - The application token.
* @param {string} account_id - The account ID of the follow request to accept
*/
export async function acceptFollowRequest(host, token, account_id) {
let url = `https://${host}/api/v1/follow_requests/${account_id}/authorize`;
const data = await fetch(url, {
method: 'POST',
headers: { "Authorization": "Bearer " + token }
}).then(res => res.json());
return data;
}
/**
* POST /api/v1/follow_requests/:account_id/reject
* @param {string} host - The domain of the target server.
* @param {string} token - The application token.
* @param {string} account_id - The account ID of the follow request to reject
*/
export async function rejectFollowRequest(host, token, account_id) {
let url = `https://${host}/api/v1/follow_requests/${account_id}/reject`;
const data = await fetch(url, {
method: 'POST',
headers: { "Authorization": "Bearer " + token }
}).then(res => res.json());
return data;
}
/**
* GET /api/v1/timelines/{timeline}
* @param {string} host - The domain of the target server.
* @param {string} token - The application token.
* @param {string} timeline - The name of the timeline to pull (default "home").
* @param {string} max_id - If provided, only shows posts after this ID.
*/
export async function getTimeline(host, token, timeline, max_id) {
let url = `https://${host}/api/v1/timelines/${timeline || "home"}`;
let params = new URLSearchParams();
if (max_id) params.append("max_id", max_id);
const params_string = params.toString();
if (params_string) url += '?' + params_string;
const data = await fetch(url, {
method: 'GET',
headers: { "Authorization": token ? `Bearer ${token}` : null }
}).then(res => res.json());
return data;
}
/**
* GET /api/v1/statuses/{post_id}.
* @param {string} host - The domain of the target server.
* @param {string} token - The application token.
* @param {string} post_id - The ID of the post to fetch.
*/
export async function getPost(host, token, post_id) {
let url = `https://${host}/api/v1/statuses/${post_id}`;
const data = await fetch(url, {
method: 'GET',
headers: { "Authorization": token ? `Bearer ${token}` : null }
}).then(res => res.json())
if (!data || data.error) return false;
return data;
}
/**
* POST /api/v1/statuses
* @param {string} host - The domain of the target server.
* @param {string} token - The application token
* @param {any} post_data - The post content
*/
export async function createPost(host, token, post_data) {
let formdata = new FormData();
for (const key in post_data) {
formdata.append(key, post_data[key]);
}
let url = `https://${host}/api/v1/statuses`;
const data = await fetch(url, {
method: 'POST',
headers: { "Authorization": `Bearer ${token}` },
body: formdata
})
return await data.json();
}
/**
* PUT /api/v1/statuses/{post_id}
* @param {string} host - The domain of the target server.
* @param {string} token - The application token
* @param {any} post_id - The ID of the post to edit.
* @param {any} post_data - The post content
*/
export async function editPost(host, token, post_id, post_data) {
let formdata = new FormData();
for (const key in post_data) {
formdata.append(key, post_data[key]);
}
let url = `https://${host}/api/v1/statuses/${post_id}`;
const data = await fetch(url, {
method: 'PUT',
headers: { "Authorization": `Bearer ${token}` },
body: formdata
})
return await data.json();
}
/**
* DELETE /api/v1/statuses/{post_id}
* Returns the deleted post's data, in the case of republishing.
* @param {string} host - The domain of the target server.
* @param {string} token - The application token
* @param {any} post_id - The ID of the post to delete.
*/
export async function deletePost(host, token, post_id) {
let url = `https://${host}/api/v1/statuses/${post_id}`;
const data = await fetch(url, {
method: 'DELETE',
headers: { "Authorization": `Bearer ${token}` },
})
return await data.json();
}
/**
* GET /api/v1/statuses/{post_id}/context.
* @param {string} host - The domain of the target server.
* @param {string} token - The application token.
* @param {string} post_id - The ID of the post to fetch.
*/
export async function getPostContext(host, token, post_id) {
let url = `https://${host}/api/v1/statuses/${post_id}/context`;
const data = await fetch(url, {
method: 'GET',
headers: { "Authorization": token ? `Bearer ${token}` : null }
}).then(res => res.json());
return data;
}
/**
* POST /api/v1/statuses/{post_id}/reblog.
* @param {string} host - The domain of the target server.
* @param {string} token - The application token.
* @param {string} post_id - The ID of the post to boost.
* @param {string} visibility - The visibility with which to boost the post.
*/
export async function boostPost(host, token, post_id, visibility) {
let url = `https://${host}/api/v1/statuses/${post_id}/reblog`;
let form = new FormData();
if (visibility) form.append("visibility", visibility);
const data = await fetch(url, {
method: 'POST',
headers: { "Authorization": `Bearer ${token}` },
body: form,
}).then(res => res.json());
return data;
}
/**
* POST /api/v1/statuses/{post_id}/unreblog.
* @param {string} host - The domain of the target server.
* @param {string} token - The application token.
* @param {string} post_id - The ID of the post to unboost.
*/
export async function unboostPost(host, token, post_id) {
let url = `https://${host}/api/v1/statuses/${post_id}/unreblog`;
const data = await fetch(url, {
method: 'POST',
headers: { "Authorization": `Bearer ${token}` }
}).then(res => res.json());
return data;
}
/**
* POST /api/v1/statuses/{post_id}/favourite.
* @param {string} host - The domain of the target server.
* @param {string} token - The application token.
* @param {string} post_id - The ID of the post to favourite.
*/
export async function favouritePost(host, token, post_id) {
let url = `https://${host}/api/v1/statuses/${post_id}/favourite`;
const data = await fetch(url, {
method: 'POST',
headers: { "Authorization": `Bearer ${token}` }
}).then(res => res.json());
return data;
}
/**
* POST /api/v1/statuses/{post_id}/unfavourite.
* @param {string} host - The domain of the target server.
* @param {string} token - The application token.
* @param {string} post_id - The ID of the post to unfavourite.
*/
export async function unfavouritePost(host, token, post_id) {
let url = `https://${host}/api/v1/statuses/${post_id}/unfavourite`;
const data = await fetch(url, {
method: 'POST',
headers: { "Authorization": `Bearer ${token}` }
}).then(res => res.json());
return data;
}
/**
* POST /api/v1/statuses/{post_id}/react/{shortcode}
* @param {string} host - The domain of the target server.
* @param {string} token - The application token.
* @param {string} post_id - The ID of the post to favourite.
* @param {string} shortcode - The shortcode of the emote to react with.
*/
export async function reactPost(host, token, post_id, shortcode) {
// note: reacting with foreign emotes is unsupported on most servers
// chuckya appears to allow this, but other servers tested have
// not demonstrated this.
let url = `https://${host}/api/v1/statuses/${post_id}/react/${encodeURIComponent(shortcode)}`;
const data = await fetch(url, {
method: 'POST',
headers: { "Authorization": `Bearer ${token}` }
}).then(res => res.json());
return data;
}
/**
* POST /api/v1/statuses/{post_id}/unreact/{shortcode}
* @param {string} host - The domain of the target server.
* @param {string} token - The application token.
* @param {string} post_id - The ID of the post to favourite.
* @param {string} shortcode - The shortcode of the reaction emote to remove.
*/
export async function unreactPost(host, token, post_id, shortcode) {
let url = `https://${host}/api/v1/statuses/${post_id}/unreact/${encodeURIComponent(shortcode)}`;
const data = await fetch(url, {
method: 'POST',
headers: { "Authorization": `Bearer ${token}` }
}).then(res => res.json());
return data;
}
/**
* GET /api/v1/accounts/{user_id}
* @param {string} host - The domain of the target server.
* @param {string} token - The application token.
* @param {string} user_id - The ID of the user to fetch.
*/
export async function getUser(host, token, user_id) {
let url = `https://${host}/api/v1/accounts/${user_id}`;
const data = await fetch(url, {
method: 'GET',
headers: { "Authorization": token ? `Bearer ${token}` : null }
}).then(res => res.json());
return data;
}
/**
* GET /api/v1/accounts/lookup?acct={handle}
* @param {string} host - The domain of the target server.
* @param {string} token - The application token.
* @param {string} handle - The handle of the user to fetch.
*/
export async function lookupUser(host, token, handle) {
let url = `https://${host}/api/v1/accounts/lookup?acct=${handle}`;
const res = await fetch(url, {
method: 'GET',
headers: { "Authorization": token ? `Bearer ${token}` : null }
});
if (!res.ok) {
const json = await res.json();
if (json.error = errors.AUTHENTICATION_FAILED)
throw new Error("This method requires authentication");
}
const data = await res.json();
return data;
}
/**
* GET /api/v1/accounts/{user_id}/statuses
* @param {string} host - The domain of the target server.
* @param {string} token - The application token.
* @param {string} user_id - The ID of the user to fetch.
* @param {string} max_id - If provided, only shows notifications before this ID.
* @param {boolean} replies - If replies should be fetched.
* @param {boolean} boosts - If boosts should be fetched.
* @param {boolean} only_media - If only media should be fetched.
*/
export async function getUserPosts(host, token, user_id, max_id, show_replies, show_boosts, only_media) {
let url = new URL(`https://${host}/api/v1/accounts/${user_id}/statuses`);
let query = [];
if (!show_replies)
query.push('exclude_replies=true');
if (!show_boosts)
query.push('exclude_boosts=true');
if (only_media)
query.push('only_media=true');
if (max_id)
query.push(`max_id=${max_id}`);
url.search = query.join('&');
const data = await fetch(url, {
method: 'GET',
headers: { "Authorization": token ? `Bearer ${token}` : null }
}).then(res => res.json());
return data;
}
/**
* GET /api/v1/accounts/{user_id}/statuses?pinned=true
* @param {string} host - The domain of the target server.
* @param {string} token - The application token.
* @param {string} user_id - The ID of the user to fetch.
*/
export async function getUserPinnedPosts(host, token, user_id) {
let url = `https://${host}/api/v1/accounts/${user_id}/statuses?pinned=true`;
const data = await fetch(url, {
method: 'GET',
headers: { "Authorization": token ? `Bearer ${token}` : null }
}).then(res => res.json());
return data;
}