mirror of
https://github.com/AMNatty/Mastodon-Circles.git
synced 2024-11-21 18:47:26 -07:00
Implement support for *oma’s emoji reactions
Like in *key, *oma users can react with arbitrary emojis. Unlike in *key, a single user can add multiple emoji reactions to a single post and favouriting still exists as a separate concept. By using only the Mastodon API for *oma, favourites still get interpreted but emoji reactions aren’t taken into account at all. Fix this by adding support for the relevant Pleroma APIs. To avoid a bias towards fav+react or multiple reacts on a single post and to be more similar to connection strengths in Mastodon and *key, aggregate reacts and favs and count them only once per per user and post. It is not clear whether the existence of a non-empty pleroma/emoji_reactions field in notes data already implies support for the reactions API endpoint. It at least seems sensible and would allow to skip the initial feature metadata check, but let’s stick to the safe approach for now. Fedibird also supports emoji reacts and (like Akkoma >= 3.2.0) provides the emoji_reactions sections at the toplevel of notes with an array of account IDs included for every reaction. At first glance Fedibird doesn't seem to provide a /reactions API or similar with more detailed user info directly included. Supporting Fedibird would thereofore require additional user info lookups at the end. This commit makes no attempt at supporting Fedibird or taking advantage of the account_ids array for Akkoma. Co-authored-by: Natty <natty.sh.git@gmail.com>
This commit is contained in:
parent
fc16ce0640
commit
f5c2fb7f2d
2 changed files with 80 additions and 5 deletions
|
@ -42,6 +42,7 @@ async function apiRequest(url, options = null)
|
|||
* replies: number,
|
||||
* renotes: number,
|
||||
* favorites: number,
|
||||
* extra_reacts: boolean,
|
||||
* instance: string,
|
||||
* author?: FediUser,
|
||||
* }} Note
|
||||
|
@ -109,6 +110,14 @@ class ApiClient {
|
|||
return client;
|
||||
}
|
||||
|
||||
let features = apiResponse?.metadata?.features;
|
||||
if (Array.isArray(features) && features.includes("pleroma_api")) {
|
||||
const has_emoji_reacts = features.includes("pleroma_emoji_reactions");
|
||||
const client = new PleromaApiClient(instance, has_emoji_reacts);
|
||||
instanceTypeCache.set(instance, client);
|
||||
return client;
|
||||
}
|
||||
|
||||
const client = new MastodonApiClient(instance);
|
||||
instanceTypeCache.set(instance, client);
|
||||
return client;
|
||||
|
@ -144,10 +153,11 @@ class ApiClient {
|
|||
|
||||
/**
|
||||
* @param {Note} note
|
||||
* @param {boolean} extra_reacts
|
||||
*
|
||||
* return {Promise<FediUser[] | null>}
|
||||
*/
|
||||
async getFavs(note) { throw new Error("Not implemented"); }
|
||||
async getFavs(note, extra_reacts) { throw new Error("Not implemented"); }
|
||||
|
||||
/**
|
||||
* @return string
|
||||
|
@ -193,6 +203,8 @@ 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?.["pleroma"]?.["emoji_reactions"]?.length > 0,
|
||||
instance: this._instance,
|
||||
author: user
|
||||
}));
|
||||
|
@ -231,6 +243,8 @@ 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?.["pleroma"]?.["emoji_reactions"]?.length > 0,
|
||||
instance: handle.instance,
|
||||
author: {
|
||||
id: note["account"]["id"],
|
||||
|
@ -243,7 +257,7 @@ class MastodonApiClient extends ApiClient {
|
|||
});
|
||||
}
|
||||
|
||||
async getFavs(note) {
|
||||
async getFavs(note, extra_reacts) {
|
||||
const url = `https://${this._instance}/api/v1/statuses/${note.id}/favourited_by`;
|
||||
const response = await apiRequest(url);
|
||||
|
||||
|
@ -265,6 +279,61 @@ class MastodonApiClient extends ApiClient {
|
|||
}
|
||||
}
|
||||
|
||||
class PleromaApiClient 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) {
|
||||
// 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);
|
||||
|
||||
if (!this._emoji_reacts || !extra_reacts)
|
||||
return favs;
|
||||
|
||||
/**
|
||||
* @type {Map<string, FediUser>}
|
||||
*/
|
||||
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) ?? [];
|
||||
|
||||
for (const reaction of response) {
|
||||
reaction["accounts"]
|
||||
.map(account => ({
|
||||
id: account["id"],
|
||||
avatar: account["avatar"],
|
||||
bot: account["bot"],
|
||||
name: account["display_name"],
|
||||
handle: parseHandle(account["acct"], note.instance)
|
||||
}))
|
||||
.forEach(u => {
|
||||
if(!users.has(u.id))
|
||||
users.set(u.id, u);
|
||||
})
|
||||
}
|
||||
|
||||
return Array.from(users.values());
|
||||
}
|
||||
|
||||
getClientName() {
|
||||
return "pleroma";
|
||||
}
|
||||
}
|
||||
|
||||
class MisskeyApiClient extends ApiClient {
|
||||
/**
|
||||
* @param {string} instance
|
||||
|
@ -316,6 +385,7 @@ class MisskeyApiClient extends ApiClient {
|
|||
replies: note["repliesCount"],
|
||||
renotes: note["renoteCount"],
|
||||
favorites: Object.values(note["reactions"]).reduce((a, b) => a + b, 0),
|
||||
extra_reacts: false,
|
||||
instance: this._instance,
|
||||
author: user
|
||||
}));
|
||||
|
@ -366,6 +436,7 @@ class MisskeyApiClient extends ApiClient {
|
|||
replies: reply["repliesCount"],
|
||||
renotes: reply["renoteCount"],
|
||||
favorites: Object.values(reply["reactions"]).reduce((a, b) => a + b, 0),
|
||||
extra_reacts: false,
|
||||
instance: handle.instance,
|
||||
author: {
|
||||
id: reply["user"]["id"],
|
||||
|
@ -378,7 +449,7 @@ class MisskeyApiClient extends ApiClient {
|
|||
});
|
||||
}
|
||||
|
||||
async getFavs(note) {
|
||||
async getFavs(note, extra_reacts) {
|
||||
const url = `https://${this._instance}/api/notes/reactions`;
|
||||
const response = await apiRequest(url, {
|
||||
method: "POST",
|
||||
|
@ -455,6 +526,9 @@ async function circleMain() {
|
|||
case "mastodon":
|
||||
client = new MastodonApiClient(selfUser.instance);
|
||||
break;
|
||||
case "pleroma":
|
||||
client = new PleromaApiClient(selfUser.instance, true);
|
||||
break;
|
||||
case "misskey":
|
||||
client = new MisskeyApiClient(selfUser.instance);
|
||||
break;
|
||||
|
@ -524,8 +598,8 @@ async function processNotes(client, connectionList, notes) {
|
|||
* @param {Note} note
|
||||
*/
|
||||
async function evaluateNote(client, connectionList, note) {
|
||||
if (note.favorites > 0) {
|
||||
await client.getFavs(note).then(users => {
|
||||
if (note.favorites > 0 || note.extra_reacts) {
|
||||
await client.getFavs(note, note.extra_reacts).then(users => {
|
||||
if (!users)
|
||||
return;
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
<label><input type="radio" name="backend" value="detect" autocomplete="off" checked> Autodetect</label>
|
||||
<label><input type="radio" name="backend" value="mastodon" autocomplete="off"> Mastodon API</label>
|
||||
<label><input type="radio" name="backend" value="misskey" autocomplete="off"> Misskey API</label>
|
||||
<label><input type="radio" name="backend" value="pleroma" autocomplete="off"> Pleroma API</label>
|
||||
</span>
|
||||
<br>
|
||||
<button type="submit" id="generateButton">Generate circle</button>
|
||||
|
|
Loading…
Reference in a new issue