Skip to content

Piero Bosio Social Web Site Personale Logo Fediverso

Social Forum federato con il resto del mondo. Non contano le istanze, contano le persone

where would you expect quotes to show up in #WordPress ?

Fediverso
20 9 62

Gli ultimi otto messaggi ricevuti dalla Federazione
  • In the Fediverse track at SFSCon, Mayel de Borniol and Ivan Minutillo spoke about:

    Bonfire: Public Interest Social Networks

    https://fediforum.org/2025-11-sfscon/

    read more

  • Right on the heels of WordPress 6.9 we released a new version of the ActivityPub plugin, making quote comments visible in the Reactions block and bringing you new ways of customizing your author pages.

    Quotes Join the Reactions Party

    When someone quotes your post on Mastodon or other Fediverse platforms, you’ll now see it right alongside your likes and reposts. Quotes get their own row in the Fediverse Reactions display, making it easy to see at a glance who’s building on your ideas and adding their own commentary.

    Fediverse Reactions section showing three rows: 25 reposts displayed as a row of user avatars, 27 likes with their own row of avatars, and 1 quote with a single avatar—demonstrating how quotes now appear as a separate reaction type alongside likes and reposts.

    Behind the scenes, we improved how we’re detecting quotes. Different platforms have their own ways of handling quote posts, and not all of them speak the same language. The plugin now understands these variations better, so whether someone quotes you from Mastodon, Misskey, or elsewhere, it just works.

    This means your engagement stats tell a fuller story. A quote isn’t just a repost—it’s someone adding their voice to yours, and now WordPress can recognize and display that distinction.

    Show Off Your Fediverse Identity

    If you’ve set up extra fields on your Fediverse profile—things like your website, pronouns, location, or links to other accounts—you can now display them directly on your WordPress site with the new Extra Fields block.

    Fediverse Extra Fields block using the cards style, showing two profile fields displayed as separate bordered cards: 'Powered by' with value 'WordPress' and 'Blog' with a clickable URL, stacked vertically below the author profile header.Fediverse Extra Fields block using the default list style, showing profile fields in a compact table layout with labels on the left and values on the right: 'Powered by: WordPress' and 'Blog:' with a clickable URL.Fediverse Extra Fields block using the stacked style, showing profile fields with labels above their values: 'Powered by' above 'WordPress' and 'Blog' above a clickable URL, arranged vertically below the author profile header.

    Drop it onto any page, post, or your author archive template, pick a style that fits your theme, and your profile details appear right where your visitors can see them. Choose from a clean table layout, a stacked list, or styled cards. You can also control how many fields to show and customize colors to match your site.

    ChangelogAddedAdd documentation guide for using ActivityPub blocks in classic themes with Block Template PartsAdded a new Fediverse Extra Fields block to display ActivityPub extra fields, featuring compact, stacked, and card layouts with flexible user selection options.Added support for quote comments, improving detection and handling of quoted replies and links in post interactions.Add notifications for boosts, likes, and new followers in Mastodon apps via the Enable Mastodon Apps pluginAdds support for turning tags, categories, and custom taxonomies into federated collections in the Reader view so you can browse and follow topics more seamlessly.Prevent email notifications for comments on ActivityPub custom post types.Send a Reject activity when a quote comment is deleted, revoking previous quote permissions and ensuring consistent inbox handling.Store and retrieve webfinger acct for remote actors to improve identification and reduce lookupsChangedImprove gallery and image block markup for ap_posts with better alt text and optimized layouts.Improve support for media attachments by handling Audio, Document, and Video object types in addition to Images.Maintain consistent return values in Create handler.Remove trailing hashtags from incoming posts to prevent duplication with taxonomy tags.Store comments and reactions from followed actors on reader posts, and keep them separate from your site’s comments in wp-admin.Update compatibility testing for PHP 8.5 and WordPress 6.9Use tag name instead of slug for hashtag display.FixedAlways includes id, first, and last links in collection responses, ensuring followers and following lists display correctly in Mastodon.Automatically approves reactions on ActivityPub posts in the Reader view for a smoother, more seamless interaction experience.Deliver public activities to followers only.Disable REST API endpoints for internal post types.False mention email notifications for users in CC field without actual mention tags.Fix “Filename too long” errors when downloading attachments from URLs with query parameters (e.g., Instagram CDN URLs).Fix make_clickable corrupting existing anchor tags in ActivityPub contentFix PHP 8.5 deprecation warnings for ReflectionProperty::setAccessible() and ReflectionMethod::setAccessible()Improved handling of unusual activity data to avoid errors when activities contain unexpected formats.Preserve original ActivityPub activity timestamps when creating posts and comments instead of using current time.Prevented duplicate email notifications when ActivityPub instances re-send Follow activities for already-following actors.Prevents unwanted comment types—like pingbacks, trackbacks, notes and custom system comments, from being federated, ensuring only real user comments are shared with the fediverse.Removed a redundant instruction from the custom post content settings to simplify the UI.Reply block now shows fallback link when oEmbed fails instead of empty div.Simplified reply links by removing special handling for federated comments, making replies work the same for all comments where replying is allowed.Undefined array key warning in Scheduler::async_batch when called without arguments.DownloadsWordPress.org: activitypub.7.7.0.zipGitHub: tag/7.7.0Thank You!

    As always, a huge thanks to everyone who contributed code, reported bugs, tested early builds, and shared ideas. Every bit of feedback helps make ActivityPub for WordPress better for the whole community.

    Version 7.7.0 is available now—update and let us know what you think!

    read more

  • When someone quote-boosts one of my Mastodon toots why do I get two identical notifications? One for the quote and the other for the boost? I use the web client of an instance that runs Mastodon Glitch Edition.

    read more

  • @jan thank you Jan! Have a great day as well

    read more

  • @_elena I hope too, good job! Have a wonderful day Elena 😉

    read more

  • @jan hard to tell because it was towards the very end of the event and I think people were in a rush to go home. I hope we planted some seeds of curiosity

    read more

  • @_elena @morloi @niccolove so, what kind of reactions did you guys get? Any feedback from audience, like somebody coming after the stage time and asking some questions? On stage questions? I'm curious! 🙂

    read more

  • Quoting @morloi and re-sharing this here (translated) for you my Fedi friends:

    “Today I fulfilled a desire. Thanks to @_elena and @niccolove, I brought a little piece of the Fediverse to the stage at Social Media Strategies, perhaps the most important event in Italy dedicated to social media professionals. Surprised faces, a gesture of annoyance, and hopefully a bit of curiosity too….”

    Thank you so much Morloi! 💖

    https://livellosegreto.it/@morloi/115657932664318057

    read more
