123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387(*********************************************************************************)(* OCaml-ActivityPub *)(* *)(* Copyright (C) 2023-2024 INRIA All rights reserved. *)(* Author: Maxence Guesdon, INRIA Saclay *)(* *)(* This program is free software; you can redistribute it and/or modify *)(* it under the terms of the GNU Lesser General Public License version *)(* 3 as published by the Free Software Foundation. *)(* *)(* This program is distributed in the hope that it will be useful, *)(* but WITHOUT ANY WARRANTY; without even the implied warranty of *)(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *)(* GNU General Public License for more details. *)(* *)(* You should have received a copy of the GNU General Public License *)(* along with this program; if not, write to the Free Software *)(* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA *)(* 02111-1307 USA *)(* *)(* Contact: maxence.guesdon@inria.fr *)(* *)(*********************************************************************************)(** Interface of activitypub objects + convenient functions. *)(* This module defines class interfaces of various activitypub
objects. They are implemented in server and client side. *)(** {2 Object types} *)(** An id is an RDF term. *)typeid=Rdf.Term.term(** Generate a new id, by appending to a given base IRI. For a same base IRI,
Ids are guaranteed to be generated in increasing order when compared
alphanumerically. The function returns both the id and the corresponding IRI, for conveniency. *)letgen_idbase_iri=lett=Printf.sprintf"%0.12f"(Ptime.to_float_s(Ptime_clock.now()))inletl=String.split_on_char'.'tinlets=String.concat""linletiri=Iri.append_pathbase_iri[s]in(Rdf.Term.Iriiri,iri)(** A {{:https://www.w3.org/TR/activitystreams-vocabulary/#dfn-link}Link}'s interface. *)classtypelink=objectmethodid:Rdf.Term.termmethodheight:intoptionmethodhref:Iri.tmethodhreflang:stringoptionmethodmedia_type:Ldp.Ct.mimeoptionmethodname:stringoptionmethodrel:stringlistmethodwidth:intoptionmethodg:Rdf.Graph.graphend(** An {{:https://www.w3.org/TR/activitystreams-vocabulary/#dfn-object}Object}'s interface.
Most of the methods are accessors, querying the internal [#g] graph. Before
querying any information, it is important that the object have been
dererefenced using the [#dereference] method. Calling [#dereference] on an
already dereferenced object has no effect, i.e. the internal graph is not updated.
*)andobject_=objectmethodid:idmethodas_id:Iri.toptionmethodiri:Iri.tmethodtype_:Iri.tmethodattachment:[`Loflink|`Oofobject_]listmethodattributed_to:[`Loflink|`Oofobject_]optionmethodaudience:[`Loflink|`Oofobject_]optionmethodlikes:collectionoptionmethodshares:collectionoptionmethodcontent:stringoptionmethodcontent_map:stringSmap.tmethodname:stringoptionmethodname_map:stringSmap.tmethodend_time:Rdf.Term.datetimeoptionmethodgenerator:[`Loflink|`Oofobject_]optionmethodicon:[`Loflink|`Iofimage]listmethodimage:[`Loflink|`Iofimage]listmethodin_reply_to:[`Loflink|`Oofobject_]listmethodpreview:[`Loflink|`Oofobject_]optionmethodpublished:Rdf.Term.datetimeoptionmethodreplies:collectionoptionmethodstart_time:Rdf.Term.datetimeoptionmethodsummary:stringoptionmethodsummary_map:stringSmap.tmethodtag:[`Loflink|`Oofobject_]listmethodupdated:Rdf.Term.datetimeoptionmethodurl:[`Loflink|`IofIri.t]listmethodto_:[`Loflink|`Oofobject_]listmethodbto:[`Loflink|`Oofobject_]listmethodcc:[`Loflink|`Oofobject_]listmethodbcc:[`Loflink|`Oofobject_]listmethodmedia_type:Ldp.Ct.mimeoptionmethodg:Rdf.Graph.graphoptionmethodis_empty:boolmethodpp:Format.formatter->unit->unitmethoddereference:unitLwt.tmethodas_link:linkmethodas_activity:activityend(** A {{:https://www.w3.org/TR/activitystreams-vocabulary/#dfn-document}Document}'s interface.*)anddocument=objectinheritobject_end(** An {{:https://www.w3.org/TR/activitystreams-vocabulary/#dfn-image}Image}'s interface. *)andimage=objectinheritdocumentend(** A {{:https://www.w3.org/TR/activitystreams-vocabulary/#dfn-collection}Collection}'s interface. *)andcollection=objectinheritobject_methodtotal_items:intmethodcurrent:[`Loflink|`IofIri.t]optionmethodfirst:[`Loflink|`IofIri.t]optionmethodlast:[`Loflink|`IofIri.t]optionmethoditems:[`Loflink|`Oofobject_]Lwt_stream.tLwt.tend(** A {{:https://www.w3.org/TR/activitystreams-vocabulary/#dfn-collectionpage}CollectionPage}'s interface. *)andcollection_page=objectinheritcollectionmethodpart_of:[`Loflink|`IofIri.t]optionmethodnext:[`Loflink|`IofIri.t]optionmethodprev:[`Loflink|`IofIri.t]optionend(** An {{:https://www.w3.org/TR/activitystreams-vocabulary/#dfn-orderedcollection}OrderedCollection}'s interface. *)andordered_collection=objectinheritcollectionend(** An {{:https://www.w3.org/TR/activitystreams-vocabulary/#dfn-orderedcollectionpage}OrderedCollectionPage}'s interface. *)andordered_collection_page=objectinheritordered_collectioninheritcollection_pagemethodstart_index:intoptionend(** An {{:https://www.w3.org/TR/activitystreams-vocabulary/#dfn-actor}Actor}'s interface. *)andactor=objectinheritobject_methodinbox:ordered_collectionmethodoutbox:ordered_collectionmethodfollowing:collectionoptionmethodfollowers:collectionoptionmethodliked:collectionoptionmethodmanually_approves_followers:bool(** default should be [false] *)methodstreams:collectionlistmethodpreferred_username:stringoptionmethodpublic_keypem:X509.Public_key.toptionmethodpublic_key_iri:Iri.toptionmethodprivate_keypem:X509.Private_key.toptionLwt.tend(** An {{:https://www.w3.org/TR/activitystreams-vocabulary/#dfn-activity}Activity}'s interface. *)andactivity=objectinheritobject_methodactor:[`Loflink|`Oofobject_]optionmethodas_object:object_methodobject_:object_optionmethodtarget:[`Loflink|`Oofobject_]optionmethodorigin:[`Loflink|`Oofobject_]optionmethodresult:[`Loflink|`Oofobject_]optionmethodinstrument:[`Loflink|`Oofobject_]listendmoduleAS=Rdf.Activitypub(** {2 Activity types} *)typeactivity_type=[|`Accept|`Add|`Announce|`Arrive|`Block|`Create|`Delete|`Dislike|`Flag|`Follow|`Ignore|`Invite|`Join|`Leave|`Like|`Listen|`Move|`Offer|`Question|`Read|`Reject|`Remove|`TentativeAccept|`TentativeReject|`Travel|`Undo|`Update|`View]letactivity_types:(activity_type*Iri.t)list=[`Accept,AS.c_Accept;`Add,AS.c_Add;`Announce,AS.c_Announce;`Arrive,AS.c_Arrive;`Block,AS.c_Block;`Create,AS.c_Create;`Delete,AS.c_Delete;`Dislike,AS.c_Dislike;`Flag,AS.c_Flag;`Follow,AS.c_Follow;`Ignore,AS.c_Ignore;`Invite,AS.c_Invite;`Join,AS.c_Join;`Leave,AS.c_Leave;`Like,AS.c_Like;`Listen,AS.c_Listen;`Move,AS.c_Move;`Offer,AS.c_Offer;`Question,AS.c_Question;`Reject,AS.c_Reject;`Read,AS.c_Read;`Remove,AS.c_Remove;`TentativeReject,AS.c_TentativeReject;`TentativeAccept,AS.c_TentativeAccept;`Travel,AS.c_Travel;`Undo,AS.c_Undo;`Update,AS.c_Update;`View,AS.c_View;]letactivity_type_of_iri:Iri.t->activity_typeoption=letmap=List.fold_left(funacc(t,iri)->Iri.Map.addiritacc)Iri.Map.emptyactivity_typesinfuniri->Iri.Map.find_optirimapletiri_of_activity_type:activity_type->Iri.t=letmoduleM=Map.Make(structtypet=activity_typeletcompare=Stdlib.compareend)inletmap=List.fold_left(funacc(t,iri)->M.addtiriacc)M.emptyactivity_typesinfunt->M.findtmap(** {2 Actor types} *)typeactor_type=[`Application|`Group|`Organization|`Person|`Service]letactor_types:(actor_type*Iri.t)list=[`Application,AS.c_Application;`Group,AS.c_Group;`Organization,AS.c_Organization;`Person,AS.c_Person;`Service,AS.c_Service;]letactor_type_of_iri:Iri.t->actor_typeoption=letmap=List.fold_left(funacc(t,iri)->Iri.Map.addiritacc)Iri.Map.emptyactor_typesinfuniri->Iri.Map.find_optirimapletiri_of_actor_type:actor_type->Iri.t=letmoduleM=Map.Make(structtypet=actor_typeletcompare=Stdlib.compareend)inletmap=List.fold_left(funacc(t,iri)->M.addtiriacc)M.emptyactor_typesinfunt->M.findtmap(** {2 Convenient functions} *)(** [iri_of_lo (`L link)] returns [link#href].
[iri_of_lo (`O obj)] returns [obj#iri]. *)letiri_of_lo:[`Loflink|`Oofobject_]->Iri.t=function`Ll->l#href|`Oo->o#iri(** [iri_of_liri (`L link)] returns [link#href].
[iri_of_liri (`I iri)] returns [iri]. *)letiri_of_liri:[`Loflink|`IofIri.t]->Iri.t=function`Ll->l#href|`Ii->i(** [iri_of_li (`L link)] returns [link#href].
[iri_of_li (`I image)] returns the iri associated to [image#id],
or else the first iri of [image#url]. *)letiri_of_li:[`Loflink|`Iofimage]->Iri.t=function|`Ll->l#href|`Ii->matchi#idwith|Rdf.Term.Iriiri->iri|_->matchi#urlwith|[]->i#iri|`Iiri::_->iri|`Ll::_->l#href(** [actor_name a] returns name of actor is present, or preferred_username
if present, or [""]. Whe a [lang] argument is given, lookup for the name
in then name language map of [a]. *)letactor_name?langa=matchmatchlangwith|None->a#name|Somelang->matchSmap.find_optlanga#name_mapwith|None->a#name|x->xwith|None->Option.value~default:""a#preferred_username|Somestr->str(** [object_content o] returns content string of [o] if
present, or [""]. If a [lang] argument is given, lookup in
the content map of [o]. *)letobject_content?lang(o:object_)=Option.value~default:""(matchlangwith|None->o#content|Somelang->matchSmap.find_optlango#content_mapwith|None->o#content|x->x)(** {2 Activity trees}
This is a recursive representation of activities, since activities
can refer to activities, themselves referring to other activities and so on.
It is sometimes useful to be able to pattern-match on activities on different
depths. *)typeactivity_obj=[`None|`Activityofactivity_tree|`Actorofactor_type*object_|`Objectofobject_|`Loopofid]andactivity_tree=activity_type*activity*activity_obj(** Build an {!type-activity_tree} from the given object and
its internal graph. No dereferencing is performed
to build the tree. *)letactivity_tree=letreciterseen:object_option->activity_obj=function|None->`None|Someo->ifRdf.Term.TSet.memo#idseenthen`Loopo#idelselettyp=o#type_inmatchactivity_type_of_iritypwith|Somet->leta=o#as_activityin`Activity(t,a,iter(Rdf.Term.TSet.adda#idseen)a#object_)|None->matchactor_type_of_iritypwith|Somet->`Actor(t,o)|None->`Objectoinfuno->matchactivity_type_of_irio#type_with|None->None|Somet->Some(t,o,iterRdf.Term.TSet.emptyo#object_)