洪 民憙 (Hong Minhee)'s avatar

洪 民憙 (Hong Minhee)

@hongminhee@hollo.social · 954 following · 1270 followers

An intersectionalist, feminist, and socialist guy living in Seoul (UTC+09:00). @tokolovesme's spouse. Who's behind @fedify, @hollo, and @botkit. Write some free software in , , , & . They/them.

서울에 사는 交叉女性主義者이자 社會主義者. 金剛兔(@tokolovesme)의 配偶者. @fedify, @hollo, @botkit 메인테이너. , , , 等으로 自由 소프트웨어 만듦.

()

洪 民憙 (Hong Minhee)'s avatar
洪 民憙 (Hong Minhee)

@hongminhee@hollo.social

Hello, I'm an open source software engineer in my late 30s living in , , and an avid advocate of and the .

I'm the creator of @fedify, an server framework in , @hollo, an ActivityPub-enabled microblogging software for single users, and @botkit, a simple ActivityPub bot framework.

I'm also very interested in East Asian languages (so-called ) and . Feel free to talk to me in , (), or (), or even in Literary Chinese (, )!

洪 民憙 (Hong Minhee)'s avatar
洪 民憙 (Hong Minhee)

@hongminhee@hollo.social · Reply to 洪 民憙 (Hong Minhee)'s post

安寧(안녕)하세요, 저는 서울에 살고 있는 30() 後半(후반) 오픈 소스 소프트웨어 엔지니어이며, 自由(자유)·오픈 소스 소프트웨어와 聯合宇宙(연합우주)(fediverse)의 熱烈(열렬)支持者(지지자)입니다.

저는 TypeScript() ActivityPub 서버 프레임워크인 @fedify 프로젝트와 싱글 유저() ActivityPub 마이크로블로그인 @hollo 프로젝트와 ActivityPub 봇 프레임워크인 @botkit 프로젝트의 製作者(제작자)이기도 합니다.

저는 ()아시아 言語(언어)(이른바 )와 유니코드에도 關心(관심)이 많습니다. 聯合宇宙(연합우주)에서는 國漢文混用體(국한문 혼용체)를 쓰고 있어요! 제게 韓國語(한국어)英語(영어), 日本語(일본어)로 말을 걸어주세요. (아니면, 漢文(한문)으로도!)

洪 民憙 (Hong Minhee)'s avatar
洪 民憙 (Hong Minhee)

@hongminhee@hollo.social · Reply to 洪 民憙 (Hong Minhee)'s post

こんにちは、私はソウルに住んでいる30代後半のオープンソースソフトウェアエンジニアで、自由・オープンソースソフトウェアとフェディバースの熱烈な支持者です。名前は洪 民憙ホン・ミンヒです。

私はTypeScript用のActivityPubサーバーフレームワークである「@fedify」と、ActivityPubをサポートする1人用マイクロブログである 「@hollo」と、ActivityPubのボットを作成する為のシンプルなフレームワークである「@botkit」の作者でもあります。

私は東アジア言語(いわゆるCJK)とUnicodeにも興味が多いです。日本語、英語、韓国語で話しかけてください。(または、漢文でも!)

Luminα's avatar
Luminα

@campanula@qdon.space

내년에 한국에서 마츠다 세이코 라이브하는구나

진짜 일본 아티스트들 내한 공연은 시대정신인건가

AmaseCocoa's avatar
AmaseCocoa

@cocoa@hackers.pub


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:

  1. Imports and Configuration: Set up necessary imports and basic configuration variables.
  2. Key Generation: Prepare the cryptographic keys needed for signing activities.
  3. Actor Definition: Define the bot's identity on the Fediverse.
  4. Server Initialization: Set up the apkit ActivityPub server.
  5. Data Storage: Implement a simple in-memory store for created activities.
  6. Reminder Logic: Code the core logic for parsing reminders and sending notifications.
  7. Endpoint Definitions: Create the necessary web endpoints (/actor, /inbox, etc.).
  8. Activity Handlers: Process incoming activities from other servers.
  9. 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

  1. Set the HOST and USER_ID variables in main.py to match your environment.

  2. Run the server from your terminal:

    uvicorn main:app --host 0.0.0.0 --port 8000
  3. 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

perlmint

@me@social.perlmint.app · Reply to perlmint's post

여행 정산 하는 중에 만든 정산 시트 템플릿 대충 정산할 항목 환율이 여럿인 경우와 건별 결제자, 균등 분할 금액, 개별 금액 처리등을 커버합니다.

https://docs.google.com/spreadsheets/d/1e9Wufcf9y_5MToncbuXgaKAfvdk2K8boKLOI4VbmnDc/edit?usp=drivesdk

이적행위's avatar
이적행위