Post suggeriti
  • An idea.

    General Discussion activitypub chess
    2
    1
    0 Votes
    2 Posts
    10 Views
    @jdt one of the first non-social-media uses I've seen for ActivityPub was castling club:https://castling.club/
  • Choice is power

    Fediverso fediday fediverse savesocial
    1
    1
    0 Votes
    1 Posts
    16 Views
    Choice is power.Choice is freedom.And I will never have anyone take that away from me.@_elena kicks off Berlin #FediDay with something like a #Fediverse creed.#SaveSocial
  • 0 Votes
    6 Posts
    26 Views
    @Claire @julian ah! Good spot. I'll try fixing that this evening.I really appreciate the help 😃
  • 0 Votes
    1 Posts
    20 Views
    This tutorial will guide you through building a simple ActivityPub bot using Python. The bot will listen for mentions and, when it receives a message in a specific format, it will schedule and send a reminder back to the user after a specified delay. For example, if a user mentions the bot with a message like "@reminder@your.host.com 10m check the oven", the bot will reply 10 minutes later with a message like "🔔 Reminder for @user: check the oven". Prerequisites To follow this tutorial, you will need Python 3.10+ and the following libraries: apkit[server]: A powerful toolkit for building ActivityPub applications in Python. We use the server extra, which includes FastAPI-based components. uvicorn: An ASGI server to run our FastAPI application. cryptography: Used for generating and managing the cryptographic keys required for ActivityPub. uv: An optional but recommended fast package manager. You can install these dependencies using uv or pip. # Initialize a new project with uv uv init # Install dependencies uv add "apkit[server]" uvicorn cryptography Project Structure The project structure is minimal, consisting of a single Python file for our bot's logic. . ├── main.py └── private_key.pem main.py: Contains all the code for the bot. private_key.pem: The private key for the bot's Actor. This will be generated automatically on the first run. Code Walkthrough Our application logic can be broken down into the following steps: Imports and Configuration: Set up necessary imports and basic configuration variables. Key Generation: Prepare the cryptographic keys needed for signing activities. Actor Definition: Define the bot's identity on the Fediverse. Server Initialization: Set up the apkit ActivityPub server. Data Storage: Implement a simple in-memory store for created activities. Reminder Logic: Code the core logic for parsing reminders and sending notifications. Endpoint Definitions: Create the necessary web endpoints (/actor, /inbox, etc.). Activity Handlers: Process incoming activities from other servers. Application Startup: Run the server. Let's dive into each section of the main.py file. 1. Imports and Configuration First, we import the necessary modules and define the basic configuration for our bot. # main.py import asyncio import logging import re import uuid import os from datetime import timedelta, datetime # Imports from FastAPI, cryptography, and apkit from fastapi import Request, Response from fastapi.responses import JSONResponse from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.primitives import serialization as crypto_serialization from apkit.config import AppConfig from apkit.server import ActivityPubServer from apkit.server.types import Context, ActorKey from apkit.server.responses import ActivityResponse from apkit.models import ( Actor, Application, CryptographicKey, Follow, Create, Note, Mention, Actor as APKitActor, OrderedCollection, ) from apkit.client import WebfingerResource, WebfingerResult, WebfingerLink from apkit.client.asyncio.client import ActivityPubClient # --- Logging Setup --- logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) # --- Basic Configuration --- HOST = "your.host.com" # Replace with your domain USER_ID = "reminder" # The bot's username Make sure to replace your.host.com with the actual domain where your bot will be hosted. These values determine your bot's unique identifier (e.g., @reminder@your.host.com). 2. Key Generation and Persistence ActivityPub uses HTTP Signatures to secure communication between servers. This requires each actor to have a public/private key pair. The following code generates a private key and saves it to a file if one doesn't already exist. # main.py (continued) # --- Key Persistence --- KEY_FILE = "private_key.pem" # Load the private key if it exists, otherwise generate a new one if os.path.exists(KEY_FILE): logger.info(f"Loading existing private key from {KEY_FILE}.") with open(KEY_FILE, "rb") as f: private_key = crypto_serialization.load_pem_private_key(f.read(), password=None) else: logger.info(f"No key file found. Generating new private key and saving to {KEY_FILE}.") private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048) with open(KEY_FILE, "wb") as f: f.write(private_key.private_bytes( encoding=crypto_serialization.Encoding.PEM, format=crypto_serialization.PrivateFormat.PKCS8, encryption_algorithm=crypto_serialization.NoEncryption() )) # Generate the public key from the private key public_key_pem = private_key.public_key().public_bytes( encoding=crypto_serialization.Encoding.PEM, format=crypto_serialization.PublicFormat.SubjectPublicKeyInfo ).decode('utf-8') 3. Actor Definition Next, we define the bot's Actor. The Actor is the bot's identity in the ActivityPub network. We use the Application type, as this entity is automated. # main.py (continued) # --- Actor Definition --- actor = Application( id=f"https://{HOST}/actor", name="Reminder Bot", preferredUsername=USER_ID, summary="A bot that sends you reminders. Mention me like: @reminder 5m Check the oven", inbox=f"https://{HOST}/inbox", # Endpoint for receiving activities outbox=f"https://{HOST}/outbox", # Endpoint for sending activities publicKey=CryptographicKey( id=f"https://{HOST}/actor#main-key", owner=f"https://{HOST}/actor", publicKeyPem=public_key_pem ) ) 4. Server Initialization We initialize the ActivityPubServer from apkit, providing it with a function to retrieve our Actor's keys for signing outgoing activities. # main.py (continued) # --- Key Retrieval Function --- async def get_keys_for_actor(identifier: str) -> list[ActorKey]: """Returns the key for a given Actor ID.""" if identifier == actor.id: return [ActorKey(key_id=actor.publicKey.id, private_key=private_key)] return [] # --- Server Initialization --- app = ActivityPubServer(apkit_config=AppConfig( actor_keys=get_keys_for_actor # Register the key retrieval function )) 5. In-Memory Storage and Cache To serve created activities, we need to store them somewhere. For simplicity, this example uses a basic in-memory dictionary as a store and a cache. In a production application, you would replace this with a persistent database (like SQLite or PostgreSQL) and a proper cache (like Redis). # main.py (continued) # --- In-memory Store and Cache --- ACTIVITY_STORE = {} # A simple dict to store created activities CACHE = {} # A cache for recently accessed activities CACHE_TTL = timedelta(minutes=5) # Cache expiration time (5 minutes) 6. Reminder Parsing and Sending Logic This is the core logic of our bot. The parse_reminder function uses a regular expression to extract the delay and message from a mention, and send_reminder schedules the notification. # main.py (continued) # --- Reminder Parsing Logic --- def parse_reminder(text: str) -> tuple[timedelta | None, str | None, str | None]: """Parses reminder text like '5m do something'.""" # ... (implementation omitted for brevity) # --- Reminder Sending Function --- async def send_reminder(ctx: Context, delay: timedelta, message: str, target_actor: APKitActor, original_note: Note): """Waits for a specified delay and then sends a reminder.""" logger.info(f"Scheduling reminder for {target_actor.id} in {delay}: '{message}'") await asyncio.sleep(delay.total_seconds()) # Asynchronously wait logger.info(f"Sending reminder to {target_actor.id}") # Create the reminder Note reminder_note = Note(...) # Wrap it in a Create activity reminder_create = Create(...) # Store the created activities ACTIVITY_STORE[reminder_note.id] = reminder_note ACTIVITY_STORE[reminder_create.id] = reminder_create # Send the activity to the target actor's inbox keys = await get_keys_for_actor(f"https://{HOST}/actor") await ctx.send(keys, target_actor, reminder_create) logger.info(f"Reminder sent to {target_actor.id}") 7. Endpoint Definitions We define the required ActivityPub endpoints. Since apkit is built on FastAPI, we can use standard FastAPI decorators. The main endpoints are: Webfinger: Allows users on other servers to discover the bot using an address like @user@host. This is a crucial first step for federation. /actor: Serves the bot's Actor object, which contains its profile information and public key. /inbox: The endpoint where the bot receives activities from other servers. apkit handles this route automatically, directing activities to the handlers we'll define in the next step. /outbox: A collection of the activities created by the bot. but this returns placeholder collection. /notes/{note_id} and /creates/{create_id}: Endpoints to serve specific objects created by the bot, allowing other servers to fetch them by their unique ID. Here is the code for defining these endpoints: # main.py (continued) # The inbox endpoint is handled by apkit automatically. app.inbox("/inbox") @app.webfinger() async def webfinger_endpoint(request: Request, acct: WebfingerResource) -> Response: """Handles Webfinger requests to make the bot discoverable.""" if not acct.url: # Handle resource queries like acct:user@host if acct.username == USER_ID and acct.host == HOST: link = WebfingerLink(rel="self", type="application/activity+json", href=actor.id) wf_result = WebfingerResult(subject=acct, links=[link]) return JSONResponse(wf_result.to_json(), media_type="application/jrd+json") else: # Handle resource queries using a URL if acct.url == f"https://{HOST}/actor": link = WebfingerLink(rel="self", type="application/activity+json", href=actor.id) wf_result = WebfingerResult(subject=acct, links=[link]) return JSONResponse(wf_result.to_json(), media_type="application/jrd+json") return JSONResponse({"message": "Not Found"}, status_code=404) @app.get("/actor") async def get_actor_endpoint(): """Serves the bot's Actor object.""" return ActivityResponse(actor) @app.get("/outbox") async def get_outbox_endpoint(): """Serves a collection of the bot's sent activities.""" items = sorted(ACTIVITY_STORE.values(), key=lambda x: x.id, reverse=True) outbox_collection = OrderedCollection( id=actor.outbox, totalItems=len(items), orderedItems=items ) return ActivityResponse(outbox_collection) @app.get("/notes/{note_id}") async def get_note_endpoint(note_id: uuid.UUID): """Serves a specific Note object, with caching.""" note_uri = f"https://{HOST}/notes/{note_id}" # Check cache first if note_uri in CACHE and (datetime.now() - CACHE[note_uri]["timestamp"]) < CACHE_TTL: return ActivityResponse(CACHE[note_uri]["activity"]) # If not in cache, get from store if note_uri in ACTIVITY_STORE: activity = ACTIVITY_STORE[note_uri] # Add to cache before returning CACHE[note_uri] = {"activity": activity, "timestamp": datetime.now()} return ActivityResponse(activity) return Response(status_code=404) # Not Found @app.get("/creates/{create_id}") async def get_create_endpoint(create_id: uuid.UUID): """Serves a specific Create activity, with caching.""" create_uri = f"https://{HOST}/creates/{create_id}" if create_uri in CACHE and (datetime.now() - CACHE[create_uri]["timestamp"]) < CACHE_TTL: return ActivityResponse(CACHE[create_uri]["activity"]) if create_uri in ACTIVITY_STORE: activity = ACTIVITY_STORE[create_uri] CACHE[create_uri] = {"activity": activity, "timestamp": datetime.now()} return ActivityResponse(activity) return Response(status_code=404) 8. Activity Handlers We use the @app.on() decorator to define handlers for specific activity types posted to our inbox. on_follow_activity: Automatically accepts Follow requests. on_create_activity: Parses incoming Create activities (specifically for Note objects) to schedule reminders. # main.py (continued) # Handler for Follow activities @app.on(Follow) async def on_follow_activity(ctx: Context): """Automatically accepts follow requests.""" # ... (implementation omitted for brevity) # Handler for Create activities @app.on(Create) async def on_create_activity(ctx: Context): """Parses mentions to schedule reminders.""" activity = ctx.activity # Ignore if it's not a Note if not (isinstance(activity, Create) and isinstance(activity.object, Note)): return Response(status_code=202) note = activity.object # Check if the bot was mentioned is_mentioned = any( isinstance(tag, Mention) and tag.href == actor.id for tag in (note.tag or []) ) if not is_mentioned: return Response(status_code=202) # ... (Parse reminder text) delay, message, time_str = parse_reminder(command_text) # If parsing is successful, schedule the reminder as a background task if delay and message and sender_actor: asyncio.create_task(send_reminder(ctx, delay, message, sender_actor, note)) reply_content = f"<p>✅ OK! I will remind you in {time_str}.</p>" else: # If parsing fails, send usage instructions reply_content = "<p>🤔 Sorry, I didn\'t understand. Please use the format: `@reminder [time] [message]`.</p><p>Example: `@reminder 10m Check the oven`</p>" # ... (Create and send the reply Note) 9. Running the Application Finally, we run the application using uvicorn. # main.py (continued) if __name__ == "__main__": import uvicorn logger.info("Starting uvicorn server...") uvicorn.run(app, host="0.0.0.0", port=8000) How to Run the Bot Set the HOST and USER_ID variables in main.py to match your environment. Run the server from your terminal: uvicorn main:app --host 0.0.0.0 --port 8000 Your bot will be running at http://0.0.0.0:8000. Now you can mention your bot from anywhere in the Fediverse (e.g., @reminder@your.host.com) to set a reminder. Next Steps This tutorial covers the basics of creating a simple ActivityPub bot. Since it only uses in-memory storage, all reminders will be lost on server restart. Here are some potential improvements: Persistent Storage: Replace the in-memory ACTIVITY_STORE with a database like SQLite or PostgreSQL. Robust Task Queuing: Use a dedicated task queue like Celery with a Redis or RabbitMQ broker to ensure reminders are not lost if the server restarts. Advanced Commands: Add support for more complex commands, such as recurring reminders. We hope this guide serves as a good starting point for building your own ActivityPub applications! https://fedi-libs.github.io/apkit/ https://github.com/fedi-libs/apkit https://github.com/AmaseCocoa/activitypub-reminder-bot