ckolderup / postmarks

a single-user bookmarking website designed to live on the Fediverse
https://postmarks.glitch.me
MIT License
466 stars 39 forks source link

Edited bookmark doesn't edit the activity #146

Open TomCasavant opened 1 year ago

TomCasavant commented 1 year ago

I edited this bookmark: https://tomcasavant.glitch.me/bookmark/19 to correct "twee" to "tweet" and that doesn't seem to have been edited in the actual activity. I assume ActivityPub has some sort of 'edit' endpoint that needs to be hit or something? image

ckolderup commented 1 year ago

Thanks for reporting-- this was working at some point (this switch case should handle it) but this code got heavily refactored a couple times in August/September, so probably without tests available something got broken.

TomCasavant commented 1 year ago

https://github.com/ckolderup/postmarks/blob/0522da2c250ad139c67a5dbc42c1a084d8f01a1b/src/activitypub.js#L90

Looks like there must have been problems w/ the update activity at some point and it was switched to create

ckolderup commented 1 year ago

oof, I wish I had documented more thoroughly what I was seeing there, because that seems like a mistake.

I'm not an expert on the Mastodon codebase but it appears that edits get broadcast as "Update" objects with a special array for the id and the to and cc set to the original post id, which is interesting: https://github.com/mastodon/mastodon/blob/c86ad4566003f15f2f4f2499efd36f20b62737ab/app/workers/activitypub/status_update_distribution_worker.rb#L18-L28

TomCasavant commented 1 year ago

That line has been in there for 9 months though so if it was working before I doubt that's what broke it, I'm not sure how mastodon interprets a 'create' notice on an object that already exists

ckolderup commented 1 year ago

Yeah, I mean I'm just working from memory at this point since that was a stage of the project where I was working on it entirely on my own and doing extremely slipshod manual QA. I can take a stab at reproducing doing things "the Mastodon way" and see if that gets interop fixed for now.

(Also once we can confirm it works, getting tests set up for some degree of reproducibility, which is actually on my todo list to scaffold this evening, as I finally have a free night at home)

TomCasavant commented 2 months ago

https://community.nodebb.org/post/101114

It turns out that just sending the Update(Note) activity was not enough, the underlying object needed to also have the updated field set in order to Mastodon to process the update. My guess is Mastodon checks that field and only processes the note if the value is greater than the last known value.

I think this might be the issue (I haven't had the opportunity to test it yet)

TomCasavant commented 2 months ago

It works! image

Probably needs a lot of cleanup and I'm not sure when I'll get the time to fully test it so here's what I have working if anyone wants to beat me to it

async function createUpdateMessage(bookmark, account, domain, db) {
  const guid = await db.getGuidForBookmarkId(bookmark.id);
  const guidCreate = crypto.randomBytes(16).toString('hex');
  let note;
  const updatedBookmark = bookmark;

  console.log(bookmark.description)
  updatedBookmark.title = escapeHTML(bookmark.title);
  updatedBookmark.description = bookmark.description;
  console.log(updatedBookmark.description)

  let linkedTags = '';

  if (bookmark.tags && bookmark.tags.length > 0) {
    linkedTags = bookmark.tags
      ?.split(' ')
      .map((tag) => {
        const tagName = tag.slice(1);
        return `<a href="https://${domain}/tagged/${tagName}" class="mention hashtag" rel="tag nofollow noopener noreferrer">${tag}</a>`;
      })
      .join(' ');
  }

  if (updatedBookmark.description?.trim().length > 0) {
    updatedBookmark.description = `<br/>${updatedBookmark.description?.trim().replace('\n', '<br/>') || ''}`;
  }

  if (linkedTags.trim().length > 0) {
    linkedTags = `<p>${linkedTags}</p>`;
  }

  if (guid === undefined) {
    // If the bookmark was created but not published to ActivityPub, create a new note
    note = createNoteObject(bookmark, account, domain);
    await createMessage(note, bookmark.id, account, domain, db);
  } else {
    // Fetch the existing note object
    const existingMessage = await db.getMessage(guid);
    const existingNote = existingMessage ? JSON.parse(existingMessage.message) : null;

    const publishedDate = existingNote.published;

    //console.log(existingNote);
    note = {
      id: `https://${domain}/m/${guid}`,
      type: 'Note',
      attributedTo: `https://${domain}/u/${account}`,
      content: `<p><strong><a href="${updatedBookmark.url}" rel="nofollow noopener noreferrer">${replaceEmptyText(
      updatedBookmark.title,
      updatedBookmark.url,
    )}</a></strong>${updatedBookmark.description}</p>`,
      published: publishedDate, // Keep the original published date
      updated: new Date().toISOString(), // Set the updated date to now
      to: [`https://${domain}/u/${account}/followers/`, 'https://www.w3.org/ns/activitystreams#Public'],
      tag: []

    };

    bookmark.tags?.split(' ').forEach((tag) => {
      const tagName = tag.slice(1);
      note.tag.push({
        type: 'Hashtag',
        href: `https://${domain}/tagged/${tagName}`,
        name: tag,
      });
    });

    // Update the note in the database
    await db.updateNoteByGuid(guid, note);
  }

  const updateMessage = {
    '@context': ['https://www.w3.org/ns/activitystreams', 'https://w3id.org/security/v1'],
    summary: `${account} updated the bookmark`,
    id: `https://${domain}/m/${guidCreate}`,
    type: 'Update',
    actor: `https://${domain}/u/${account}`,
    object: note.id ? note : `https://${domain}/m/${guid}`,
  };
  console.log(updateMessage);

  //print(err);
  return updateMessage;
}