open Pyops open Pytypes open Batteries open Yojson.Safe open Config open Utils open Pam let issue_of_json (m_room: Datatypes.MatrixRoom.t) (json): Datatypes.forgejo_issue_data = let open Yojson.Safe.Util in let due_date = json |> member "due_date" |> to_option to_string in let record: Datatypes.forgejo_issue_data = { url = json |> member "url" |> to_string; title = json |> member "title" |> to_string; body = json |> member "body" |> to_string; matrix_target = m_room; due_date = due_date } in record let issues_of_json matrix_room json_str = let open Yojson.Safe.Util in try json_str |> from_string |> to_list |> List.map (issue_of_json matrix_room) |> Result.ok with | Yojson.Json_error msg -> Error [%string "JSON parsing error: %{msg}"] module ForgejoUrl = struct type t = {url: string; page: int} let from_id repo_id = { url = [%string "https://salsa.lezzo.org/api/v1/repos/%{repo_id}/issues?state=open&type=issues&limit=50"]; page = 0 } let next_page t = {t with page=t.page+1} let to_string t = [%string "%{t.url}&page=%{t.page#Int}"] end type repo_pytuple = {url: ForgejoUrl.t; headers: pyobject} type http_actor = {requests: pyobject; repos: repo_pytuple StringMap.t} let make_headers base64_password = let headers = [("accept", "application/json"); ("authorization", [%string "Basic %{base64_password}"])] in headers |> List.map (fun (k, v) -> (k, Py.String.of_string v)) |> Py.Dict.of_bindings_string let init (repos: Config.repo_data list) = let _ = Py.initialize () in let requests = Py.import "requests" in let urls = repos |> List.map (fun {forgejo_id; base64_password; matrix_room} -> (matrix_room, {url=ForgejoUrl.from_id forgejo_id; headers=make_headers base64_password})) |> StringMap.of_list in {requests=requests; repos=urls} let pyprint () = let builtins = Py.Eval.get_builtins () in let p = Py.Dict.find_string builtins "print" in Py.Callable.to_function p let extract_pagination resp = let key = Py.String.of_string "X-Total-Count" in resp .@$("headers") .&("get") [|key|] (* not really a pydict, so need to use `get` *) |> Py.Object.to_string |> int_of_string_opt let make_get_request {requests; repos} = let get = Py.Module.get_function_with_keywords requests "get" in let rec fold_fn (accum: (Datatypes.forgejo_issue_data list)) = function | [] -> Ok accum | (m_room_string, {url; headers})::rest -> let pyurl = url |> ForgejoUrl.to_string |> Py.String.of_string in let resp = get [|pyurl|] [("headers", headers)] in let jsontext = resp.@$("text") |> Py.String.to_string in let target_items_total = extract_pagination resp in let matrix_room = Datatypes.MatrixRoom.make m_room_string in let issues = issues_of_json matrix_room jsontext in match target_items_total, issues with | None, _ -> Error "Can't extract pagination" | _, Error err -> Error err | Some target_items_total, Ok target_issues -> let n_received = List.length target_issues in let accum' = target_issues@accum in let urls = if n_received <> target_items_total then (m_room_string, {url=ForgejoUrl.next_page url; headers=headers})::rest else rest in fold_fn accum' urls in StringMap.to_list repos |> fold_fn []