mirror of
https://github.com/AMNatty/Mastodon-Circles.git
synced 2024-11-24 12:07:25 -07:00
Rudimentary error reporting
This commit is contained in:
parent
5ae5c2f8d6
commit
1ea9d65571
1 changed files with 104 additions and 74 deletions
178
create-circle.js
178
create-circle.js
|
@ -18,10 +18,13 @@ async function apiRequestWithHeaders(url, options = null) {
|
||||||
|
|
||||||
throw new Error(`Error fetching ${url}: ${response.status} ${response.statusText}`);
|
throw new Error(`Error fetching ${url}: ${response.status} ${response.statusText}`);
|
||||||
})
|
})
|
||||||
.then(async response => ({ headers: response.headers, body: await response.json() }))
|
.then(async response => ({ response: { headers: response.headers, body: await response.json(), error: undefined }}))
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error(`Error fetching ${url}: ${error}`);
|
console.error(`Error fetching ${url}:`, error);
|
||||||
return null;
|
return {
|
||||||
|
response: undefined,
|
||||||
|
error: `Error fetching ${url}: ${error}`
|
||||||
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,7 +35,16 @@ async function apiRequestWithHeaders(url, options = null) {
|
||||||
*/
|
*/
|
||||||
async function apiRequest(url, options = null) {
|
async function apiRequest(url, options = null) {
|
||||||
const reply = await apiRequestWithHeaders(url, options);
|
const reply = await apiRequestWithHeaders(url, options);
|
||||||
return reply?.body;
|
|
||||||
|
if (!reply.response) {
|
||||||
|
return {
|
||||||
|
error: reply.error
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
response: reply.response.body
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function Handle(name, instance) {
|
function Handle(name, instance) {
|
||||||
|
@ -80,7 +92,7 @@ Handle.prototype.webFinger = async function () {
|
||||||
resource: `acct:${this}`
|
resource: `acct:${this}`
|
||||||
});
|
});
|
||||||
|
|
||||||
let webFinger = await apiRequest(defaultWebfingerUrl);
|
let { response: webFinger } = await apiRequest(defaultWebfingerUrl);
|
||||||
|
|
||||||
if (!webFinger) {
|
if (!webFinger) {
|
||||||
const contentTypeXrd = "application/xrd+xml";
|
const contentTypeXrd = "application/xrd+xml";
|
||||||
|
@ -104,7 +116,7 @@ Handle.prototype.webFinger = async function () {
|
||||||
?.getAttribute("template");
|
?.getAttribute("template");
|
||||||
|
|
||||||
if (webfingerTemplate)
|
if (webfingerTemplate)
|
||||||
webFinger = await apiRequest(webfingerTemplate.replace("{uri}", `acct:${this}`));
|
webFinger = (await apiRequest(webfingerTemplate.replace("{uri}", `acct:${this}`)))?.response;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!webFinger)
|
if (!webFinger)
|
||||||
|
@ -204,7 +216,7 @@ class ApiClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
let url = `https://${instance}/.well-known/nodeinfo`;
|
let url = `https://${instance}/.well-known/nodeinfo`;
|
||||||
let nodeInfo = await apiRequest(url);
|
let { response: nodeInfo } = await apiRequest(url);
|
||||||
|
|
||||||
if (!nodeInfo || !Array.isArray(nodeInfo.links)) {
|
if (!nodeInfo || !Array.isArray(nodeInfo.links)) {
|
||||||
const client = new MastodonApiClient(instance, true);
|
const client = new MastodonApiClient(instance, true);
|
||||||
|
@ -226,11 +238,11 @@ class ApiClient {
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
let apiResponse = await apiRequest(apiLink.href);
|
let { response: apiResponse } = await apiRequest(apiLink.href);
|
||||||
|
|
||||||
if (!apiResponse) {
|
if (!apiResponse) {
|
||||||
// Guess from API endpoints
|
// Guess from API endpoints
|
||||||
const misskeyMeta = await apiRequest(`https://${instance}/api/meta`, {
|
const { response: misskeyMeta } = await apiRequest(`https://${instance}/api/meta`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json"
|
||||||
|
@ -270,7 +282,7 @@ class ApiClient {
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
let features = apiResponse?.metadata?.features;
|
let features = apiResponse?.metadata?.["features"];
|
||||||
if (Array.isArray(features) && features.includes("pleroma_api")) {
|
if (Array.isArray(features) && features.includes("pleroma_api")) {
|
||||||
const has_emoji_reacts = features.includes("pleroma_emoji_reactions");
|
const has_emoji_reacts = features.includes("pleroma_emoji_reactions");
|
||||||
const client = new PleromaApiClient(instance, has_emoji_reacts);
|
const client = new PleromaApiClient(instance, has_emoji_reacts);
|
||||||
|
@ -286,14 +298,14 @@ class ApiClient {
|
||||||
/**
|
/**
|
||||||
* @param {Handle} handle
|
* @param {Handle} handle
|
||||||
*
|
*
|
||||||
* @returns {Promise<FediUser>}
|
* @returns {Promise<{ response: FediUser, error: undefined } | { response: undefined, error: string }>}
|
||||||
*/
|
*/
|
||||||
async getUserIdFromHandle(handle){ throw new Error("Not implemented"); }
|
async getUserIdFromHandle(handle){ throw new Error("Not implemented"); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {FediUser} user
|
* @param {FediUser} user
|
||||||
*
|
*
|
||||||
* @returns {Promise<Note[]>}
|
* @returns {Promise<{ response: Note[], error: undefined } | { response: undefined, error: string }>}
|
||||||
*/
|
*/
|
||||||
async getNotes(user){ throw new Error("Not implemented"); }
|
async getNotes(user){ throw new Error("Not implemented"); }
|
||||||
|
|
||||||
|
@ -427,9 +439,13 @@ class MastodonApiClient extends ApiClient {
|
||||||
let remaining = targetCount;
|
let remaining = targetCount;
|
||||||
let data = [];
|
let data = [];
|
||||||
while (remaining > 0 && nextUrl !== null) {
|
while (remaining > 0 && nextUrl !== null) {
|
||||||
const reply = await apiRequestWithHeaders(nextUrl);
|
const { response: reply, error } = await apiRequestWithHeaders(nextUrl);
|
||||||
if (reply?.body === null) {
|
if (reply?.body == null || error) {
|
||||||
console.error(`Error while gathering entries. Returning incomplete data!`);
|
console.error(`Error while gathering entries. Returning incomplete data!`);
|
||||||
|
if (data.length === 0) {
|
||||||
|
return { error };
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
nextUrl = MastodonApiClient.getNextPage(reply.headers);
|
nextUrl = MastodonApiClient.getNextPage(reply.headers);
|
||||||
|
@ -443,37 +459,45 @@ class MastodonApiClient extends ApiClient {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return data.length === 0 ? null : data.flat();
|
return { response: data.length === 0 ? null : data.flat() };
|
||||||
}
|
}
|
||||||
|
|
||||||
async getUserIdFromHandle(handle) {
|
async getUserIdFromHandle(handle) {
|
||||||
const url = `https://${this._instance}/api/v1/accounts/lookup?acct=${handle.baseHandle}`;
|
const url = `https://${this._instance}/api/v1/accounts/lookup?acct=${handle.baseHandle}`;
|
||||||
let response = await apiRequest(url, null);
|
let { response } = await apiRequest(url, null);
|
||||||
|
|
||||||
if (!response) {
|
if (!response) {
|
||||||
const url = `https://${this._instance}/api/v1/accounts/lookup?acct=${handle}`;
|
const url = `https://${this._instance}/api/v1/accounts/lookup?acct=${handle}`;
|
||||||
response = await apiRequest(url, null);
|
|
||||||
|
const { response: res, error } = (await apiRequest(url, null));
|
||||||
|
if (error) {
|
||||||
|
return { error };
|
||||||
|
}
|
||||||
|
|
||||||
|
response = res;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!response) {
|
if (typeof response !== "object") {
|
||||||
return null;
|
return { error: `Could not parse user lookup response from server, invalid object: ${response}` };
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: response.id,
|
response: {
|
||||||
avatar: response.avatar,
|
id: response.id,
|
||||||
bot: response.bot,
|
avatar: response.avatar,
|
||||||
name: response["display_name"],
|
bot: response.bot,
|
||||||
handle: handle,
|
name: response["display_name"],
|
||||||
|
handle: handle,
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async getNotes(user) {
|
async getNotes(user) {
|
||||||
const url = `https://${this._instance}/api/v1/accounts/${user.id}/statuses?exclude_replies=true&exclude_reblogs=true&limit=${this._API_LIMIT_SMALL}`;
|
const url = `https://${this._instance}/api/v1/accounts/${user.id}/statuses?exclude_replies=true&exclude_reblogs=true&limit=${this._API_LIMIT_SMALL}`;
|
||||||
const response = await MastodonApiClient.apiRequestPaged(url, this._CNT_NOTES, this._API_LIMIT_SMALL, true);
|
const { response, error} = await MastodonApiClient.apiRequestPaged(url, this._CNT_NOTES, this._API_LIMIT_SMALL, true);
|
||||||
|
|
||||||
if (!response) {
|
if (error) {
|
||||||
return null;
|
return { error };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response?.some(note => note?.["pleroma"]?.["emoji_reactions"]?.length)) {
|
if (response?.some(note => note?.["pleroma"]?.["emoji_reactions"]?.length)) {
|
||||||
|
@ -482,15 +506,17 @@ class MastodonApiClient extends ApiClient {
|
||||||
this._flavor = MastodonFlavor.FEDIBIRD;
|
this._flavor = MastodonFlavor.FEDIBIRD;
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.map(note => ({
|
return {
|
||||||
id: note.id,
|
response: response.map(note => ({
|
||||||
replies: note["replies_count"] || 0,
|
id: note.id,
|
||||||
renotes: note["reblogs_count"] || 0,
|
replies: note["replies_count"] || 0,
|
||||||
favorites: note["favourites_count"],
|
renotes: note["reblogs_count"] || 0,
|
||||||
extra_reacts: note?.["emoji_reactions"]?.length > 0 || note?.["pleroma"]?.["emoji_reactions"]?.length > 0,
|
favorites: note["favourites_count"],
|
||||||
instance: this._instance,
|
extra_reacts: note?.["emoji_reactions"]?.length > 0 || note?.["pleroma"]?.["emoji_reactions"]?.length > 0,
|
||||||
author: user
|
instance: this._instance,
|
||||||
}));
|
author: user
|
||||||
|
}))
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async getRenotes(note) {
|
async getRenotes(note) {
|
||||||
|
@ -513,7 +539,7 @@ class MastodonApiClient extends ApiClient {
|
||||||
async getReplies(noteIn) {
|
async getReplies(noteIn) {
|
||||||
// The context endpoint has no limit parameter or pages
|
// The context endpoint has no limit parameter or pages
|
||||||
const url = `https://${this._instance}/api/v1/statuses/${noteIn.id}/context`;
|
const url = `https://${this._instance}/api/v1/statuses/${noteIn.id}/context`;
|
||||||
const response = await apiRequest(url);
|
const response = (await apiRequest(url)).response;
|
||||||
|
|
||||||
if (!response) {
|
if (!response) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -549,7 +575,7 @@ class MastodonApiClient extends ApiClient {
|
||||||
|
|
||||||
async getFavs(note) {
|
async getFavs(note) {
|
||||||
const url = `https://${this._instance}/api/v1/statuses/${note.id}/favourited_by?limit=${this._API_LIMIT}`;
|
const url = `https://${this._instance}/api/v1/statuses/${note.id}/favourited_by?limit=${this._API_LIMIT}`;
|
||||||
const response = await MastodonApiClient.apiRequestPaged(url, this._CNT_FAVS, this._API_LIMIT);
|
const response = (await MastodonApiClient.apiRequestPaged(url, this._CNT_FAVS, this._API_LIMIT)).response;
|
||||||
|
|
||||||
if (!response) {
|
if (!response) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -592,7 +618,7 @@ class PleromaApiClient extends MastodonApiClient {
|
||||||
|
|
||||||
// The documentation doesn't specify the hardcoded limit, so just use the lowest known one
|
// The documentation doesn't specify the hardcoded limit, so just use the lowest known one
|
||||||
const url = `https://${this._instance}/api/v1/pleroma/statuses/${note.id}/reactions?limit=${this._API_LIMIT_SMALL}`;
|
const url = `https://${this._instance}/api/v1/pleroma/statuses/${note.id}/reactions?limit=${this._API_LIMIT_SMALL}`;
|
||||||
const response = await MastodonApiClient.apiRequestPaged(url, this._CNT_FAVS, this._API_LIMIT_SMALL) ?? [];
|
const response = (await MastodonApiClient.apiRequestPaged(url, this._CNT_FAVS, this._API_LIMIT_SMALL)).response ?? [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {Map<string, FediUser>}
|
* @type {Map<string, FediUser>}
|
||||||
|
@ -642,7 +668,7 @@ class FedibirdApiClient extends MastodonApiClient {
|
||||||
|
|
||||||
// Could not locate documentation for Fedibird API, so just use the lowest known limit
|
// Could not locate documentation for Fedibird API, so just use the lowest known limit
|
||||||
const url = `https://${this._instance}/api/v1/statuses/${note.id}/emoji_reactioned_by?limit=${this._API_LIMIT_SMALL}`;
|
const url = `https://${this._instance}/api/v1/statuses/${note.id}/emoji_reactioned_by?limit=${this._API_LIMIT_SMALL}`;
|
||||||
const response = await MastodonApiClient.apiRequestPaged(url, this._CNT_FAVS, this._API_LIMIT_SMALL) ?? [];
|
const response = (await MastodonApiClient.apiRequestPaged(url, this._CNT_FAVS, this._API_LIMIT_SMALL)).response ?? [];
|
||||||
|
|
||||||
for (const reaction of response) {
|
for (const reaction of response) {
|
||||||
let account = reaction["account"];
|
let account = reaction["account"];
|
||||||
|
@ -682,7 +708,7 @@ class MisskeyApiClient extends ApiClient {
|
||||||
|
|
||||||
async getUserIdFromHandle(handle) {
|
async getUserIdFromHandle(handle) {
|
||||||
const lookupUrl = `https://${this._instance}/api/users/search-by-username-and-host`;
|
const lookupUrl = `https://${this._instance}/api/users/search-by-username-and-host`;
|
||||||
const lookup = await apiRequest(lookupUrl, {
|
const lookup = (await apiRequest(lookupUrl, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json"
|
||||||
|
@ -691,7 +717,7 @@ class MisskeyApiClient extends ApiClient {
|
||||||
username: handle.name,
|
username: handle.name,
|
||||||
host: null
|
host: null
|
||||||
}
|
}
|
||||||
});
|
})).response;
|
||||||
|
|
||||||
let id = null;
|
let id = null;
|
||||||
|
|
||||||
|
@ -707,7 +733,7 @@ class MisskeyApiClient extends ApiClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = `https://${this._instance}/api/users/show`;
|
const url = `https://${this._instance}/api/users/show`;
|
||||||
const response = await apiRequest(url, {
|
const { response, error } = await apiRequest(url, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json"
|
||||||
|
@ -718,22 +744,24 @@ class MisskeyApiClient extends ApiClient {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response) {
|
if (error) {
|
||||||
return null;
|
return { error };
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: response.id,
|
response: {
|
||||||
avatar: response["avatarUrl"],
|
id: response.id,
|
||||||
bot: response["isBot"],
|
avatar: response["avatarUrl"],
|
||||||
name: response["name"],
|
bot: response["isBot"],
|
||||||
handle: handle,
|
name: response["name"],
|
||||||
|
handle: handle,
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async getNotes(user) {
|
async getNotes(user) {
|
||||||
const url = `https://${this._instance}/api/users/notes`;
|
const url = `https://${this._instance}/api/users/notes`;
|
||||||
const response = await apiRequest(url, {
|
const { response, error } = await apiRequest(url, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json"
|
||||||
|
@ -748,24 +776,26 @@ class MisskeyApiClient extends ApiClient {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response) {
|
if (error) {
|
||||||
return null;
|
return { error };
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.map(note => ({
|
return {
|
||||||
id: note.id,
|
response: response.map(note => ({
|
||||||
replies: note["repliesCount"],
|
id: note.id,
|
||||||
renotes: note["renoteCount"],
|
replies: note["repliesCount"],
|
||||||
favorites: Object.values(note["reactions"]).reduce((a, b) => a + b, 0),
|
renotes: note["renoteCount"],
|
||||||
extra_reacts: false,
|
favorites: Object.values(note["reactions"]).reduce((a, b) => a + b, 0),
|
||||||
instance: this._instance,
|
extra_reacts: false,
|
||||||
author: user
|
instance: this._instance,
|
||||||
}));
|
author: user
|
||||||
|
}))
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async getRenotes(note) {
|
async getRenotes(note) {
|
||||||
const url = `https://${this._instance}/api/notes/renotes`;
|
const url = `https://${this._instance}/api/notes/renotes`;
|
||||||
const response = await apiRequest(url, {
|
const response = (await apiRequest(url, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json"
|
||||||
|
@ -774,7 +804,7 @@ class MisskeyApiClient extends ApiClient {
|
||||||
noteId: note.id,
|
noteId: note.id,
|
||||||
limit: this._CNT_RENOTES,
|
limit: this._CNT_RENOTES,
|
||||||
}
|
}
|
||||||
});
|
})).response;
|
||||||
|
|
||||||
if (!response) {
|
if (!response) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -791,7 +821,7 @@ class MisskeyApiClient extends ApiClient {
|
||||||
|
|
||||||
async getReplies(note) {
|
async getReplies(note) {
|
||||||
const url = `https://${this._instance}/api/notes/replies`;
|
const url = `https://${this._instance}/api/notes/replies`;
|
||||||
const response = await apiRequest(url, {
|
const response = (await apiRequest(url, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json"
|
||||||
|
@ -800,7 +830,7 @@ class MisskeyApiClient extends ApiClient {
|
||||||
noteId: note.id,
|
noteId: note.id,
|
||||||
limit: this._CNT_REPLIES,
|
limit: this._CNT_REPLIES,
|
||||||
}
|
}
|
||||||
});
|
})).response;
|
||||||
|
|
||||||
if (!response) {
|
if (!response) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -829,7 +859,7 @@ class MisskeyApiClient extends ApiClient {
|
||||||
|
|
||||||
async getFavs(note) {
|
async getFavs(note) {
|
||||||
const url = `https://${this._instance}/api/notes/reactions`;
|
const url = `https://${this._instance}/api/notes/reactions`;
|
||||||
const response = await apiRequest(url, {
|
const response = (await apiRequest(url, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json"
|
||||||
|
@ -838,7 +868,7 @@ class MisskeyApiClient extends ApiClient {
|
||||||
noteId: note.id,
|
noteId: note.id,
|
||||||
limit: this._CNT_FAVS,
|
limit: this._CNT_FAVS,
|
||||||
}
|
}
|
||||||
});
|
})).response;
|
||||||
|
|
||||||
if (!response) {
|
if (!response) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -921,10 +951,10 @@ async function circleMain() {
|
||||||
|
|
||||||
progress.innerText = "Fetching your user...";
|
progress.innerText = "Fetching your user...";
|
||||||
|
|
||||||
const user = await client.getUserIdFromHandle(selfUser);
|
const { response: user, error: userError } = await client.getUserIdFromHandle(selfUser);
|
||||||
|
|
||||||
if (!user) {
|
if (userError) {
|
||||||
alert("Something went horribly wrong, couldn't fetch your user.");
|
alert(`Something went horribly wrong, couldn't fetch your user:\n\n${userError}`);
|
||||||
fediHandle.disabled = false;
|
fediHandle.disabled = false;
|
||||||
for (const radio of backend) {
|
for (const radio of backend) {
|
||||||
radio.disabled = false;
|
radio.disabled = false;
|
||||||
|
@ -937,10 +967,10 @@ async function circleMain() {
|
||||||
|
|
||||||
progress.innerText = "Fetching your latest posts...";
|
progress.innerText = "Fetching your latest posts...";
|
||||||
|
|
||||||
const notes = await client.getNotes(user);
|
const { response: notes, error: noteError } = await client.getNotes(user);
|
||||||
|
|
||||||
if (!notes) {
|
if (noteError) {
|
||||||
alert("Something went horribly wrong, couldn't fetch your notes.");
|
alert(`Something went horribly wrong, couldn't fetch your notes:\n\n${noteError}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue