t_ap.ml 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606
  1. (*
  2. * _ _ ____ _
  3. * _| || |_/ ___| ___ _ __ _ __ ___ | |
  4. * |_ .. _\___ \ / _ \ '_ \| '_ \ / _ \| |
  5. * |_ _|___) | __/ |_) | |_) | (_) |_|
  6. * |_||_| |____/ \___| .__/| .__/ \___/(_)
  7. * |_| |_|
  8. *
  9. * Personal Social Web.
  10. *
  11. * t_as2.ml
  12. *
  13. * Copyright (C) The #Seppo contributors. All rights reserved.
  14. *
  15. * This program is free software: you can redistribute it and/or modify
  16. * it under the terms of the GNU General Public License as published by
  17. * the Free Software Foundation, either version 3 of the License, or
  18. * (at your option) any later version.
  19. *
  20. * This program is distributed in the hope that it will be useful,
  21. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  22. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  23. * GNU General Public License for more details.
  24. *
  25. * You should have received a copy of the GNU General Public License
  26. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  27. *)
  28. open Seppo_lib
  29. open Alcotest
  30. module A = Assrt
  31. let set_up () =
  32. Mirage_crypto_rng_lwt.initialize (module Mirage_crypto_rng.Fortuna);
  33. Unix.chdir "../../../test/"
  34. let tc_follower_state () =
  35. Logr.info (fun m -> m "%s.%s" "Ap_test" "test_follower_state");
  36. Ap.Followers.State.(Pending |> to_string) |>
  37. check string __LOC__ "pending"
  38. let tc_inbox_follow () =
  39. Logr.info (fun m -> m "%s.%s" "Ap_test" "test_inbox_follow");
  40. let a = "data/follow.mastodon.json"
  41. |> File.in_channel Ezjsonm.from_channel
  42. |> As2_vocab.Activitypub.Decode.follow
  43. |> Result.get_ok in
  44. a.id |> A.equals_uri __LOC__ (Uri.of_string "https://alpaka.social/afe4ac8a-aaeb-41f5-a348-e5d133bdb931");
  45. a.actor |> A.equals_uri __LOC__ (Uri.of_string "https://alpaka.social/users/traunstein");
  46. a.object_ |> A.equals_uri __LOC__ (Uri.of_string "https://dev.seppo.social/2023-06-12/activitypub/profile.json");
  47. match a.state with
  48. | None -> ()
  49. | _ -> "-" |> A.equals_string __LOC__ ""
  50. let tc_inbox_unfollow () =
  51. Logr.info (fun m -> m "%s.%s" "Ap_test" "test_inbox_unfollow");
  52. let o = "data/undo-follow.mastodon.json"
  53. |> File.in_channel Ezjsonm.from_channel
  54. |> As2_vocab.Activitypub.Decode.(undo follow)
  55. |> Result.get_ok in
  56. o.id |> Uri.to_string |> A.equals_string __LOC__ "https://alpaka.social/users/traunstein#follows/5219/undo";
  57. o.actor |> Uri.to_string |> A.equals_string __LOC__ "https://alpaka.social/users/traunstein";
  58. o.obj.id |> Uri.to_string |> A.equals_string __LOC__ "https://alpaka.social/a5fc3ab5-92f3-4641-a33e-96418e9bec78";
  59. o.obj.object_ |> Uri.to_string |> A.equals_string __LOC__ "https://dev.seppo.social/2023-06-12/activitypub/profile.json";
  60. assert (o.actor |> Uri.equal o.obj.actor);
  61. (*
  62. a.object_ |> A.equals_uri __LOC__ (Uri.of_string "https://dev.seppo.social/2023-06-12/activitypub/profile.json");
  63. match a.state with
  64. | None -> ()
  65. | _ -> "-" |> A.equals_string __LOC__ ""
  66. *)
  67. assert true
  68. let tc_followers_page_json_raw () =
  69. Logr.info (fun m -> m "%s.%s" "Ap_test" "test_followers_page_json_raw");
  70. let fs = [
  71. "1/" |> Uri.of_string;
  72. "2/" |> Uri.of_string;
  73. "3/" |> Uri.of_string;
  74. "4/" |> Uri.of_string;
  75. "5/" |> Uri.of_string;
  76. ] in
  77. let base' = "https://example.com/u/" |> Uri.of_string in
  78. let base = "https://example.com/ap/followers/2.jsa" |> Uri.of_string in
  79. let p : Uri.t As2_vocab.Types.collection_page = {
  80. id = base;
  81. current = Some base;
  82. first = None;
  83. is_ordered = true;
  84. items = fs;
  85. last = Some (Http.reso ~base (Uri.of_string "0.jsa"));
  86. next = Some (Http.reso ~base (Uri.of_string "1.jsa"));
  87. part_of = Some (Http.reso ~base (Uri.of_string "index.jsa"));
  88. prev = Some (Http.reso ~base (Uri.of_string "3.jsa"));
  89. total_items= None;
  90. } in
  91. p |> As2_vocab.Encode.(collection_page ~base (uri ~base:base'))
  92. |> Ezjsonm.value_to_string ~minify:false
  93. |> A.equals_string __LOC__ {|{
  94. "@context": [
  95. "https://www.w3.org/ns/activitystreams",
  96. "https://w3id.org/security/v1",
  97. {
  98. "schema": "http://schema.org#",
  99. "PropertyValue": "schema:PropertyValue",
  100. "value": "schema:value",
  101. "@language": "und"
  102. }
  103. ],
  104. "type": "OrderedCollectionPage",
  105. "id": "https://example.com/ap/followers/2.jsa",
  106. "current": "https://example.com/ap/followers/2.jsa",
  107. "last": "https://example.com/ap/followers/0.jsa",
  108. "next": "https://example.com/ap/followers/1.jsa",
  109. "partOf": "https://example.com/ap/followers/index.jsa",
  110. "prev": "https://example.com/ap/followers/3.jsa",
  111. "orderedItems": [
  112. "https://example.com/u/1/",
  113. "https://example.com/u/2/",
  114. "https://example.com/u/3/",
  115. "https://example.com/u/4/",
  116. "https://example.com/u/5/"
  117. ]
  118. }|}
  119. let tc_followers_page_json () =
  120. Logr.info (fun m -> m "%s.%s" "Ap_test" "test_followers_page_json");
  121. let foo = "https://example.com/foo/usr/" |> Uri.of_string in
  122. let base = "https://example.com/" |> Uri.of_string in
  123. let path = Ap.apub ^ "followers/" in
  124. let base = Http.reso ~base (Uri.make ~path ()) in
  125. [ "1/"; "2/"; "3/"; "4/"; "5/" ]
  126. |> List.rev
  127. |> List.fold_left (fun init path -> (Uri.make ~path () |> Http.reso ~base:foo) :: init) []
  128. |> Ap.Followers.Json.to_page_json ~base path ~is_last:false 2
  129. |> Ezjsonm.value_to_string ~minify:false
  130. |> A.equals_string __LOC__ {|{
  131. "@context": [
  132. "https://www.w3.org/ns/activitystreams",
  133. "https://w3id.org/security/v1",
  134. {
  135. "schema": "http://schema.org#",
  136. "PropertyValue": "schema:PropertyValue",
  137. "value": "schema:value",
  138. "@language": "und"
  139. }
  140. ],
  141. "type": "OrderedCollectionPage",
  142. "id": "https://example.com/activitypub/followers/2.jsa",
  143. "current": "https://example.com/activitypub/followers/2.jsa",
  144. "last": "https://example.com/activitypub/followers/0.jsa",
  145. "next": "https://example.com/activitypub/followers/1.jsa",
  146. "partOf": "https://example.com/activitypub/followers/index.jsa",
  147. "prev": "https://example.com/activitypub/followers/3.jsa",
  148. "orderedItems": [
  149. "https://example.com/foo/usr/1/",
  150. "https://example.com/foo/usr/2/",
  151. "https://example.com/foo/usr/3/",
  152. "https://example.com/foo/usr/4/",
  153. "https://example.com/foo/usr/5/"
  154. ]
  155. }|}
  156. let tc_followers_json () =
  157. Logr.info (fun m -> m "%s.%s" "Ap_test" "test_followers_json");
  158. let foo = "https://example.com/foo/usr/" |> Uri.of_string in
  159. let lst = [ "1/"; "2/"; "3/"; "4/"; "5/" ]
  160. |> List.rev
  161. |> List.fold_left (fun init path -> (Uri.make ~path () |> Http.reso ~base:foo) :: init) []
  162. in
  163. let base = "https://example.com/" |> Uri.of_string in
  164. let path = Ap.apub ^ "followers/" in
  165. let base = Http.reso ~base (Uri.make ~path ()) in
  166. let _ = File.mkdir_p File.pDir path in
  167. (* let oc = stdout in *)
  168. "/dev/null" |> File.out_channel_append (fun oc ->
  169. Ap.Followers.Json.(List.fold_left
  170. (fold2pages 3 (flush_page_json ~base ~oc path))
  171. (0,0,[],0)
  172. lst
  173. |> flush_page_json ~base ~is_last:true ~oc path);
  174. (* check existence of index.jsa *)
  175. (* check existence of 0.jsa *)
  176. (* check existence of 1.jsa *)
  177. "" |> A.equals_string __LOC__ ""
  178. )
  179. let tc_followers_cdb_json () =
  180. Logr.info (fun m -> m "%s.%s" "Ap_test" "test_followers_cdb_json");
  181. "/dev/null" |> File.out_channel_append (fun oc ->
  182. let base = "https://example.com/" |> Uri.of_string in
  183. let path = Ap.apub ^ "follower2/" in
  184. let base = Http.reso ~base (Uri.make ~path ()) in
  185. let path = "tmp/" ^ path in
  186. let _ = File.mkdir_p File.pDir path in
  187. let _ = Ap.Followers.Json.coll_of_cdb ~base ~oc ~pagesize:3 path (Mapcdb.Cdb "data/followers.cdb") in
  188. "" |> A.equals_string __LOC__ ""
  189. )
  190. module Note = struct
  191. let tc_of_rfc4287 () =
  192. let open Rfc4287 in
  193. let e = {Entry.empty with
  194. id = "" |> Uri.of_string;
  195. lang = Rfc4646 "nl";
  196. title = "Title";
  197. published = Rfc3339.T "2023-10-24T11:12:13+02:00";
  198. updated = Rfc3339.T "2023-10-24T11:12:13+02:00";
  199. links = [Link.make ("https://Seppo.Social/demo" |> Uri.of_string) ];
  200. content = "string";
  201. } in
  202. e.title |> check string __LOC__ "Title";
  203. let n = e |> Ap.Note.of_rfc4287 in
  204. let _,su = n.summary_map |> List.hd in
  205. su |> check string __LOC__ "Title";
  206. n.url |> List.hd |> Uri.to_string |> check string __LOC__ "https://seppo.social/demo"
  207. let tc_create_note_json () =
  208. Logr.info (fun m -> m "%s.%s" "Ap_test" "test_create_note_json");
  209. "data/create.note.mastodon2.json" |> File.in_channel (fun ic ->
  210. let c : As2_vocab.Types.note As2_vocab.Types.create = Ezjsonm.from_channel ic
  211. |> As2_vocab.Decode.(create note)
  212. |> Result.get_ok in
  213. assert (not c.direct_message);
  214. let n = c.obj in
  215. n.id |> Uri.to_string |> A.equals_string __LOC__ "https://bewegung.social/users/mro/statuses/111561416759041219";
  216. n.attributed_to |> Uri.to_string |> A.equals_string __LOC__ "https://bewegung.social/users/mro";
  217. n.published |> Option.get |> Ptime.to_rfc3339 |> A.equals_string __LOC__ "2023-12-11T10:55:25-00:00";
  218. n.in_reply_to |> List.length |> A.equals_int __LOC__ 0;
  219. assert (n.summary_map |> List.length = 0);
  220. let _,co = n.content_map |> List.hd in
  221. co |> A.equals_string __LOC__ {|<p>Good morning 😀 <a href="https://bewegung.social/tags/Social" class="mention hashtag" rel="tag">#<span>Social</span></a> <a href="https://bewegung.social/tags/Web" class="mention hashtag" rel="tag">#<span>Web</span></a>! <span class="h-card" translate="no"><a href="https://seppo.social/demo/" class="u-url mention">@<span>demo</span></a></span></p>|};
  222. n.tags |> List.length |> A.equals_int __LOC__ 3;
  223. (match n.tags with
  224. | [
  225. {ty=`Mention;name=n0;href=h0};
  226. {ty=`Hashtag;name=n1;href=h1};
  227. {ty=`Hashtag;name=n2;href=h2};
  228. ] ->
  229. n0 |> A.equals_string __LOC__ "@demo@seppo.social";
  230. h0 |> Uri.to_string |> A.equals_string __LOC__ "https://seppo.social/demo/activitypub/profile.jlda";
  231. n1 |> A.equals_string __LOC__ "#social";
  232. h1 |> Uri.to_string |> A.equals_string __LOC__ "https://bewegung.social/tags/social";
  233. n2 |> A.equals_string __LOC__ "#web";
  234. h2 |> Uri.to_string |> A.equals_string __LOC__ "https://bewegung.social/tags/web";
  235. ()
  236. | _ -> failwith "aua"
  237. (**) );
  238. assert true
  239. )
  240. let tc_diluviate () =
  241. let n =
  242. {Ap.Note.empty with
  243. id = "https://example.com/1" |> Uri.of_string;
  244. sensitive = true;
  245. content_map= ["de","Inhalt"];
  246. summary_map= ["de","⚠️ Summary"];
  247. url = ["https://Seppo.Social/demo" |> Uri.of_string];
  248. } in
  249. let base = Uri.empty in
  250. let minify = false in
  251. n
  252. |> Ap.Note.diluviate
  253. |> As2_vocab.Encode.note ~base |> Ezjsonm.value_to_string ~minify
  254. |> Assrt.equals_string
  255. __LOC__ {|{
  256. "type": "Note",
  257. "id": "https://example.com/1",
  258. "attributedTo": "",
  259. "mediaType": "text/html; charset=utf8",
  260. "contentMap": {
  261. "de": "<a href='https://seppo.social/demo'>https://seppo.social/demo</a><br/>\n<br/>\nInhalt"
  262. },
  263. "sensitive": true,
  264. "summaryMap": {
  265. "de": "⚠️ Summary"
  266. },
  267. "url": "https://example.com/1"
  268. }|};
  269. assert true
  270. let tc_note_to_plain () =
  271. (*
  272. test/data/ap/inbox/create/note/note-OZcAekXDY1A.json
  273. *)
  274. ()
  275. end
  276. let tc_create_follow_json () =
  277. let to_actor = "http://example.com/to/actor" |> Uri.of_string
  278. and from_actor = "http://example.com/from/actor" |> Uri.of_string
  279. and to_inbox = "http://example.com/to/inbox" |> Uri.of_string
  280. and base = "http://example.com/from/" |> Uri.of_string
  281. and tnow = Ptime.of_date_time ((2024,1,2), ((3,4,5),60*60)) |> Option.value ~default:Ptime.max
  282. and minify = false in
  283. let _fo = to_actor |> Ap.Following.make ~tnow ~me:from_actor ~inbox:to_inbox in
  284. _fo
  285. |> As2_vocab.Encode.follow ~base
  286. |> Ezjsonm.value_to_string ~minify |>
  287. Assrt.equals_string
  288. __LOC__ {|{
  289. "@context": [
  290. "https://www.w3.org/ns/activitystreams",
  291. "https://w3id.org/security/v1",
  292. {
  293. "schema": "http://schema.org#",
  294. "PropertyValue": "schema:PropertyValue",
  295. "value": "schema:value",
  296. "@language": "und"
  297. }
  298. ],
  299. "type": "Follow",
  300. "id": "http://example.com/from/actor#subscribe",
  301. "actor": "http://example.com/from/actor",
  302. "endTime": "2024-04-03T02:04:05Z",
  303. "object": "http://example.com/to/actor"
  304. }|}
  305. let tc_reject_json () =
  306. Logr.info (fun m -> m "%s.%s" "Ap_test" "test_reject_json");
  307. let reject me id =
  308. `O [("@context", `String "https://www.w3.org/ns/activitystreams");
  309. ("type", `String "Reject");
  310. ("actor", `String (me |> Uri.to_string));
  311. ("object", `String (id |> Uri.to_string))]
  312. in
  313. let me = "https://example.com/alice" |> Uri.of_string in
  314. let id = match "data/create.note.mastodon2.json" |> File.in_channel Ezjsonm.from_channel with
  315. | `O (_ :: ("id", `String id) :: _) -> id |> Uri.of_string
  316. | _ -> Uri.empty in
  317. id |> Uri.to_string |> A.equals_string __LOC__ {|https://bewegung.social/users/mro/statuses/111561416759041219/activity|};
  318. id
  319. |> reject me
  320. |> Ezjsonm.value_to_string
  321. |> A.equals_string __LOC__ {|{"@context":"https://www.w3.org/ns/activitystreams","type":"Reject","actor":"https://example.com/alice","object":"https://bewegung.social/users/mro/statuses/111561416759041219/activity"}|};
  322. assert true
  323. let tc_subscribed () =
  324. Logr.info (fun m -> m "%s" __LOC__);
  325. for i = 0 to 10 do
  326. try i |> Printf.sprintf "/tmp/%d.xml" |> Unix.unlink
  327. with | Unix.Unix_error(Unix.ENOENT, "unlink", _) -> ()
  328. done ;
  329. let cdb = Mapcdb.Cdb "data/2024-04-30-131146-subscribed.cdb" in
  330. cdb
  331. |> Mapcdb.fold_left (fun c _ -> 1 + c) 0
  332. |> A.equals_int __LOC__ 52;
  333. let r = Ap.Followers.Atom.rule in
  334. r.target |> A.equals_string __LOC__ "activitypub/subscribers/index.xml";
  335. (match Ap.Followers.Atom.of_cdb
  336. ~cdb
  337. ~base:("https://example.com/" |> Uri.of_string)
  338. ~title:"My Title"
  339. ~xsl:(Rfc4287.xsl "my.xsl" "./tmp/noop")
  340. ~rel:(Some Rfc4287.Link.subscribers)
  341. ~page_size:5
  342. "./tmp/" with
  343. | Error _ -> failwith __LOC__
  344. | Ok s -> s)
  345. |> A.equals_string __LOC__ "./tmp/10.xml";
  346. "./tmp/0.xml" |> File.to_string |> A.equals_string __LOC__ {|<?xml version="1.0"?>
  347. <?xml-stylesheet type="text/xsl" href="../../themes/current/my.xsl"?>
  348. <feed xmlns="http://www.w3.org/2005/Atom" xml:base="https://example.com/">
  349. <title>My Title</title>
  350. <id>0.xml</id>
  351. <link rel="self" href="0.xml" title="1"/>
  352. <link rel="first" href="." title="last"/>
  353. <link rel="last" href="0.xml" title="1"/>
  354. <link rel="previous" href="1.xml" title="2"/>
  355. <link rel="subscribers" href="https://social.wohlfarth.name/users/alain" title=""/>
  356. <link rel="subscribers" href="https://hci.social/users/sigchi" title=""/>
  357. <link rel="subscribers" href="https://fosstodon.org/users/nlnetlabs" title=""/>
  358. <link rel="subscribers" href="https://mastodon.xyz/users/NGIZero" title=""/>
  359. <link rel="subscribers" href="https://w3c.social/users/w3c" title=""/>
  360. </feed>|};
  361. "./tmp/10.xml" |> File.to_string |> A.equals_string __LOC__ {|<?xml version="1.0"?>
  362. <?xml-stylesheet type="text/xsl" href="../../themes/current/my.xsl"?>
  363. <feed xmlns="http://www.w3.org/2005/Atom" xml:base="https://example.com/">
  364. <title>My Title</title>
  365. <id>10.xml</id>
  366. <link rel="self" href="10.xml" title="11"/>
  367. <link rel="first" href="." title="last"/>
  368. <link rel="last" href="0.xml" title="1"/>
  369. <link rel="previous" href="11.xml" title="12"/>
  370. <link rel="next" href="9.xml" title="10"/>
  371. <link rel="subscribers" href="https://mas.to/users/leostera" title=""/>
  372. <link rel="subscribers" href="https://social.network.europa.eu/users/EU_Commission" title="European Commission" rfc7565="acct:EU_Commission@social.network.europa.eu"/>
  373. </feed>|};
  374. "?" |> A.equals_string __LOC__ "?"
  375. let tc_to_rfc4287_0 () =
  376. Logr.info (fun m -> m "%s.%s" "Ap_test" "to_rfc4287_0");
  377. let now = ((2024,3,19), ((1,2,3),60*60)) |> Ptime.of_date_time
  378. and tz = Timedesc.Time_zone.utc in
  379. let n = {Ap.Note.empty with
  380. id = "https://example.com/o/42" |> Uri.of_string;
  381. attributed_to = "https://example.com/a/23" |> Uri.of_string;
  382. content_map = ["en","Some Content"];
  383. published = now;
  384. summary_map = ["en","No content warning"];
  385. } in
  386. let now = now |> Option.value ~default:Ptime.epoch in
  387. let a = n |> Ap.Note.to_rfc4287 ~tz ~now in
  388. a.id |> Uri.to_string |> Assrt.equals_string __LOC__ "https://example.com/o/42";
  389. a.author.uri |> Option.value ~default:Uri.empty |> Uri.to_string |> Assrt.equals_string __LOC__ "https://example.com/a/23";
  390. let Rfc3339.T t = a.published in
  391. t |> Assrt.equals_string __LOC__ "2024-03-19T00:02:03Z";
  392. a.title |> Assrt.equals_string __LOC__ "No content warning";
  393. a.content |> Assrt.equals_string __LOC__ "Some Content";
  394. ()
  395. let tc_to_rfc4287_loaded () =
  396. Logr.info (fun m -> m "%s.%s" "Ap_test" "to_rfc4287_loaded");
  397. let load_note fn =
  398. let fn = "data/ap/inbox/create/note/" ^ fn in
  399. fn
  400. |> File.in_channel
  401. (fun ic ->
  402. match ic |> Ezjsonm.from_channel |> As2_vocab.Activitypub.Decode.obj with
  403. | Error _ -> failwith "failed to load note"
  404. | Ok o -> match o with
  405. | `Create { obj = `Note obj; _ } -> obj
  406. | _ -> failwith "strange type")
  407. in
  408. let now = ((2024,3,19), ((1,2,3),60*60)) |> Ptime.of_date_time |> Option.get
  409. and tz = Timedesc.Time_zone.utc
  410. and base = "https://example.com/fe/" |> Uri.of_string
  411. and attr = [ ((Xmlm.ns_xmlns,"xmlns"),Xml.ns_a);
  412. ((Xmlm.ns_xmlns,"thr") ,Xml.ns_thr);
  413. ((Xmlm.ns_xmlns,"as") ,Xml.ns_as);
  414. ((Xmlm.ns_xmlns,"wf") ,Xml.ns_rfc7033); ] in
  415. let apu = load_note "note-Hjcb9bqwCgk.json" in
  416. let ato = apu |> Ap.Note.to_rfc4287 ~tz ~now in
  417. ato.author.name |> Assrt.equals_string __LOC__ "https://mastodon.social/users/nicholas_saunders";
  418. ato.author.uri |> Option.value ~default:Uri.empty |> Uri.to_string |> Assrt.equals_string __LOC__ "https://mastodon.social/users/nicholas_saunders";
  419. ato.content |> Assrt.equals_string __LOC__ {|@wschenk@floss.social @geoglyphentropy@mstdn.social @nus@mstdn.social @DavidKafri@tooot.im @thetechtutor@me.dm After the Goat refused to explain what military action in response to the #alaqsaflood[1] would've been moral he lost all priveleges with me, although he eventually admitted that nothing would meet his standards. Eventually he blocked me, although he seems to have somehow replied to me.
  420. I'm inferring that this has something to with the Goat, but have no idea. Guess I'll never know what it was about.
  421. [1]: https://mastodon.social/tags/alaqsaflood|};
  422. let buf = 1024 |> Buffer.create in
  423. buf |> Xml.to_buf (ato |> Rfc4287.Entry.to_atom ~attr ~base);
  424. buf |> Buffer.to_bytes |> Bytes.to_string |> Assrt.equals_string __LOC__ {|<?xml version="1.0"?>
  425. <entry xml:lang="en" xmlns="http://www.w3.org/2005/Atom" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:as="https://www.w3.org/ns/activitystreams" xmlns:wf="urn:ietf:rfc:7033">
  426. <id>https://mastodon.social/users/nicholas_saunders/statuses/112615088973179734</id>
  427. <title type="text"></title>
  428. <updated>2024-06-14T12:58:07Z</updated>
  429. <published>2024-06-14T12:58:07Z</published>
  430. <as:sensitive>false</as:sensitive>
  431. <author>
  432. <name>https://mastodon.social/users/nicholas_saunders</name>
  433. <wf:uri></wf:uri>
  434. <uri>https://mastodon.social/users/nicholas_saunders</uri></author>
  435. <link rel="self" href="https://mastodon.social/users/nicholas_saunders/statuses/112615088973179734"/>
  436. <thr:in-reply-to ref="https://floss.social/users/wschenk/statuses/112615060106830322"/>
  437. <content type="text">@wschenk@floss.social @geoglyphentropy@mstdn.social @nus@mstdn.social @DavidKafri@tooot.im @thetechtutor@me.dm After the Goat refused to explain what military action in response to the #alaqsaflood[1] would've been moral he lost all priveleges with me, although he eventually admitted that nothing would meet his standards. Eventually he blocked me, although he seems to have somehow replied to me.
  438. I'm inferring that this has something to with the Goat, but have no idea. Guess I'll never know what it was about.
  439. [1]: https://mastodon.social/tags/alaqsaflood</content>
  440. </entry>|};
  441. ();
  442. let apu : As2_vocab.Types.note = load_note "note-OiqQGte9TSY.json" in
  443. (match apu.in_reply_to with
  444. | [ u ] -> u |> Uri.to_string |> Assrt.equals_string __LOC__ {|https://infosec.exchange/users/womble/statuses/112948594753912420|}
  445. | _ -> failwith __LOC__);
  446. let ato : Rfc4287.Entry.t = apu |> Ap.Note.to_rfc4287 ~tz ~now in
  447. apu.in_reply_to |> List.length |> Assrt.equals_int __LOC__ 1;
  448. ato.in_reply_to |> List.length |> Assrt.equals_int __LOC__ (apu.in_reply_to |> List.length);
  449. buf |> Buffer.clear;
  450. buf |> Xml.to_buf (ato |> Rfc4287.Entry.to_atom ~attr ~base);
  451. buf |> Buffer.to_bytes |> Bytes.to_string |> Assrt.equals_string __LOC__ {|<?xml version="1.0"?>
  452. <entry xml:lang="und" xmlns="http://www.w3.org/2005/Atom" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:as="https://www.w3.org/ns/activitystreams" xmlns:wf="urn:ietf:rfc:7033">
  453. <id>https://benjojo.co.uk/u/benjojo/h/89L5knBH4TGG4qY4VT</id>
  454. <title type="text"></title>
  455. <updated>2024-08-12T10:40:45Z</updated>
  456. <published>2024-08-12T10:40:45Z</published>
  457. <as:sensitive>false</as:sensitive>
  458. <author>
  459. <name>benjojo</name>
  460. <wf:uri>acct:benjojo@benjojo.co.uk</wf:uri>
  461. <uri>https://benjojo@benjojo.co.uk/u/benjojo</uri></author>
  462. <link rel="self" href="https://benjojo.co.uk/u/benjojo/h/89L5knBH4TGG4qY4VT"/>
  463. <thr:in-reply-to ref="https://infosec.exchange/users/womble/statuses/112948594753912420"/>
  464. <content type="text">@womble@infosec.exchange It's automatically enabled on most modern android devices and as far as I understand iPhones too. I would assume either the phone that is sending the messages just enabled it seemlessly with w/e API they are using to dispatch messages, or that they are assuming (probably correctly) that whatever carrier side spam filtering exists on the SMS end does not get applied on the RCS end</content>
  465. </entry>|};
  466. ()
  467. let tc_decode_create_notes () =
  468. Logr.info (fun m -> m "%s" __LOC__);
  469. let dir = "data/ap/inbox/create/note/" in
  470. let l : As2_vocab.Types.note list =
  471. dir |> File.fold_dir
  472. (fun init f ->
  473. let f = dir ^ f in
  474. try
  475. let j = f |> File.in_channel Ezjsonm.from_channel in
  476. (match j |> As2_vocab.Activitypub.Decode.obj with
  477. | Ok `Update { obj = `Note obj; _ }
  478. | Ok `Create { obj = `Note obj; _ } -> obj :: init
  479. | _ ->
  480. Logr.debug (fun m -> m "%s %s no!" "decode_create_notes" f);
  481. init),true
  482. with
  483. | e ->
  484. Logr.debug (fun m -> m "%s %s ouch! %s" "decode_create_notes" f (Printexc.to_string e));
  485. init,true)
  486. [] in
  487. l |> List.length |> Assrt.equals_int __LOC__ 204;
  488. let now = ((2024,3,19), ((1,2,3),60*60)) |> Ptime.of_date_time |> Option.value ~default:Ptime.epoch in
  489. let tz = Timedesc.Time_zone.make "Europe/Amsterdam" |> Option.value ~default:Timedesc.Time_zone.utc in
  490. let l = l |> List.map (Ap.Note.to_rfc4287 ~tz ~now) in
  491. l |> List.length |> Assrt.equals_int __LOC__ 204;
  492. let l = l |> List.sort Rfc4287.Entry.compare in
  493. let l = l |> List.filteri (fun i _ -> i < 2) in
  494. let x = l |> Rfc4287.Feed.to_atom
  495. ~base:(Uri.of_string "http://example.com/")
  496. ~self:(Uri.of_string "seppo.cgi/p-37/")
  497. ~prev:None
  498. ~next:None
  499. ~first:(Uri.of_string "seppo.cgi/p-37/")
  500. ~last:(Uri.of_string "seppo.cgi/p-0/")
  501. ~title:"ti"
  502. ~updated:(Rfc3339.T "2024-06-19 16:15:14+02:00")
  503. ~lang:(Rfc4287.Rfc4646 "de")
  504. ~author:{Rfc4287.Person.empty with
  505. name = "Alice";
  506. uri = Some ("alice@example.com" |> Uri.of_string)} in
  507. let fn = "./tmp/as2_vocab_text.dat" in
  508. (try fn |> Unix.unlink with _ -> ());
  509. fn |> File.out_channel_replace (x |> Xml.to_chan ~xsl:(Rfc4287.xsl "posts.xsl" fn));
  510. (match fn |> File.cat with
  511. | Error _ -> failwith __LOC__
  512. | Ok s -> s)
  513. |> Assrt.equals_string __LOC__ {|<?xml version="1.0"?>
  514. <?xml-stylesheet type="text/xsl" href="../../themes/current/posts.xsl"?>
  515. <feed xmlns="http://www.w3.org/2005/Atom" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:wf="urn:ietf:rfc:7033" xmlns:as="https://www.w3.org/ns/activitystreams" xml:lang="de" xml:base="http://example.com/">
  516. <id>http://example.com/seppo.cgi/p-37/</id>
  517. <title type="text">ti</title>
  518. <updated>2024-06-19 16:15:14+02:00</updated>
  519. <generator uri="Seppo.Social">Seppo - Personal Social Web</generator>
  520. <link rel="self" href="seppo.cgi/p-37/" title="38"/>
  521. <link rel="first" href="seppo.cgi/p-37/" title="last"/>
  522. <link rel="last" href="seppo.cgi/p-0/" title="1"/>
  523. <entry xml:lang="en">
  524. <id>https://mastodon.social/users/nicholas_saunders/statuses/112608234771751001</id>
  525. <title type="text"></title>
  526. <updated>2024-06-13T09:55:01+02:00</updated>
  527. <published>2024-06-13T09:55:01+02:00</published>
  528. <as:sensitive>false</as:sensitive>
  529. <author>
  530. <name>https://mastodon.social/users/nicholas_saunders</name>
  531. <wf:uri></wf:uri>
  532. <uri>https://mastodon.social/users/nicholas_saunders</uri></author>
  533. <link rel="self" href="https://mastodon.social/users/nicholas_saunders/statuses/112608234771751001"/>
  534. <thr:in-reply-to ref="https://infosec.exchange/users/SpaceLifeForm/statuses/112608204971110992"/>
  535. <content type="text">@SpaceLifeForm@infosec.exchange @RickiTarr@beige.party I'll be here all day @SpaceLifeForm@infosec.exchange :)
  536. (not really.)</content>
  537. </entry>
  538. <entry xml:lang="en">
  539. <id>https://mastodon.social/users/nicholas_saunders/statuses/112606941257663038</id>
  540. <title type="text"></title>
  541. <updated>2024-06-13T04:26:03+02:00</updated>
  542. <published>2024-06-13T04:26:03+02:00</published>
  543. <as:sensitive>false</as:sensitive>
  544. <author>
  545. <name>https://mastodon.social/users/nicholas_saunders</name>
  546. <wf:uri></wf:uri>
  547. <uri>https://mastodon.social/users/nicholas_saunders</uri></author>
  548. <link rel="self" href="https://mastodon.social/users/nicholas_saunders/statuses/112606941257663038"/>
  549. <thr:in-reply-to ref="https://mastodon.social/users/thejapantimes/statuses/112606642206596379"/>
  550. <content type="text">@thejapantimes@mastodon.social so then #hamas[1] didn't accept the offer, but rather seeks different terms.
  551. [1]: https://mastodon.social/tags/hamas</content>
  552. </entry>
  553. </feed>|}
  554. let () =
  555. run
  556. "seppo_suite" [
  557. __FILE__ , [
  558. "set_up", `Quick, set_up;
  559. "tc_follower_state", `Quick, tc_follower_state;
  560. "tc_inbox_follow", `Quick, tc_inbox_follow;
  561. "tc_inbox_unfollow", `Quick, tc_inbox_unfollow;
  562. "tc_followers_page_json_raw",`Quick, tc_followers_page_json_raw;
  563. "tc_followers_page_json", `Quick, tc_followers_page_json;
  564. "tc_followers_json", `Quick, tc_followers_json;
  565. "tc_followers_cdb_json", `Quick, tc_followers_cdb_json;
  566. "Note.tc_of_rfc4287", `Quick, Note.tc_of_rfc4287;
  567. "Note.tc_create_note_json", `Quick, Note.tc_create_note_json;
  568. "Note.tc_diluviate", `Quick, Note.tc_diluviate;
  569. "Note.tc_note_to_plain", `Quick, Note.tc_note_to_plain;
  570. "tc_create_follow_json", `Quick, tc_create_follow_json;
  571. "tc_reject_json", `Quick, tc_reject_json;
  572. "tc_subscribed", `Quick, tc_subscribed;
  573. "tc_to_rfc4287_0", `Quick, tc_to_rfc4287_0;
  574. "tc_to_rfc4287_loaded", `Quick, tc_to_rfc4287_loaded;
  575. "tc_decode_create_notes", `Quick, tc_decode_create_notes;
  576. ]
  577. ];
  578. assert true