001  (ns dog-and-duck.quack.quack
002    "Validator for ActivityPub objects: if it walks like a duck, and it quacks like a duck..."
003    ;;(:require [clojure.spec.alpha as s])
004    (:import [java.net URI URISyntaxException]))
005  
006  ;;;     Copyright (C) Simon Brooke, 2022
007  
008  ;;;     This program is free software; you can redistribute it and/or
009  ;;;     modify it under the terms of the GNU General Public License
010  ;;;     as published by the Free Software Foundation; either version 2
011  ;;;     of the License, or (at your option) any later version.
012  
013  ;;;     This program is distributed in the hope that it will be useful,
014  ;;;     but WITHOUT ANY WARRANTY; without even the implied warranty of
015  ;;;     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016  ;;;     GNU General Public License for more details.
017  
018  ;;;     You should have received a copy of the GNU General Public License
019  ;;;     along with this program; if not, write to the Free Software
020  ;;;     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
021  
022  (defn object?
023    "Returns `true` iff `x` is recognisably an ActivityStreams object.
024     
025     **NOTE THAT** The ActivityStreams spec 
026     [says](https://www.w3.org/TR/activitystreams-core/#object):
027     
028     > All properties are optional (including the id and type)
029     
030     But we are *just not having that*, because otherwise we're flying blind.
031     We *shall* reject objects lacking at least `:type`. Missing `:id` keys are
032     tolerable because they represent transient objects, which we expect to 
033     handle."
034    [x]
035    (and (map? x) (:type x) true))
036  
037  (defn persistent-object?
038    "`true` iff `x` is a persistent object.
039  
040     Transient objects in ActivityPub are not required to have an `id` key, but persistent
041     ones must have a key, and it must be an IRI (but normally a URI)."
042    [x]
043    (try
044      (and (object? x) (uri? (URI. (:id x))))
045      (catch URISyntaxException _ false)))
046  
047  (persistent-object? {:type "test" :id "https://mastodon.scot/@barfilfarm"})
048  
049  (def ^:const actor-types
050    "The set of types we will accept as actors.
051     
052     There's an [explicit set of allowed actor types]
053     (https://www.w3.org/TR/activitystreams-vocabulary/#actor-types)."
054    #{"Application"
055      "Group"
056      "Organization"
057      "Person"
058      "Service"})
059  
060  (defn actor-type?
061    ;; TODO: better as a macro
062    [x]
063    (if (actor-types x) true false))
064  
065  (def ^:const verb-types
066    "The set of types we will accept as verbs.
067     
068     There's an [explicit set of allowed verb types]
069     (https://www.w3.org/TR/activitystreams-vocabulary/#activity-types)."
070    #{"Accept" "Add" "Announce" "Arrive" "Block" "Create" "Delete" "Dislike"
071      "Flag" "Follow" "Ignore" "Invite" "Join" "Leave" "Like" "Listen" "Move"
072      "Offer" "Question" "Reject" "Read" "Remove" "TentativeAccept"
073      "TentativeReject" "Travel" "Undo" "Update" "View"})
074  
075  (defn verb-type?
076    ;; TODO: better as a macro
077    [x]
078    (if (verb-types x) true false))
079  
080  (def ^:const activitystreams-context-uri
081    "The URI of the context of an ActivityStreams object is expected to be this
082     literal string."
083    "https://www.w3.org/ns/activitystreams")
084  
085  (defn context?
086    "Returns `true` iff `x` quacks like an ActivityStreams context, else false.
087     
088     A context is either
089     1. the URI (actually an IRI) `activitystreams-context-uri`, or
090     2. a collection comprising that URI and a map."
091    [x]
092    (cond
093      (nil? x) false
094      (string? x) (and (= x activitystreams-context-uri) true)
095      (coll? x) (and (context? (first (remove map? x))) 
096                     (= (count x) 2)
097                     true)
098      :else false))
099  
100  (defmacro has-context? [x]
101    `(context? ((keyword "@context") ~x)))
102  
103  (defn actor?
104    "Returns `true` if `x` quacks like an actor, else false."
105    [x]
106    (and
107     (object? x)
108     (has-context? x)
109     (uri? (URI. (:inbox x)))
110     (uri? (URI. (:outbox x)))
111     (actor-type? (:type x))
112     true))
113  
114  (defn activity?
115    "`true` iff `x` quacks like an activity, else false."
116    [x]
117    (try
118      (and (object? x)
119           (has-context? x)
120           (string? (:summary x))
121           (actor? (:actor x))
122           (verb-type? (:type x))
123           (or (object? (:object x)) (uri? (URI. (:object x))))
124           true)
125      (catch URISyntaxException _ false)))
126  
127  (defn link?
128    "`true` iff `x` quacks like a link, else false."
129    [x]
130    (and (object? x)
131         (= (:type x) "Link")
132         (uri? (URI. (:href x)))
133         true))
134  
135  (defn link-or-uri?
136    "`true` iff `x` is either a URI or a link, else false.
137     
138     There are several points in the specification where e.g. the `:image`
139     property (if present) may be either a link or a URI."
140    [x]
141    (and
142     (cond (string? x) (uri? (URI. x))
143           :else (link? x))
144     true))
145  
146  (defn collection?
147    "`true` iff `x` quacks like a collection of type `type`, else `false`.
148     
149     With one argument, will recognise plain collections and ordered collections,
150     but (currently) not collection pages."
151    ([x type]
152     (let [items (or (:items x) (:orderedItems x))]
153       (and
154        (cond
155          (:items x) (nil? (:orderedItems x))
156          (:orderedItems x) (nil? (:items x))) ;; can't have both properties
157        (object? x)
158        (= (:type x) type)
159        (coll? items)
160        (every? object? items)
161        (integer? (:totalItems x))
162        true)))
163    ([x]
164     (or (collection? x "Collection")
165         (collection? x "OrderedCollection"))))
166  
167  (defn unordered-collection?
168    "`true` iff `x` quacks like an unordered collection, else `false`."
169    [x]
170    (collection? x "Collection"))
171  
172  (defn ordered-collection?
173    "`true` iff `x` quacks like an ordered collection, else `false`."
174    [x]
175    (collection? x "OrderedCollection"))
176  
177  (defn collection-page?
178    "`true` iff `x` quacks like a page in a paged collection, else `false`."
179    [x]
180    (collection? x "CollectionPage"))
181  
182  (defn ordered-collection-page?
183    "`true` iff `x` quacks like a page in an ordered paged collection, else `false`."
184    [x]
185    (collection? x "OrderedCollectionPage"))
186  
187