From 3e8d268d52e54d3ed515c05366524214376f340d Mon Sep 17 00:00:00 2001 From: Takeshi Umeda Date: Thu, 31 Aug 2023 10:24:40 +0000 Subject: [PATCH 1/2] Added Fedibird support --- create-circle.js | 67 ++++++++++++++++++++++++++++++++++++++++++++++-- index.html | 2 ++ 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/create-circle.js b/create-circle.js index ca65ec8..781413e 100644 --- a/create-circle.js +++ b/create-circle.js @@ -115,6 +115,12 @@ class ApiClient { let { software } = apiResponse; software.name = software.name.toLowerCase(); + if (software.name.includes("fedibird")) { + const client = new FedibirdApiClient(instance, true); + instanceTypeCache.set(instance, client); + return client; + } + if (software.name.includes("misskey") || software.name.includes("calckey") || software.name.includes("foundkey") || @@ -219,7 +225,7 @@ class MastodonApiClient extends ApiClient { renotes: note["reblogs_count"] || 0, favorites: note["favourites_count"], // Actually a Pleroma/Akkoma thing - extra_reacts: note?.["pleroma"]?.["emoji_reactions"]?.length > 0, + extra_reacts: note?.["emoji_reactions"]?.length > 0 || note?.["pleroma"]?.["emoji_reactions"]?.length > 0, instance: this._instance, author: user })); @@ -259,7 +265,7 @@ class MastodonApiClient extends ApiClient { renotes: note["reblogs_count"] || 0, favorites: note["favourites_count"], // Actually a Pleroma/Akkoma thing - extra_reacts: note?.["pleroma"]?.["emoji_reactions"]?.length > 0, + extra_reacts: note?.["emoji_reactions"]?.length > 0 || note?.["pleroma"]?.["emoji_reactions"]?.length > 0, instance: handle.instance, author: { id: note["account"]["id"], @@ -349,6 +355,60 @@ class PleromaApiClient extends MastodonApiClient { } } +class FedibirdApiClient extends MastodonApiClient { + /** + * @param {string} instance + * @param {boolean} emoji_reacts + */ + constructor(instance, emoji_reacts) { + super(instance); + this._emoji_reacts = emoji_reacts; + } + + async getFavs(note, extra_reacts) { + // Frdibird supports both favs and emoji reacts + // with several emoji reacts per users being possible. + // Coalesce them and count every user only once + let favs = await super.getFavs(note); + + if (!this._emoji_reacts || !extra_reacts) + return favs; + + /** + * @type {Map} + */ + let users = new Map(); + if (favs !== null) { + favs.forEach(u => { + users.set(u.id, u); + }); + } + + const url = `https://${this._instance}/api/v1/statuses/${note.id}/emoji_reactioned_by`; + const response = await apiRequest(url) ?? []; + + for (const reaction of response) { + let account = reaction["account"]; + let u = { + id: account["id"], + avatar: account["avatar"], + bot: account["bot"], + name: account["display_name"], + handle: parseHandle(account["acct"], note.instance) + } + + if(!users.has(u.id)) + users.set(u.id, u); + } + + return Array.from(users.values()); + } + + getClientName() { + return "fedibird"; + } +} + class MisskeyApiClient extends ApiClient { /** * @param {string} instance @@ -585,6 +645,9 @@ async function circleMain() { case "misskey": client = new MisskeyApiClient(selfUser.instance); break; + case "fedibird": + client = new FedibirdApiClient(selfUser.instance, true); + break; default: progress.innerText = "Detecting instance..."; client = await ApiClient.getClient(selfUser.instance); diff --git a/index.html b/index.html index ca25c9a..feb49ab 100644 --- a/index.html +++ b/index.html @@ -22,6 +22,7 @@ +
@@ -41,6 +42,7 @@


+

Added Fedibird support by noellabo

Contribute on Github

Thanks to Duiker101 for creating chirpty for Twitter

Original on Github

From a4a32cdabfe65c6edeadd540be71392a015f82a3 Mon Sep 17 00:00:00 2001 From: Natty Date: Fri, 1 Sep 2023 19:40:14 +0200 Subject: [PATCH 2/2] Mastodon flavor detection --- create-circle.js | 149 +++++++++++++++++++++++++++++++---------------- 1 file changed, 99 insertions(+), 50 deletions(-) diff --git a/create-circle.js b/create-circle.js index f3c736e..3f479e8 100644 --- a/create-circle.js +++ b/create-circle.js @@ -168,7 +168,7 @@ class ApiClient { let nodeInfo = await apiRequest(url); if (!nodeInfo || !Array.isArray(nodeInfo.links)) { - const client = new MastodonApiClient(instance); + const client = new MastodonApiClient(instance, true); instanceTypeCache.set(instance, client); return client; } @@ -182,7 +182,7 @@ class ApiClient { if (!apiLink) { console.error(`No NodeInfo API found for ${instance}}`); - const client = new MastodonApiClient(instance); + const client = new MastodonApiClient(instance, true); instanceTypeCache.set(instance, client); return client; } @@ -205,7 +205,7 @@ class ApiClient { return client; } - const client = new MastodonApiClient(instance); + const client = new MastodonApiClient(instance, true); instanceTypeCache.set(instance, client); return client; } @@ -237,7 +237,7 @@ class ApiClient { return client; } - const client = new MastodonApiClient(instance); + const client = new MastodonApiClient(instance, true); instanceTypeCache.set(instance, client); return client; } @@ -272,11 +272,50 @@ class ApiClient { /** * @param {Note} note - * @param {boolean} extra_reacts * * return {Promise} */ - async getFavs(note, extra_reacts) { throw new Error("Not implemented"); } + async getReactions(note){ return []; } + + /** + * @param {Note} note + * + * @returns {Promise} + */ + async getFavs(note) { throw new Error("Not implemented"); } + + /** + * @param {Note} note + * @param {boolean} extra_reacts Also include emoji reacts + * + * return {Promise} + */ + async getConsolidatedReactions(note, extra_reacts = false){ + let favs = await this.getFavs(note); + + if (!extra_reacts) + return favs; + + /** + * @type {Map} + */ + let users = new Map(); + if (favs !== null) { + favs.forEach(u => { + users.set(u.id, u); + }); + } + + const reactions = await this.getReactions(note); + if (reactions !== null) { + reactions.forEach(u => { + users.set(u.id, u); + }); + } + + + return Array.from(users.values()); + } /** * @return string @@ -287,9 +326,13 @@ class ApiClient { class MastodonApiClient extends ApiClient { /** * @param {string} instance + * @param {boolean} emoji_reacts + * @param {MastodonApiClient} flavor */ - constructor(instance) { + constructor(instance, emoji_reacts, flavor = MastodonFlavor.MASTODON) { super(instance); + this._emoji_reacts = emoji_reacts; + this._flavor = flavor; } async getUserIdFromHandle(handle) { @@ -322,12 +365,17 @@ class MastodonApiClient extends ApiClient { return null; } + if (response?.some(note => note?.["emoji_reactions"]?.length)) { + this._flavor = MastodonFlavor.FEDIBIRD; + } else if (response?.some(note => note?.["pleroma"]?.["emoji_reactions"]?.length)) { + this._flavor = MastodonFlavor.PLEROMA; + } + return response.map(note => ({ id: note.id, replies: note["replies_count"] || 0, renotes: note["reblogs_count"] || 0, favorites: note["favourites_count"], - // Actually a Pleroma/Akkoma thing extra_reacts: note?.["emoji_reactions"]?.length > 0 || note?.["pleroma"]?.["emoji_reactions"]?.length > 0, instance: this._instance, author: user @@ -359,7 +407,14 @@ class MastodonApiClient extends ApiClient { return null; } + if (response["descendants"]?.some(note => note?.["emoji_reactions"]?.length)) { + this._flavor = MastodonFlavor.FEDIBIRD; + } else if (response["descendants"]?.some(note => note?.["pleroma"]?.["emoji_reactions"]?.length)) { + this._flavor = MastodonFlavor.PLEROMA; + } + return response["descendants"].map(note => { + let handle = parseHandle(note["account"]["acct"], noteIn.instance); return { @@ -367,7 +422,6 @@ class MastodonApiClient extends ApiClient { replies: note["replies_count"] || 0, renotes: note["reblogs_count"] || 0, favorites: note["favourites_count"], - // Actually a Pleroma/Akkoma thing extra_reacts: note?.["emoji_reactions"]?.length > 0 || note?.["pleroma"]?.["emoji_reactions"]?.length > 0, instance: handle.instance, author: { @@ -381,7 +435,7 @@ class MastodonApiClient extends ApiClient { }); } - async getFavs(note, extra_reacts) { + async getFavs(note) { const url = `https://${this._instance}/api/v1/statuses/${note.id}/favourited_by`; const response = await apiRequest(url); @@ -398,6 +452,14 @@ class MastodonApiClient extends ApiClient { })); } + async getReactions(note) { + if (this._flavor === MastodonFlavor.MASTODON) { + return []; + } + + return this._flavor.getReactions.call(this, note); + } + getClientName() { return "mastodon"; } @@ -409,31 +471,20 @@ class PleromaApiClient extends MastodonApiClient { * @param {boolean} emoji_reacts */ constructor(instance, emoji_reacts) { - super(instance); - this._emoji_reacts = emoji_reacts; + super(instance, emoji_reacts, MastodonFlavor.PLEROMA); } - async getFavs(note, extra_reacts) { - // Pleroma/Akkoma supports both favs and emoji reacts - // with several emoji reacts per users being possible. - // Coalesce them and count every user only once - let favs = await super.getFavs(note); + async getReactions(note) { + if (!this._emoji_reacts) + return []; - if (!this._emoji_reacts || !extra_reacts) - return favs; + const url = `https://${this._instance}/api/v1/pleroma/statuses/${note.id}/reactions`; + const response = await apiRequest(url) ?? []; /** * @type {Map} */ - let users = new Map(); - if (favs !== null) { - favs.forEach(u => { - users.set(u.id, u); - }); - } - - const url = `https://${this._instance}/api/v1/pleroma/statuses/${note.id}/reactions`; - const response = await apiRequest(url) ?? []; + const users = new Map(); for (const reaction of response) { reaction["accounts"] @@ -464,28 +515,17 @@ class FedibirdApiClient extends MastodonApiClient { * @param {boolean} emoji_reacts */ constructor(instance, emoji_reacts) { - super(instance); - this._emoji_reacts = emoji_reacts; + super(instance, emoji_reacts, MastodonFlavor.FEDIBIRD); } - async getFavs(note, extra_reacts) { - // Frdibird supports both favs and emoji reacts - // with several emoji reacts per users being possible. - // Coalesce them and count every user only once - let favs = await super.getFavs(note); - - if (!this._emoji_reacts || !extra_reacts) - return favs; + async getReactions(note) { + if (!this._emoji_reacts) + return []; /** * @type {Map} */ let users = new Map(); - if (favs !== null) { - favs.forEach(u => { - users.set(u.id, u); - }); - } const url = `https://${this._instance}/api/v1/statuses/${note.id}/emoji_reactioned_by`; const response = await apiRequest(url) ?? []; @@ -512,6 +552,12 @@ class FedibirdApiClient extends MastodonApiClient { } } +const MastodonFlavor = { + MASTODON: MastodonApiClient.prototype, + PLEROMA: PleromaApiClient.prototype, + FEDIBIRD: FedibirdApiClient.prototype, +}; + class MisskeyApiClient extends ApiClient { /** * @param {string} instance @@ -665,7 +711,7 @@ class MisskeyApiClient extends ApiClient { }); } - async getFavs(note, extra_reacts) { + async getFavs(note) { const url = `https://${this._instance}/api/notes/reactions`; const response = await apiRequest(url, { method: "POST", @@ -736,7 +782,7 @@ async function circleMain() { let client; switch (backend.value) { case "mastodon": - client = new MastodonApiClient(selfUser.apiInstance); + client = new MastodonApiClient(selfUser.apiInstance, true); break; case "pleroma": client = new PleromaApiClient(selfUser.apiInstance, true); @@ -744,13 +790,16 @@ async function circleMain() { case "misskey": client = new MisskeyApiClient(selfUser.apiInstance); break; - case "fedibird": - client = new FedibirdApiClient(selfUser.instance, true); - break; default: progress.innerText = "Detecting instance..."; client = await ApiClient.getClient(selfUser.apiInstance); - backend.value = client.getClientName(); + + backend.value = (() => { + switch (client.getClientName()) { + case "fedibird": return "mastodon"; + default: return client.getClientName(); + } + })(); break; } @@ -812,7 +861,7 @@ async function processNotes(client, connectionList, notes) { */ async function evaluateNote(client, connectionList, note) { if (note.favorites > 0 || note.extra_reacts) { - await client.getFavs(note, note.extra_reacts).then(users => { + await client.getConsolidatedReactions(note, note.extra_reacts).then(users => { if (!users) return;