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

People are Starter Packs

Technical Discussion
3 2 18
  • I think that my comment on GitHub that Group Actors acting as Starter Packs really comes from how I approach my social network.

    I rely on my friends and peers to discover new topics and meet new people.

    Friends and peers as Starter Packs work great on a small microblog instance like Mastodon, Misskey, or Sharkey because the federated feed/bubble lists topics written by the amazing people that they are following. This approach is ideal for small instances, but flagship, large and relayed instances make it much harder as the topics are much too diverse. 

    When a small instance I was part of shutdown, I was really at a loss because I relied heavily on that feature without following people myself i.e. I'm lazy. And so to figure out who to follow, I went back to my peers’ and friends’ following Collection. Note to self, I need to remember to continually discover more people from people I follow now that I am on my own single-actor instance.

    This is why I come to the conclusion that people are Starter Packs. Or in other words, Actors are Starter Packs. And more specifically, anyone or thing with a publicly-available following Collection is a Starter Pack. 

    But making a public directory of everyone is not a good idea and discoverability is hard.

    Starter Packs should be (Group) Actors because

    • Consent is built-in with the Follow Accept/Reject.
      • People that want to explicitly allow followers already have this option.
      • Implementations should use Mastodon’s discoverability property to also limit who a Starter Pack may follow.
    • Revoking consent is mostly built-in with either Remove Follow, Block Undo, or Reject Follow.
    • Discoverability is built-in:
      • The following Collection is available.
      • Group Actors can make posts when their following Collection is updated.
      • Amazing UX designers can make the following Collection more usable in their software.
    • Starter Packs that are used in bad faith can be defederated like any other bad faith Actor or instance.
    • The barrier to implementation is much lower than tacking on anything new to the specification.
  • mradcliffe@nokoto.org apart from the technical details, this approach also is extremely easy to explain to laypeople.

    "Follow who this person follows" :heavy_check_mark:

  • Thank you, @julian.

    One thing I am concerned about is how that affects forums and communities.

    But then I think, so what if a NodeBB forum or PieFed community wanted to highlight people because those people represented the values of that community (even if they were not necessarily a part of it)?

    This is something that no forum software that I am aware of does because we always think of forums as only something we join into.

    And it is so amazing to me that the FediVerse and the social web movement could provide a forum or group the opportunity to grow in the opposite direction—sending people outwards for new shared experiences.


Gli ultimi otto messaggi ricevuti dalla Federazione
  • @silverpill
    Yep! Just was on vacation and had a conference to do. It's on my list for Monday to do some followup and updates

    read more

  • @toddsundsted ok, bookmarked. When I'll circle back to ONI themeing I'll send some feedback your way to compare notes. :D

    With ONI I tried to be "fun" and I generate a basic palette from the images actors upload as their Icon and Image properties (ie, profile and header picture in mastodon terms :D) As you can imagine that can lead to very dubious results.

    read more

  • no, i'm still trying to figure that out myself. what i have done is build something that seems capable of handling themes that i find that i like. but even there i guess i have ~2-3 more revisions before it is fully capable.

    i did pick semantic ui as my base framework. it has worked well, but it is abandoned. there is a fork called fomantic ui that is supported, but it is also much larger. other frameworks also seemed too large for my tastes, but i will admit to not doing a ton of research.

    read more

  • @toddsundsted did you use any literature for deciding on the theme basic colors and how to combine them?

    I've been struggling with something very similar a while back and the material design specs seemed too complex for what I wanted, but nothing else popped up on search.

    read more

  • I've streamlined theme development in Ktistec. The theming system uses a hierarchy of CSS custom properties and fallbacks. Theme authors can customize a theme at multiple levels:

    Base Colors Only

    Define only base colors like --text-primary, --bg-primary, --bg-input, --semantic-primary, etc. Derived colors will auto-generate using color-mix formulas. For example:

    :root { --semantic-primary: #ffa500; }

    From this one line, theme-appropriate colors like --bg-accent-code, --anchor-color, etc. auto-generate.

    Base Colors Plus Derived Colors

    Define base colors and derived colors. Derived colors use custom values when defined. Undefined derived colors auto-generate. For example:

    :root { --text-primary: #333; --text-primary-2: #ff0000; /* red for this specific shade */ }

    Given this theme, derived shades like --text-primary-1, --text-primary-3, and --text-primary-4 auto-generate. --text-primary-2 is red.

    The simplest possible interesting theme redefines the primary semantic color. The single line above (in Base Colors Only) would result in the following, with button color, link color, disabled, selected, and hover states all derived automatically:

    screenshot of the setting page with the primary semantic color defined

    These changes will be in the upcoming release. Existing themes will continue to work, as is.

    read more

  • @tofeo take a look at https://gitlab.com/edent/activity-bot/-/blob/main/README_nginx.md?ref_type=heads

    I don't use nginx, but that should help.

    read more

  • @Edent

    I discover your onepage activitypub application at https://shkspr.mobi/blog/2024/02/activitypub-server-in-a-single-file/

    It is really a good start for me

    But how would you adapt it with Ngnix server

    All the logic is based on .htaccess but in Nginx it leads with all 404 pages

    read more

  • @julian 😘

    read more
Post suggeriti
  • 0 Votes
    1 Posts
    17 Views
    🎥📡 Livestream Update!📍 Mainhall: https://c-tube.c-base.org/📍 Rooftop: https://fair.tube/a/digiges_de/video-channelsWe’re streaming live via PeerTube!Tune in and join us — no matter where you are! 🌐🔥#Livestream #PeerTube #Fediday #fediverse
  • 0 Votes
    1 Posts
    16 Views
    Release v2.4.14 of Ktistec is small in terms of features and fixes, but it improves in two areas where I thought Ktistec was weak: light/dark mode support and autosave.I'm not hardcore dark mode, but I do prefer it in some cases. Ktistec selects light or dark mode based on the browser or system setting—there is currently no means to select the mode directly. A nice side effect of light/dark mode support is that custom theming support comes nearly for free.Figure 1: Ktistec editor showing the Dracula themeKtistec was meant for writing. I post my fair share of one-line bits of wisdom, but I started building Ktistec because I wanted a space to write long form, and existing Fediverse platforms were more for social interaction. When writing long form, autosaving is an essential feature. Ktistec will now autosave draft posts and replies. If you navigate away before publishing, you can always find the incomplete draft in the Drafts collection which is accessible from your timeline page.AddedAdd design system page for previewing UI elements.Support custom themes with automatic light/dark mode switching.Add autosave functionality for new posts, draft posts, and replies.ChangedAdd external link indicators.#ktistec #fediverse #activitypub #crystallang
  • 0 Votes
    1 Posts
    19 Views
    The network of Mastodon servers is the Mastoverse.(The Mastoverse is a subset of the Fediverse. The Fediverse is made up of more than just Mastodon servers.)A person on the Mastoverse is a Mastonaut.#Fediverse #Mastodon #Mastonaut #Mastoverse
  • 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