خیلی از شبکه های اجتماعی فدرال و اوپن سورس مثل Plume, Peertube, Mastodon PixelFed, Lemmy, Funkwhale و... دارن از پروتکل ActivityPub استفاده میکنن و برای همین این پروتکل برای من جذاب بود و یه مدت سرچ کردم راجبش و... در نهایت خواستم چیز هایی که یاد گرفتم و تجربه کردم رو اینجا به اشتراک بزارم تا برای افرادی که میخوان آشنا بشن باهش منبع خوبی باشه، البته پروتکل های دیگه ای هم غیر از AP هستن مثل: Farcaster, Nostr و... در کل همیشه یه سری مشکلات و چالش ها برای این پروتکل ها هستش که با حمایت جامعه در طی زمان بهتر و بهتر میشن(اینکه کدوم پروتکل، پلتفرم و سرور رو برای استفاده انتخاب کنیم بستگی داره به اینکه چه چیزی برامون اهمیت بیشتری داره).
در ادامه یه نگاهی به پروتکل های خوبه گذشته میندازیم و بعد میریم سراغ جزئیات پروتکل اکتیویتی پاب.
اصطلاح فدریشن(Federation) : در واقع نمایانگر یه شبکه هستش که داخلش سرور ها به توافق رسیدن که از یه پروتکل استاندارد(مثل ActivityPub) برای تعامل با همدیگه بدونه نیاز به سرور مرکزی استفاده کنن.
به پلتفرم های مختلفی که از این پروتکل استاندارد و مشترک استفاده میکنن میگن federated (فدرال) مثل Mastodon ،Pleroma، Plume و...
اصطلاح فدیور(Fediverse) : این از ترکیب federated و universe بوجود اومده، در واقع اشاره میکنه به گروهی از این پلتفرم های فدرال که میتونن با هم دیگه تعامل داشته باشن. مثلا ماستودون میتونه روی n تا سرور ران باشه و این سرور ها به عنوان instance در شبکه فعالیت بکنن، حالا ممکنه plume هم n تا سرور داشته باشه و هر دو چون فدرال هستن میتونن با همدیگه تعامل داشته باشن، به این شبکه بزرگ که از ترکیب این پلتفرم های فدرال شکل گرفته میگن فدیور.
نکته : ممکنه بعضی جاها به جای واژه سرور از pod، node، relay و یا instance هم استفاده بشه که همشون مفهمون همون سرور رو دارن ولی داخل پلتفرم و پروتکل های مختلف ممکنه اسم های متفاوتی داشته باشه.
شبکه های اجتماعی متمرکز(Centralize):
در این نوع از شبکه های اجتماعی همه کاربرا وصل میشن به یک یا چند سرور و دیتای خصوصی شون داخل سرور های اون شرکت ذخیره میشه: مثل توتیتر، یوتیوب، فیسبوک و... این دیتای خصوصی میتونه به شرکت های دیگه فروخته بشه و یا برای هدف های دیگه استفاده بشه و کاربر کنترلی روی جریان نداره، همه کاربر ها باید از قوانینی که اون شبکه اجتماعی تعیین کرده پیروی بکنن و باز هم کاری از دسته کاربر نمیاد(مثل محدودیت های توییتر که زیاد تر شد و..)، دولت میتونه به شکل ساده تری سانسور بکنه، اکانت کاربر رو ببنده، روی دیتا و فعالیت های کاربر نظارت داشته باشه و... همچنین کاربر ها برای اینکه بتونن با بقیه تعامل داشته باشن، پست هاشون رو بنویسن و یا از پست های جدید بقیه باخبر بشن و... باید از برنامه یا وب سایتی که برای اون شبکه اجتماعی هستش استفاده بکنن و نمیتونن مثلا داخل توییتر نوتیفای بگیر که فلان کاربر تو یوتیوب فیلم جدید اپلود کرد یا لایو داره یا برن کامنت بزارن براش و از این موارد، شرکت هایی اصلی میتونن هر تبلیغاتی رو که میخوان به کاربر ها نشون بدن و این هم آزار دهنده هستش.
در کل سانسور، نقض حریم خصوصی، محدودیت ها، تبلیغات و... میتونه هر لحظه ای اتفاق بیفته تو این شبکه ها و کاربر رو چیزی کنترل نداره و همچنین اگر سرور های اصلی مشکلی براشون پیدا بشه کل شبکه down میشه و یا اگر سروری هک بشه اطلاعت همه کاربر ها در دسترس قرار میگیره .
شبکه های اجتماعی فدرال(Decentralized) :
تو این نوع از شبکههای اجتماعی کاربر ها میتونن به سرور های متعددی وصل بشن و محدود به سرور های یه شرکت/شخص خاص نمیشن و میتونن تصمیم بگیرن که دیتاشون روی کدوم سرور ذخیره بشه و هروقت خواستن هم میتونن اطلاعات شون رو از یک سرور به سرور دیگه انتقال بدن و یا حتی سرور خودشون رو بیارن بالا و به عنوان یه instance به شبکه اضافه بشن، هر سرور هم قوانین و موضوع خودش رو میتونه داشته باشه و افرادی که با این موافقن داخلش رجستر میکنن. برای مثال تعداد زیادی instance در mastodon وجود داره و کاربر میتونه داخل هرکدوم از این instance ها که دوست داره عضو بشه و با کاربر های اون instance تعامل داشته باشه و یا اگر خواست میتونه از قسمت تایم لاین با یوزر سرور های دیگه هم در تعامل باشه... و حتی کاربر میتونه با پلتفرم های دیگه هم در تعامل باشه(به شرطی که داخل فدیور باشه (بالاتر گفتیم)) .
البته این شبکه های اجتماعی که تو مثال گفتیم هنوز میتونن به شکل کاملی غیر متمرکز نباشن، چون هر سرور یه صاحب داره و اون هستش که اطلاعات ما رو ذخیره میکنه(اگه سرور خودمون نباشه) و زمانی که بخوایم از یه سرور اطلاعات مون رو انتقال بدیم به سرور دیگه ادمین سرور فعلی میتونه این کار رو انجام نده، چون کاربر هاش دارن میرن!! و حتی ممکنه زمان انتقال، دیتا کامل انتقال داده نشه و... که این موضوع داخل پروتکل هایی مثل Nostr بهتر هستش چون کاربر به یه سری relay وصل میشه و بعد یه پست میزاره و این برای همه relay ها ارسال میشه(تو همشون ذخیره میشه) و کل یوزر هایی که وصلن به اون relay ها میتونن پیام رو ببین و...(کاربر ها با پرایویت کی خودشون و الگوریتم Schnorr امضا(sign) میکنن پیام رو و بعد به relay ارسال میشه، اینجوری کسی نمیتونه محتوا رو تغیر بده چون امضا با public key ما چک میشه و اگر تغیر کرده باشه مشخص میشه).
قبلا برای ایجاد یه شبکه اجتماعی غیر متمرکز از پروتکل های دیگه ای مثل XMPP یا OStatus یا Diasora و... استفاده میشد ولی خب هرکدوم یک سری مزایا و معایبی داشتن که میخوایم یه نگاهی بهشون بندازیم.
اگر علاقه ای به این قسمت ندارید میتونید برید قسمت بعدی.
این پروتکل برای ارتباط ریل تایم مثل چت های انلاین به شکل client-to-server ایجاد شد و دیتا در قالب XML ارسال و دریافت میشه. همچنین به کاربر اجازه میده وضعیت خودش رو در شبکه اعالم بکنه(Presence)، مثلا اعلام بکنه انلاین یا افلاین هستش و یا اطلاعات دیگه ای رو ارسال بکنه. این پروتکل برای شبکه های اجتماعی کوچیک و ساده و یا برای ارتباط بین دستگاه های IoT و ارسال پیام فوری خوب بود، مثلا تو IoT میشه برای اعلام وضعیت حسگر های یه دستگاه یا آلارم ها ازش استفاده کرد(Presence). بعضی جا ها هنوز استفاده میشه ولی برای یه شبکه اجتماعی که واقعا کار بکنه یه سری مشکلات داشت: برای ریل تایم بودنش میتونست از websocket استفاده بکنه به جای سوکت tcp، قابلیت های یه شبکه اجتماعی مثل فالو کردن، تایم لاین، ادیت کردن پروفایل، داشتن سطح های مختلف پرایوسی و... رو نداشت، نمیتونست بفهمه که کدوم کاربر برای کدوم سرور هستش و چطوری باید پیداش بکنه و...
البته همه این مشکلات رو میشه با نوشتن Extension های مختلف حل کرد(XMPP پروتکل XEP رو برای نوشتن اکستنشن هاش ارئه که با فرمت استانداردش باعث اینجاد همکاری بین پیاده سازی های مختلف و کلاینت های مختلف میشه) ولی خب در مقایسه با پروتکل های جدید تر که به صورت پیشفرض خودشون موراد مورد نیاز یه شبکه اجتماعی رو هندل کردن کم کم این پروتکل کم رنگ تر شد و... همچنین تو سطح بالا تر منیج کردن چندین میلیون یوزر با این پروتکل سخت بود.
این پروتکل بوجود اومد تا شبکه های اجتماعی غیر متمرکز ایجاد بشن و بتونن با هم دیگه در قالب استاندارد تری تعامل و همکاری داشته باشن( برای ایجاد شبکه هایی مثل توییتر و..). از این پروتکل در پلتفرم هایی مثل Mastodon, GNU Social, Pleroma استفاده میشد که البته الان از بین این موارد فقط در GNU Social استفاده میشه، این پروتکل خودش از چندین پروتکل دیگه مثل : ActivityStream 1.0, Salmon, Atom, WebSub, Webfinger, Portable Contacts استفاده میکنه تا بتونه سازگار تر باشه با سایر سرویس های وب و بتونه یه حالت استاندارد تری رو داشته باشه.
این پروتکل به ما اجازه میده که بتونیم یک سری تغیرات رو ایجاد بکنیم و مورادی که نیاز داریم رو اضافه بکنیم تا در کل دستمون باز تر باشه، بعضی جاها نیاز بود که اون instance یه سری ویژگی ها رو خودش اضافه بکنه مثلا:
این پروتکل و وب اپلیکیشنش که همین اسم رو داره با این هدف ایجاد شد که بتونه پراویسی و مدیریت دیتای خوبی رو به کاربر هاش بده، کاربر ها میتونن پروایسی رو تا سطح های خیلی خوبی تعیین بکنن، مثلا چه افراد یا گروه هایی بتونن یه پست رو بیینن و یا چه قسمت هایی از پروفایل رو بتونن همه ببینن و... همچنین به کاربر اجازه میده که کل دیتاش که داخل pod(سرور) هستش رو export بکنه تا خیلی راحت بتونه بره داخل یه pod دیگه عضو بشه و کوچ بکنه، همچنین کاربر ها میتونن شخص دیگه ای رو بلاک بکنن یا پست های یه کاربر دیگه رو ignore بکنن تا براشون نمایش داده نشه. برخلاف OStatus که از پروتکل های مختلف استفاده میکرد اینجا Diaspora(دیاسپورا) پروتکل خودش رو داره و خیلی از چیز ها داخل core خودش هندل شده.
اکتیویتی پاب یه پروتکل هستش که یه سری قوانین و قرارداد ها رو تعیین میکنه تا همه بتونن به شکل استانداردی با همدیگه صحبت بکنن، AP بعداً توسط w3c تایید شد و به یه پروتکل استاندارد و قوی برای شبکه های اجتماعی فدرال تبدیل شد. داخل AP دیپندنسی ها نسب به OStatus و... خیلی کمتر شد و دیگه نیازی به پروتکل هایی مثل Salmon, WebSub, Atom و... نیست چون اکتیویتی پاب به شکل built-in کاری که این پروتکل ها انجام میدادن رو انجام میده و با داکیومنت خوبی هم که ارئه میده کار رو برای دولوپر ها خیلی راحت تر کرد(نسبت به قبل).
اکتیویتی پاب رو میشه به دو شکل Client To Server و Server To Server استفاده کرد که بیشتر همون حالت سرور به سرور هستش و یوزر ها به api اون وب اپلیکیشن وصل میشن و تعامل دارن.
نکته: ممکنه وقتی راجب این پروتکل سرچ میکنیم به inbox و outbox برخورد کرده باشیم، inbox میشه endpoint که ما داخل backend با همین اسم مینویسم و از این به بعد بقیه سرور ها درخواست های POST شون رو به اینجا میفرستن مثلا : ایجاد یک پیام - فالو کردن - لایک و... outbox هم مثل قبلی هستش ولی برای درخواست های GET یعنی سرور های دیگه درخواست میدن مثلا برای دریافت پست های فلان یوزر یا تعداد لایک های فلان یوزر و...
برای کار کردن با پروتکل AP و ساختن وب اپلیکیشن خودمون باید این 4 تا پروتکل پایین رو درک کنیم:
اکتیویتی استریم از Activity Vocabulary و JSON-LD تشکیل شده و هدف اصلیش سریالایز کردن دیتا هستش(در ادامه وقتی این دو مورد رو توضیح دادیم این هم قابل لمس تر میشه). اکتیویتی استریم تو ورژن 1 از فرمت xml برای سریالایز کردن دیتا استفاده میکرد و همچنین تایپ و پراپرتی های کمتری رو هم پشتیبانی میکرد. تو ورژن 2 توسط w3c تأیید و به یه پروتکل استاندارد تبدیل شد، تو این ورژن تغیر های زیادی بوجود اومد، مثلا استفاده از Json-LD برای سریالایز کردن دیتا و یا اضافه شدن تایپ و پراپرتی های بیشتر برای پوشش دادن متا دیتا های مربوط به یه اکشن(همین Vocabulary).
این پروتکل یه سری Type و Property رو برای Activity Stream تعریف میکنه، در حالت واقعی ما میایم با استفاده از این تایپ و پراپرتی ها متا دیتای مربوط به یه activity رو تعریف میکنیم و بعد در قالب Json-LD برای یه instance دیگه ارسالش میکنیم، فعلا باید یه درک اولیه از تایپ ها داشته باشیم و بدونیم به چه شکل هستن در ادامه قابل لمس تر میشه قضیه... :(
خود Type ها به دو دسته بندی کلی تقسیم میشن :
مورد اول خودش شامل یه سری تایپ بیسیک میشه مثل:
Object, Link, Activity، IntransitiveActivity, Collection, CollectionPage, OrderCollection, OrderCollectionPage
در کل 8 تایپ بیسیک داریم و همه این ها به عنوان تایپ های بیس برای Extended ها عمل میکنن، هرکدوم از این تایپ ها یه سری پراپرتی دارن، مثلا تو extended type ها Follow رو داریم که خود این از Activity ارث بری میکنه و خود Activity از Object، یعنی میتونیم از پراپرتی های اکتیوتی و ابجکت استفاده کنیم موقع استفاده از Follow(در حالت واقعی وقتی کسی رو فالو میکنیم میتونید این پراپرتی ها رو ست بکنیم داخل درخواست) .
این دسته شامل 3 نوع تایپ مختلف میشه :
علاوه بر تایپ ها یه سری property خیلی خوب داریم که تعدادشون هم زیاده، زمانی که ما یه Activity رو ایجاد میکنیم میتونیم از این پراپرتی ها داخلش استفاده بکنیم تا متا دیتای اون activity مون کامل بشه، اینجا 3 تا پراپرتی جالب رو مثال میزنیم:
پراپرتی to, cc, bcc, bto : این پراپرتی ها برای هدف گذاری کاربر ها استفاده میشن، هر اکتیویتی(فالو، منشن، لایک و..)میتونه هم یوزر درجه اول داشته باشه هم درجه دوم، مثلا کاربر a یه پیام میفرسته به b، اینجا این دو نفر مستقیم داخل اون activity شرکت کردن و برای همین میشن Primary audience و یوزر x که b رو دنبال میکنه میشه Secondary audience که حالا زمان ارسال این اکتیوتی میتونیم یوزر b رو داخل این فیلد cc مشخص کنیم). جزئیات بیشتر...
پراپرتی icon: میتونیم از این پراپرتی برای تعریف کردن یه عکس کوچیک استفاده کنیم مثل عکس پروفایل.
پراپرتی next, prev: برای pagination(صفحه بندی) استفاده میشه مثلا از تایپ Collection استفاده کردیم و یه لیست از پست ها رو نشون دادیم(لیست میشه همون پراپرتی items ) و حالا میتونیم از این prev, next برای دیدن پست های قبلی و بعدی استفاده کنیم .
جیسان لینک دیتا مثل همون JSON هستش با این تفاوت که داخلش میتونیم دیتا ها رو لینک بکنیم مثلا داخل یه صفحه html ما میتونیم یه لینک بسازیم و وقتی کاربری روی اون کلیک کرد میره به صفحه مربوطه، داخل json-LD هم ما میتونیم برای کلید هامون یه ولیو تعیین بکنیم و این ولیو میتونه لینک باشه همچنین به ما قابلیت های دیگه ای رو هم میده.
خود Json-LD یه سری keywords داره که برای تعریف بعضی قسمت ها ازش استفاده میشه مثلا برای تعیین کردن type@ یا id@ یا context@ و...(همشون با @ شروع میشن)، بعضی از این keywords ها داخل پراپرتی های Activity Vocabulary وجود دارن و از اون ها استفاده میکنیم(بعضی از کی ها هم وجود ندارن چون ربطی به هدف شبکه اجتماعی ندرن) این مورادی که وجود دارن همه map شدن یعنی نیازی به اون @ اولشون نیست.
ما از این پراپرتی ها خیلی جاها استفاده میکنیم مثلا برای هر ابجکت تایپی که داخل Vocabulary داشتیم یه context هم باید ست بکنیم زمان سریالایز کردن.
این context، تایپ و پراپرتی هایی که در ادامه مینویسم رو مشخص میکنه یعنی اگر سرور یا فرد دیگه ای بیاد این json ما رو ببینه از روی این context میتونه بفهمه که این keywords هامون چه معنی میدن و value شون قرار چه چیزی باشه، درواقع schema دیتای ما رو مشخص میکنه مثلا:
{ "@context": "https://www.w3.org/ns/activitystreams", "type": "Like", "id": "http://example.com/activity/1", "actor": "http://example.com/users/harold_finch", "object": "http://example.com/notes/1", "published": "2023-08-10T17:30:06Z" }
اینجا ما یه activity با تایپ Like تعریف کردیم که مشخص میکنه یوزر harold اومده پست 1 رو لایک کرده(اینجا پست و یوزر داخل یه سرور هستن) .
چند تا نکته وجود داره، اولین قسمت برای این activity اومدیم تعیین کردیم که contextش چیه و رفرنس دادیم به داکیومنتAcitvity Stream تا کاربر یا سرور مقصد متوجه بشه.
مورد بعدی اومدیم id رو ست کردیم، این هم یه keyword(پراپرتی) دیگه هستش که داخل json-ld تعریف شده و میاد شناسه یکتا برای این activity رو مشخص میکنه، اینجا مقدارش میشه url خود instance + یه عدد رندم که داخل دیتابیس ذخیره میشه و بعد ارسال(تایپ این فیلد میتونید URN یا URL باشه).
این پروتکل جالبی هستش که قبل تر هم استفاده میشد، هدف این پروتکل این هستش که بتونیم باهاش اطلاعات یه URI رو بدست بیاریم که URI میتونه URL باشه یا URN که در اکثر حالت ها ما با URL سرو کار داریم.
داخل شبکه های مثل Mastodon, GnuSocial, Diaspora و.. از این پروتکل استفاده میشه تا اطلاعات یوزر رو به دست بیاریم، داخل این شبکه های اجتماعی چون غیرمتمرکز هستن ممکنه مثلا داخل سرور x یه یوزنیم وجود داشته باشه و داخل سرور y هم همین یوزرنیم وجود داشته باشه، درواقعی چون سرور ها جدا هستن ممکنه این موارد تکراری پیش بیاد برای همین میان از domain مربوط به اون instance استفاده میکنن داخل username کاربر یعنی یوزنیم کاربر میشه:
@username@domain
بیاید یه مثلا عملی تر بزنیم که بهتر درک بشه، مثلا داخل ماستودون شما در سرور mastodon.social اکانت ساختید و الان میخواید یوزر harold_finch که داخل سرور pinkorange.red هست رو فالو بکنید یا پست هاش رو لایک بکنید یا منشن کنید و... اینجا قبل از اینکه درخواست فالو فرستاده بشه میاد یه درخواست میده به endpoint مربوط به webfinger تا اطلاعات یوزر harold_finch رو به دست بیار از اون سرور دوم.
همونطور که گفتیم یوزنیم کاربر از دامین و یوزنیم ایجاد شده و همه وب اپلیکیشن ها در سمت بک اند یه endpoint ایجاد میکنن برای webfinger :
https://domain/.well-known/webfinger
و اینجا وقتی یه instance نیاز داره اطلاعات یه یوزر رو بگیره و یوز داخل سرور خودش نیست میاد این endpoint درخواست میده:
https://pinkorange.red/.well-known/webfinger?resource=acct:harold_finch@pinkorange.red
و داخل خروجی این هم یه سری دیتا برگشت داده میشه که اینجا یه نمونه هستش و داخل سیستم خودتون هم میتونید همین درخواست رو بزنید و ببینید.
نکته : instance ها همیشه دیتا رو cache میکن و مثلا اگر اطلاعات یوزر داخل دیتابیس مربوط به کشش وجود داشته باشه میاد از همون استفاده میکنه...
تا الان یه سری اصطلاح یاد گرفتیم و راجب اینکه شبکه های متمرکز و غیر متمرکز چی هستن و چه تفاوت هایی به هم دارن تا حدودی صحبت کردیم و بعد رفتیم سراغ پروتکل های محبوبی که قبلا برای شبکه های اجتماعی استفاده میشد و بعد رفتیم سراغ ActivityPub و یه دید کلی راجبشون پیدا کردیم، پروتکل های جالب دیگه ای هم هستن که بعد از AP بوجود اومدن و دارن رشد میکنن و همیشه یه سری مشکلات رو حل میکنن و یه سری مشکلات جدید رو بوجود میارن و یه سری از ادم ها هم دنبالشون میکنن و میره جلو... ولی فعلا AP محبوب تر بوده و داخل پلتفرم های مختلفی مورد استفاده قرار گرفته و جامعه بیشتری دارن ازش استفاده میکنن(ن ب شکل مستقیم) و فعلا اوکیه..
الان انتظار میره که یه درک کلی نسبت به موارد گفته شده داشته باشیم و کانسپت کلی ماجرا تو ذهن مون شکل گرفته باشه، بعد از این میشه رفت سراغ داکیومنت AP و مطالعش کرد تا مورادی که اینجا گفته نشده رو خوند، مثل جزئیات نحوه ارتباط سرور ها با هم، sharedInbox، Security، احراز هویت و... تا بعدش بتونیم وبسایت خودمون رو بیاریم بالا و یا تو پروژه های اوپن سورس مشارکت کنیم و یا حتی اصلا برای کنجنکاوی یه سری موراد رو تست بکنیم و...
داخل این مقاله جا نمیشد که مثال های واقعی بزنیم و داخل کد مرحله به مرحله بریم جلوو به instance های دیگه درخواست بدیم و تعامل داشته باشیم و... برای همین شاید داخل یه مقاله دیگه با پایتون و فریم ورک های سمت بک اندش یه چیزایی نوشتیم...! (البته یه سری لینک مفید و خوب اخر صفحه گذاشتم که این موراد رو پوشش میده)
میتونید از لینک زیر هم استفاده کنید، یه حساب کاربری ماستودون برای 1 روز بهتون میده و درواقع یه instance ماستودون هستش مثل بقیه فقط یه قابلیت های بیشتری داره مثلا وقتی کسی رو فالو میکنید میتونید درخواست هایی که از سمت این سرور ارسال میشه و ریسپانس ها رو ببینید یا خودتون درخواست بزنید به یه url خاص و ریسپانسی که یه سرور دیگه بهتون میده رو ببینید و... در کل کار کردن باهش خوبه و درک خیلی بهتری پیدا میکنید از پروتکل Activitypub و...
https://activitypub.academy/auth/sign_up
مرسی که تا اینجا خوندید و همراهی کردید، امید وارم که مفید بوده باشه و چیز جدید بهتون اضافه شده باشه و... اگر داخل پست مشکلی وجود داشت یا چیزی به اشتباه گفت شده بود و یا می تونست بهتر باشه خوشحال میشم کامنت بزارید، چاکس ❤️
Sebastian Jambor: Part-1 - Part-2 - Part-3 new..
Tiny-Subversions: https://tinysubversions.com/notes/reading-activitypub/
Pleroma: https://blog.soykaf.com/post/pleroma-encyclical-activity-pub/
Dennis Schubert | Diaspora: Part-1 - Part-2
Libre Lounge(podcast): Part-1 - Part-2 - Part-3
Christine Lemmer: https://gitlab.com/spritely/ocappub/blob/master/README.org
https://flak.tedunangst.com/post/what-happens-when-you-honk
https://flak.tedunangst.com/post/the-activity-person-examined
https://flak.tedunangst.com/post/activity-notes
https://flak.tedunangst.com/post/AP-networking
https://www.hughrundle.net/how-to-implement-remote-following-for-your-activitypub-project/