@yijuckhangwe@qdon.space

마쓰시타 류이치 저자의 『동아시아반일무장전선』
추천

한날's avatar
한날

@hannal@hackers.pub

이미지로만 보던 그 유명한 파도 그림을 보고 왔습니다. 실제로 보니 질감과 세밀함이 정말 감동스럽더라고요. 작품 교체를 하는데, 후지산이나 파도 그림은 전반기에 전시하는 것 같아요. 전반기는 11월 2일까지.

"국립 "청주" 박물관에서 전시합니나. "충주" 아니에요. 이상 충주에 가서 밥 먹고 청주에 가서 관람한 1인이었슴미다.

https://cheongju.museum.go.kr/www/speclExbiView.do?key

파도
ALT text details파도
청주 국립 박물관 청명관 입구
ALT text details청주 국립 박물관 청명관 입구
산책로
ALT text details산책로
산책로
ALT text details산책로
Jaeyeol Lee's avatar
Jaeyeol Lee

@kodingwarrior@hackers.pub

@z9mb1 @catamorphic @msy @joonnot @d01c2 @icecream_mable @minju2996 @2chanhaeng @lionhairdino @akastoot @jihyeok @linea @woaol @theeluwin @rangho @robin @1ho @nebuleto @moreal @harei106 @realfishbread @jcha0713 @hyunjoon @krepe90 @cetaceanightsky @aioo @ink_punctuation @yg1ee

오늘 Hackers' Public 행사 첫번째 모임에 와주셔서 정말 감사합니다! 정말 즐거웠습니다! 잘 들어가세요!



RE: https://hackers.pub/@kodingwarrior/0198e9db-763b-7135-aa89-1808e9e99227

Jaeyeol Lee's avatar
Jaeyeol Lee

@kodingwarrior@hackers.pub

Hackers' Public @ Seoul 1회차 모임 (1차 모집)

서울에서 열리는 Hackers' Pub 오프라인 밋업, "Hackers' Public @ Seoul"이 2025월 9월 14일(일) 처음으로 개최됩니다. 처음 열리는 밋업인 만큼, 참여하는 많은 분들이 재밌게 느낄 수 있는 소재 위주로 연사자 분들을 섭외했습니다.

  • 일시 : 9월 14일 (일) 오후 3시 ~ 오후 6시
  • 장소 : 서울특별시 성동구 상원길 26, 튜링의사과
  • 주제
    • Code As A Canvas : 코드에서 예술작품이 되기까지 (@jakeseo)
    • 폰트는 어떻게 만들어지는가 - NeoDGM 사례로 살펴보는 개발 후일담 (@dalgona)

강연이 끝나고 난 뒤에 자유롭게 네트워킹하는 시간을 가질 예정입니다. 각자 얘기하고 싶은 주제를 들고 오시면 좋습니다.

참여 신청

