Compare commits

...

3 Enmetoj

Author SHA1 Message Date
Jaidyn Ann 71fa674ce3 Use LitePub for user-class in example server
Now that LitePub has its own classes (rather than
modifying those of Activity-Vocabulary), this is
necessary.
2024-10-21 04:21:10 -05:00
Jaidyn Ann 2182eb4ebe Remove use of :PROTOCOL & :ADDRESS in *CONFIG*
Now, the protocol is inferred through the :HOST,
and :ADDRESS is, well, :HOST.
2024-10-21 04:19:45 -05:00
Jaidyn Ann dbb23ad8b8 Replace custom encoding of users with object-FETCH
That is, activity-servist doesn’t worry about
/users/ paths anymore; it just assumes every path
is an object-request. It relies on the FETCH
callback to keep it in line.
2024-10-21 04:17:29 -05:00
2 changed files with 41 additions and 89 deletions

View File

@ -16,7 +16,8 @@
(:export :start-server :seed) (:export :start-server :seed)
;; Note that we use a nickname for the ActivityStreams vocab! ;; Note that we use a nickname for the ActivityStreams vocab!
;; I recommend doing likewise; save yourself some typing! ;; I recommend doing likewise; save yourself some typing!
(:local-nicknames (:as :activity-servist/vocab/activity))) (:local-nicknames (:ass :activity-servist/vocab/activity)
(:lp :activity-servist/vocab/litepub)))
(in-package #:activitypub-example) (in-package #:activitypub-example)
@ -26,11 +27,12 @@
(defvar *store* (make-hash-table :test #'equal) (defvar *store* (make-hash-table :test #'equal)
"Our “object-store” — stores all ActivityPub objects, mapped by their IRI @ID.") "Our “object-store” — stores all ActivityPub objects, mapped by their IRI @ID.")
(defvar *config* '(:address "localhost" :protocol "http" :port 8080 :fetch fetch)) (defvar *config*
'(:host "http://localhost:8080" :address "127.0.0.1" :port 8080 :fetch fetch))
(defvar *user-id-format* "~A://~A/users/~A" (defvar *user-id-format* "~A/users/~A"
"The format we use for users @IDs/URIs. "The format we use for users @IDs/URIs.
The first parameter is the protocol, the second the host, and the third the username. The first parameter is the protocol+host, and the second is the username.
For example: https://localhost:8080/users/lena.") For example: https://localhost:8080/users/lena.")
@ -91,15 +93,16 @@ That is, an “acct:username@host.tld” URI."
(destructuring-bind (user host) (destructuring-bind (user host)
(str:split "@" sans-preceding-@) (str:split "@" sans-preceding-@)
(format nil *user-id-format* (format nil *user-id-format*
(host-scheme host) host user)))) (host-w-scheme host) user))))
(defun host-scheme (hostname) (defun host-w-scheme (hostname)
"Helper-function for ACCT-URI->ID. Returns the expected protocol of a hostname. "Helper-function for ACCT-URI->ID. From a hostname, returns scheme://hostname.
If its our configured :ADDRESS (in *CONFIG*), then return *CONFIG*s :PROTOCOL. If it matches our configured :HOST (in *CONFIG*), simply returns :HOSTs value.
Otherwise, assume https." Otherwise, assume https."
(if (equal (getf *config* :address) hostname) (let ((our-host (getf *config* :host)))
(getf *config* :protocol) (if (equal (quri:uri-host (quri:uri our-host)) hostname)
"https")) our-host
(format nil "https://~A" hostname))))
@ -113,7 +116,7 @@ Otherwise, assume “https”."
;;; Users ;;; Users
;;; ———————————————————————————————————————— ;;; ————————————————————————————————————————
(defclass user (as:person) (defclass user (ass:person lp:object)
((inbox ((inbox
:accessor user-inbox :accessor user-inbox
:initform nil :initform nil
@ -136,15 +139,13 @@ Otherwise, assume “https”."
The ID and ENDPOINTS are derived using the parameter USERNAME and the global *USER-ID-FORMAT*." The ID and ENDPOINTS are derived using the parameter USERNAME and the global *USER-ID-FORMAT*."
(let ((obj (make-instance 'user)) (let ((obj (make-instance 'user))
(uri (format nil *user-id-format* (uri (format nil *user-id-format*
(getf *config* :protocol) (getf *config* :host) username)))
(getf *config* :address)
username)))
(flet ((sub-uri (path) (flet ((sub-uri (path)
(format nil "~A/~A" uri path))) (format nil "~A/~A" uri path)))
(setf (as:name obj) username) (setf (ass:name obj) username)
(setf (json-ld:@id obj) uri) (setf (json-ld:@id obj) uri)
(setf (as:inbox obj) (sub-uri "inbox")) (setf (ass:inbox obj) (sub-uri "inbox"))
(setf (as:outbox obj) (sub-uri "outbox")) (setf (ass:outbox obj) (sub-uri "outbox"))
(setf (as:following obj) (sub-uri "following")) (setf (ass:following obj) (sub-uri "following"))
(setf (as:followers obj) (sub-uri "followers"))) (setf (ass:followers obj) (sub-uri "followers")))
obj)) obj))

View File

@ -29,12 +29,12 @@
;;; Globals ;;; Globals
;;; ———————————————————————————————————————— ;;; ————————————————————————————————————————
(defvar *config* '(:address "localhost" :port 8080 :protocol "https") (defvar *config* '(:host "http://localhost:8080" :address "127.0.0.1" :port 8080)
"Configuration for the server, a property-list. "Configuration for the server, a property-list.
There are three optional properties: There are three optional properties:
:PROTOCOL, either https or http (the latter for testing, only!). :HOST, the public-facing URI of the server.
:ADDRESS, the servers domain-name/address. :ADDRESS, the address the server is exposed on.
:PORT, the servers port. :PORT, the port the server is exposed on.
There is one required property: There is one required property:
:FETCH, a function used as a callback by activity-servist. :FETCH, a function used as a callback by activity-servist.
@ -43,20 +43,11 @@ There is one required property:
This function should simply return an object from your storage, queried by a URI. This function should simply return an object from your storage, queried by a URI.
The URI parameter is going to be either an @ID or an account-URI of the form acct:username@hostname.") The URI parameter is going to be either an @ID or an account-URI of the form acct:username@hostname.")
(defun users ()
"List of the server's usernames."
'("servistchjo"))
(defun userhosts ()
"List of the server's usernames + hostname."
(mapcar (lambda (username) (str:concat username "@" "etc.xwx.moe"))
(users)))
(defun directories () (defun directories ()
"Alist of the server's paths and their response functions." "Alist of the server's paths and their response functions."
'(("u/" . http-user-dir) '((".well-known/webfinger" . http-webfinger)
(".well-known/webfinger" . http-webfinger) (".well-known/host-meta" . http-host-meta)
(".well-known/host-meta" . http-host-meta))) ("" . http-object))) ; By default, assume object.
(defvar *privkey* (defvar *privkey*
(alexandria:read-file-into-string (alexandria:read-file-into-string
@ -86,16 +77,12 @@ Returns the ActivityPub object associated with the given URI."
(,(str:concat "<?xml version=\"1.0\" encoding=\"UTF-8\"?> (,(str:concat "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<XRD xmlns=\"http://docs.oasis-open.org/ns/xri/xrd-1.0\"> <XRD xmlns=\"http://docs.oasis-open.org/ns/xri/xrd-1.0\">
<link rel=\"lrdd\" type=\"application/xrd+xml\" template=\"" <link rel=\"lrdd\" type=\"application/xrd+xml\" template=\""
(getf *config* :protocol) (getf *config* :host)
"://"
(getf *config* :address)
"/.well-known/webfinger?resource={uri}\"/> "/.well-known/webfinger?resource={uri}\"/>
</XRD> </XRD>
")))) "))))
;;; Webfinger response ;;; Webfinger response
;;; ———————————————————————————————————————— ;;; ————————————————————————————————————————
@ -132,55 +119,19 @@ For information on the property-lists format, see the dosctring of WEBTENTACL
;;; User info response(s) ;;; Object requests
;;; ———————————————————————————————————————— ;;; ————————————————————————————————————————
(defun http-user-dir (env path-items params) (defun http-object (env path-items params)
"Respond to requests within the /u/* directory." "If an ActivityPub object is requested, serve it (if such an object
(let ((user (car path-items))) can be found). Uses the callback :FETCH, defined in *CONFIG*."
;; In case of request for the user's actor. (let* ((uri (reduce (lambda (a b) (format nil "~A/~A" a b))
(if (member user (users) :test 'equal) (append (list (getf *config* :host)) path-items)))
`(200 (:content-type "application/activity+json") (obj (fetch uri)))
(,(user-actor env user)))))) (if obj
(list 200 '(:content-type "application/json")
(defun user-actor (config username) (list (yason:with-output-to-string* () (yason:encode-object obj))))
"The JSON of a user's actor." `(400 (:content-type "text/plain")
(let* ((user-root ("Such an object doesnt exist!")))))
(str:concat (getf *config* :protocol) "://" (getf *config* :address) "/u/" username))
(yason:*symbol-encoder*
'yason:encode-symbol-as-lowercase))
(yason:with-output-to-string* ()
(yason:encode-alist
`(("@context" . ("https://www.w3.org/ns/activitystreams"
"https://w3id.org/security/v1"
"https://litepub.social/litepub/context.jsonld"))
("endpoints" . ,(alexandria:plist-hash-table (list "sharedInbox" "https://etc.xwx.moe/inbox")))
("url" . ,user-root)
("id" . ,user-root)
("type" . "Person")
("preferredUsername" . ,username)
("name" . "Servistiĉo")
("inbox" . ,(str:concat user-root "/inbox"))
("outbox" . ,(str:concat user-root "/outbox"))
("discoverable" . t)
("summary" . "Mi estas simpla roboto, kiu montras ke iomete ekfunkcias activity-servist.
ĉu mi rajtas demeti la servistinan kostumon, nun?
Mi ne estas knabino!!")
("icon"
. ,(alexandria:plist-hash-table
(list
"type" "Image"
"url" "https://xwx.moe/etc/servisticho-profilbildo.jpg")))
("image"
. ,(alexandria:plist-hash-table
(list
"type" "Image"
"url" "https://xwx.moe/etc/servisticho-standardo.png")))
("publicKey"
. ,(alexandria:plist-hash-table
(list
"id" (str:concat user-root "#main-key")
"owner" user-root
"publicKeyPem" *pubkey*))))))))