오프라인 밋업은 여기서 참여신청이 가능합니다. https://event-us.kr/hackerspubseoul/event/110961

  • 모집 기간
    • 1차 모집 : 8월 27일 ~ 9월 1일 (Hackers' Pub에서만 모집)
    • 2차 모집 : 9월 3일 ~ 9월 7일 (Hackers' Pub 외부에서도 공개적으로 모집)

주의사항

  • 본 행사는 Hackers' Pub에서 진행하는 오프라인 행사이며, Hackers' Pub 계정을 가지지 않은 분이 신청하셨을 경우 환불처리될 수 있습니다.
  • Hackers' Pub 외부에서 유입하시는 경우, 각 모집기간이 끝나고 24시간 안에는 Hackers' Pub에 가입이 되어 있으셔야 참여자로 확정됩니다.
洪 民憙 (Hong Minhee)'s avatar
洪 民憙 (Hong Minhee)

@hongminhee@hackers.pub

오늘은 Hackers' Public 첫 번째 모임! 다들 튜링의 사과(@TuringAppleDev)에서 오후 3시에 모여요!

Yichm's avatar
Yichm

@yichm@g0v.social

滅人器

在滅火器指示牌的「火」字,因為頂上的兩點特別暗淡,使得整字形似「人」字。於是,「滅火器」三字成了「滅人器」。
ALT text details在滅火器指示牌的「火」字,因為頂上的兩點特別暗淡,使得整字形似「人」字。於是,「滅火器」三字成了「滅人器」。
在滅火器指示牌的「火」字,因為頂上的兩點特別暗淡,使得整字形似「人」字。於是,「滅火器」三字成了「滅人器」。
ALT text details在滅火器指示牌的「火」字,因為頂上的兩點特別暗淡,使得整字形似「人」字。於是,「滅火器」三字成了「滅人器」。
洪 民憙 (Hong Minhee)'s avatar
洪 民憙 (Hong Minhee)

@hongminhee@hollo.social

By the way, if anyone wants to create an server, there's also @botkit as an option.

Evan Prodromou's avatar
Evan Prodromou

@evan@cosocial.ca · Reply to Evan Prodromou's post

The update was hard; the jsonld.js library was choking on the FEP's context document because Codeberg Pages, where FEP context documents are hosted, is unreliable.

I ended up adding a feature to the `activitystrea.ms` NodeJS library to let you pre-cache a context document, so it didn't have to go fetch it at run time. While I was under the hood, I also fixed a couple of bugs that made it hard to work with extra context documents.

The bot server is now working; I'm going to add more contexts.

Evan Prodromou's avatar
Evan Prodromou

@evan@cosocial.ca

I updated the server software to use FEP-5711, the inverse properties FEP. It is a way to declare two-way relations; if X has an `inbox` property with value Y, then Y has an `inboxOf` property with value X. It helps with navigation when you're using the ActivityPub API, and it also helps with spoofing (since both objects agree on the relationship).

w3id.org/fep/5711

洪 民憙 (Hong Minhee)'s avatar
洪 民憙 (Hong Minhee)

@hongminhee@hackers.pub

완전 기대…!!



RE: https://hackers.pub/@robin/0199434c-edb7-7e5b-8213-8de18d73b8c1

robin's avatar
robin

@robin@hackers.pub

밋업때 뭔가 보여줘야 할 거 같아서 열심히 달리는중

Fedify: ActivityPub server framework's avatar
Fedify: ActivityPub server framework

@fedify@hollo.social

We've just published an experimental pre-release version 1.9.0-pr.431.1597 that adds CommonJS support to all npm packages in the Fedify ecosystem! 🧪

What's new

This experimental build addresses one of the most requested features—better compatibility with CommonJS-based Node.js applications, especially NestJS projects. The pre-release eliminates the need for Node.js's --experimental-require-module flag and resolves dual package hazard issues.

Note: While we now support CommonJS for legacy project compatibility, we still recommend using ESM or migrating to Deno for the best experience with Fedify.

Who should test this?

  • NestJS developers using Fedify who need CommonJS compatibility
  • Legacy CommonJS-based Node.js projects that had trouble integrating Fedify
  • Anyone who previously needed experimental Node.js flags to use Fedify

How to test

Install the experimental pre-release version:

npm install @fedify/fedify@1.9.0-pr.431.1597
# or for specific integrations
npm install @fedify/nestjs@1.9.0-pr.431.1597

You should now be able to use standard require() syntax without any experimental flags.

What we're looking for

  • Does CommonJS import work correctly in your legacy project?
  • Are you able to remove the --experimental-require-module flag?
  • Any issues or regressions compared to the current stable version?

Your feedback on this experimental build is invaluable for ensuring this major compatibility improvement works smoothly before the official 1.9.0 release!

Erin 💽✨'s avatar
Erin 💽✨

@erincandescent@erincandescent.net

Whoa, as of 10 days ago we have functions to go between a Uint8Array and a Base64 Normal/URL Padded/Unpadded string in JavaScript without having to go via a String of “bytes” and manually juggle the padding in every major browser.

What a time to be alive.

이찬행's avatar
이찬행

@2chanhaeng@hackers.pub

페디파이 빡시게 기여해서 2.0 뜨면 블로그 만들어서 블스 떠야지

robin's avatar
robin

@robin@hackers.pub

무슨 연합우주 스캐너 같은 게 있나...?

/api/v1/instance
/.well-known/host-meta
/status.php
/statistics.json
ALT text details/api/v1/instance /.well-known/host-meta /status.php /statistics.json
洪 民憙 (Hong Minhee)'s avatar
洪 民憙 (Hong Minhee)

@hongminhee@hollo.social · Reply to 洪 民憙 (Hong Minhee)'s post

久しぶりにブログに記事を書きました。「オープンソース開発の近況

洪 民憙 (Hong Minhee)'s avatar
洪 民憙 (Hong Minhee)

@hongminhee@hollo.social · Reply to 洪 民憙 (Hong Minhee)'s post

오랜만에 블로그에 글을 썼습니다: 〈오픈 소스 開發(개발) 近況(근황)〉.

洪 民憙 (Hong Minhee)'s avatar
洪 民憙 (Hong Minhee)

@hongminhee@hollo.social

I wrote a post on my blog after a long time: Recent open source development updates.

infinite love ⴳ's avatar
infinite love ⴳ

@trwnh@mastodon.social · Reply to Emelia 👸🏻's post

@thisismissem @mariusor @oranadoz @hongminhee +1, an image and a descriptor are different things and should be treated as different things. content negotiation is not a solution here -- the same information should be returned for the same resource (modulo whichever representation you ask for or receive).

infinite love ⴳ's avatar
infinite love ⴳ

@trwnh@mastodon.social · Reply to marius's post

@mariusor @hongminhee > the same URL can represent both

bad idea. an identifier should unambiguously refer to exactly 1 thing

infinite love ⴳ's avatar
infinite love ⴳ

@trwnh@mastodon.social · Reply to Evan Prodromou's post

@evan @hongminhee more and more i am thinking that Link was a bad idea from a data modeling perspective. "assume bare href instead of bare id" is something that can never make sense. if we really want to maintain validity of Link then it should *always* be embedded as an anonymous object:

icon: {
type: Image
url:
{
type: Link
href: foo
height: 400
width: 400
mediaType: image/png
}
}

here, Image.url means "representation of the Image"

:rss: Zennのトレンド's avatar
:rss: Zennのトレンド

@zenn_dev@rss-mstdn.studiofreesia.com

TypeScriptの型推論でCLIバリデーションをなくせた話
zenn.dev/hongminhee/articles/2

洪 民憙 (Hong Minhee)'s avatar
洪 民憙 (Hong Minhee)

@hongminhee@hackers.pub

일본의 기술 블로깅 플랫폼인 Zenn 첫 페이지에 내가 Optique에 관해 썼던 글인 「TypeScriptの型推論でCLIバリデーションをなくせた話」(TypeScript의 타입 추론으로 CLI 유효성 검사를 필요 없게 만든 이야기)가 떴다!

일본 기술 블로깅 플랫폼 Zenn의 Tech 섹션 스크린샷. 페이지에는 여러 기술 관련 글들이 나열되어 있으며, 그 중 빨간 원으로 강조된 「TypeScriptの型推論でCLIバリデーションをなくせた話」(TypeScript의 타입 추론으로 CLI 유효성 검사를 필요 없게 만든 이야기) 제목의 글이 20개의 좋아요와 12개의 댓글을 받으며 주목받고 있다. 해당 글은 TypeScript와 CLI 파서 라이브러리인 Optique에 관한 내용을 다루고 있다.
ALT text details일본 기술 블로깅 플랫폼 Zenn의 Tech 섹션 스크린샷. 페이지에는 여러 기술 관련 글들이 나열되어 있으며, 그 중 빨간 원으로 강조된 「TypeScriptの型推論でCLIバリデーションをなくせた話」(TypeScript의 타입 추론으로 CLI 유효성 검사를 필요 없게 만든 이야기) 제목의 글이 20개의 좋아요와 12개의 댓글을 받으며 주목받고 있다. 해당 글은 TypeScript와 CLI 파서 라이브러리인 Optique에 관한 내용을 다루고 있다.
Evan Prodromou's avatar
Evan Prodromou

@evan@cosocial.ca · Reply to 洪 民憙 (Hong Minhee)'s post

@hongminhee It's a place where our loosey goosey style goes into nondeterminism. We should tighten it up in the next version. My main answer would be: publishers, don't do that.

洪 民憙 (Hong Minhee)'s avatar
洪 民憙 (Hong Minhee)

@hongminhee@hollo.social

Today I discovered an interesting inconsistency in Activity Streams specs while investigating a Fedify issue.

The question: How should we interpret URLs like "icon": "https://example.com/avatar.png"?

JSON-LD context (https://www.w3.org/ns/activitystreams): @type: "@id" → “This is an IRI reference, dereference it to fetch an ActivityStreams object.”

Activity Streams Primer: “assume that a bare string is the href of a Link object, not an id” (no dereferencing)

Result: JSON-LD processor-based implementations try to parse PNG files as JSON and fail.

Turns out w3c/activitystreams#595 already discusses the same issue for href properties. I added a note that icon, image, etc. have the same problem.

Once again reminded of how tricky spec work can be…

Anuj Ahooja's avatar
Anuj Ahooja

@quillmatiq@mastodon.social · Reply to Anuj Ahooja's post

Direct link to the video: spectra.video/w/2mSF49ZFZPYJ9d

Anuj Ahooja's avatar
Anuj Ahooja

@quillmatiq@mastodon.social · Reply to Emelia 👸🏻's post

@thisismissem I mention the work you and @hongminhee are doing with @hollo in there. Really excited about it!

Anuj Ahooja's avatar
Anuj Ahooja

@quillmatiq@mastodon.social

@snarfed.org and I sat down with @deadsuperhero on Decentered!

First half is us talking about @anewsocial, and the second half is Sean letting me rave about all the exciting work being done on the open social web by @hongminhee, @cheeaun, @gabboman, @thisismissem, and so many others.

wedistribute.org/podcast/s2e5-

Older →