-module(chrobot). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). -export([open/3, with_timeout/2, to_file/2, as_value/2, quit/1, defer_quit/2, page_caller/1, close/1, screenshot/1, pdf/1, await_load_event/2, launch/0, launch_with_config/1, eval/2, eval_to_value/2, eval_async/2, get_all_html/1, select_all/2, select/2, call_custom_function_on/5, to_value/3, get_attribute/3, get_property/4, get_text/2, get_inner_html/2, get_outer_html/2, await_selector/2, create_page/3]). -export_type([page/0, encoded_file/0, call_argument/0]). -type page() :: {page, gleam@erlang@process:subject(chrome:message()), integer(), protocol@target:target_id(), protocol@target:session_id()}. -type encoded_file() :: {encoded_file, binary(), binary()}. -type call_argument() :: {string_arg, binary()} | {int_arg, integer()} | {float_arg, float()} | {bool_arg, boolean()} | {array_arg, list(call_argument())}. -spec open(gleam@erlang@process:subject(chrome:message()), binary(), integer()) -> {ok, page()} | {error, chrome:request_error()}. open(Browser_subject, Url, Time_out) -> gleam@result:'try'( protocol@target:create_target( fun(Method, Params) -> chrome:call(Browser_subject, Method, Params, none, Time_out) end, Url, {some, 1920}, {some, 1080}, none, none ), fun(Target_response) -> gleam@result:'try'( protocol@target:attach_to_target( fun(Method@1, Params@1) -> chrome:call( Browser_subject, Method@1, Params@1, none, Time_out ) end, erlang:element(2, Target_response), {some, true} ), fun(Session_response) -> {ok, {page, Browser_subject, Time_out, erlang:element(2, Target_response), erlang:element(2, Session_response)}} end ) end ). -spec with_timeout(page(), integer()) -> page(). with_timeout(Page, Time_out) -> {page, erlang:element(2, Page), Time_out, erlang:element(4, Page), erlang:element(5, Page)}. -spec to_file(encoded_file(), binary()) -> {ok, nil} | {error, simplifile:file_error()}. to_file(Input, Path) -> Res = begin _pipe = gleam@bit_array:base64_decode(erlang:element(2, Input)), gleam@result:replace_error(_pipe, unknown) end, gleam@result:'try'( Res, fun(Binary) -> simplifile:write_bits( <<<>/binary, (erlang:element(3, Input))/binary>>, Binary ) end ). -spec as_value( {ok, protocol@runtime:remote_object()} | {error, chrome:request_error()}, fun((gleam@dynamic:dynamic_()) -> {ok, XDI} | {error, any()}) ) -> {ok, XDI} | {error, chrome:request_error()}. as_value(Result, Decoder) -> case Result of {ok, {remote_object, _, _, _, {some, Value}, _, _, _}} -> _pipe = Decoder(Value), gleam@result:replace_error(_pipe, protocol_error); {error, Something} -> {error, Something}; _ -> {error, not_found_error} end. -spec quit(gleam@erlang@process:subject(chrome:message())) -> {ok, nil} | {error, gleam@erlang@process:call_error(nil)}. quit(Browser) -> chrome:quit(Browser). -spec defer_quit( gleam@erlang@process:subject(chrome:message()), fun(() -> any()) ) -> {ok, nil} | {error, gleam@erlang@process:call_error(nil)}. defer_quit(Browser, Body) -> Body(), chrome:quit(Browser). -spec pass_session(protocol@target:session_id()) -> gleam@option:option(binary()). pass_session(Session_id) -> case Session_id of {session_id, Value} -> {some, Value} end. -spec page_caller(page()) -> fun((binary(), gleam@option:option(gleam@json:json())) -> {ok, gleam@dynamic:dynamic_()} | {error, chrome:request_error()}). page_caller(Page) -> fun(Method, Params) -> chrome:call( erlang:element(2, Page), Method, Params, pass_session(erlang:element(5, Page)), erlang:element(3, Page) ) end. -spec close(page()) -> {ok, gleam@dynamic:dynamic_()} | {error, chrome:request_error()}. close(Page) -> protocol@target:close_target(page_caller(Page), erlang:element(4, Page)). -spec screenshot(page()) -> {ok, encoded_file()} | {error, chrome:request_error()}. screenshot(Page) -> gleam@result:'try'( protocol@page:capture_screenshot( page_caller(Page), {some, capture_screenshot_format_png}, none, none ), fun(Response) -> {ok, {encoded_file, erlang:element(2, Response), <<"png"/utf8>>}} end ). -spec pdf(page()) -> {ok, encoded_file()} | {error, chrome:request_error()}. pdf(Page) -> gleam@result:'try'( protocol@page:print_to_pdf( page_caller(Page), {some, false}, {some, false}, none, none, none, none, none, none, none, none, none, none, none, none ), fun(Response) -> {ok, {encoded_file, erlang:element(2, Response), <<"pdf"/utf8>>}} end ). -spec await_load_event(gleam@erlang@process:subject(chrome:message()), page()) -> {ok, gleam@dynamic:dynamic_()} | {error, chrome:request_error()}. await_load_event(Browser, Page) -> gleam@result:'try'( protocol@page:enable(page_caller(Page)), fun(_) -> chrome:listen_once( Browser, <<"Page.loadEventFired"/utf8>>, erlang:element(3, Page) ) end ). -spec validate_launch( {ok, gleam@erlang@process:subject(chrome:message())} | {error, chrome:launch_error()} ) -> {ok, gleam@erlang@process:subject(chrome:message())} | {error, chrome:launch_error()}. validate_launch(Launch_result) -> gleam@result:'try'( Launch_result, fun(Instance) -> {Major, Minor} = protocol:version(), Target_protocol_version = <<<>/binary, Minor/binary>>, Version_response = begin _pipe = chrome:get_version(Instance), gleam@result:replace_error(_pipe, unresponsive_after_start) end, gleam@result:'try'( Version_response, fun(Actual_version) -> case Target_protocol_version =:= erlang:element( 2, Actual_version ) of true -> {ok, Instance}; false -> {error, {protocol_version_mismatch, Target_protocol_version, erlang:element(2, Actual_version)}} end end ) end ). -spec launch() -> {ok, gleam@erlang@process:subject(chrome:message())} | {error, chrome:launch_error()}. launch() -> Launch_result = validate_launch(chrome:launch()), case Launch_result of {error, could_not_find_executable} -> gleam@io:println( <<"\x{1b}[31mChrobot could not find a chrome executable to launch!\x{1b}[0m"/utf8>> ), gleam@io:println(<<"\x{1b}[36m"/utf8>>), gleam@io:println( <<"ℹ️ Hint: Consider installing Chrome for Testing from puppeteer:"/utf8>> ), gleam@io:println(<<"npx @puppeteer/browsers install chrome"/utf8>>), gleam@io:println(<<"\x{1b}[0m "/utf8>>), Launch_result; Other -> Other end. -spec launch_with_config(chrome:browser_config()) -> {ok, gleam@erlang@process:subject(chrome:message())} | {error, chrome:launch_error()}. launch_with_config(Config) -> validate_launch(chrome:launch_with_config(Config)). -spec handle_eval_response( {ok, protocol@runtime:evaluate_response()} | {error, chrome:request_error()} ) -> {ok, protocol@runtime:remote_object()} | {error, chrome:request_error()}. handle_eval_response(Eval_response) -> case Eval_response of {ok, {evaluate_response, _, {some, Exception}}} -> {error, {runtime_exception, erlang:element(3, Exception), erlang:element(4, Exception), erlang:element(5, Exception)}}; {ok, {evaluate_response, Result_data, none}} -> {ok, Result_data}; {error, Other} -> {error, Other} end. -spec eval(page(), binary()) -> {ok, protocol@runtime:remote_object()} | {error, chrome:request_error()}. eval(Page, Expression) -> _pipe = protocol@runtime:evaluate( page_caller(Page), Expression, none, none, {some, false}, none, {some, false}, {some, true}, {some, false} ), handle_eval_response(_pipe). -spec eval_to_value(page(), binary()) -> {ok, protocol@runtime:remote_object()} | {error, chrome:request_error()}. eval_to_value(Page, Expression) -> _pipe = protocol@runtime:evaluate( page_caller(Page), Expression, none, none, {some, false}, none, {some, true}, {some, true}, {some, false} ), handle_eval_response(_pipe). -spec eval_async(page(), binary()) -> {ok, protocol@runtime:remote_object()} | {error, chrome:request_error()}. eval_async(Page, Expression) -> _pipe = protocol@runtime:evaluate( page_caller(Page), Expression, none, none, {some, false}, none, {some, false}, {some, true}, {some, true} ), handle_eval_response(_pipe). -spec get_all_html(page()) -> {ok, binary()} | {error, chrome:request_error()}. get_all_html(Page) -> _pipe = eval(Page, <<"window.document.documentElement.outerHTML"/utf8>>), as_value(_pipe, fun gleam@dynamic:string/1). -spec select_all(page(), binary()) -> {ok, list(protocol@runtime:remote_object_id())} | {error, chrome:request_error()}. select_all(Page, Selector) -> Selector_code = <<<<"window.document.querySelectorAll(\""/utf8, Selector/binary>>/binary, "\")"/utf8>>, Result = eval(Page, Selector_code), case Result of {ok, {remote_object, _, _, _, _, _, _, {some, Remote_object_id}}} -> gleam@result:'try'( protocol@runtime:get_properties( page_caller(Page), Remote_object_id, {some, true} ), fun(Result_properties) -> case Result_properties of {get_properties_response, _, _, {some, Exception}} -> {error, {runtime_exception, erlang:element(3, Exception), erlang:element(4, Exception), erlang:element(5, Exception)}}; {get_properties_response, Property_descriptors, _, none} -> {ok, gleam@list:filter_map( Property_descriptors, fun(Prop_descriptor) -> case Prop_descriptor of {property_descriptor, _, {some, {remote_object, _, _, _, _, _, _, {some, Object_id}}}, _, _, _, _, _, _, _, _} -> {ok, Object_id}; _ -> {error, nil} end end )} end end ); {ok, _} -> {ok, []}; {error, Any} -> {error, Any} end. -spec handle_object_id_response( {ok, protocol@runtime:remote_object()} | {error, chrome:request_error()} ) -> {ok, protocol@runtime:remote_object_id()} | {error, chrome:request_error()}. handle_object_id_response(Response) -> case Response of {ok, {remote_object, _, _, _, _, _, _, {some, Remote_object_id}}} -> {ok, Remote_object_id}; {ok, _} -> {error, not_found_error}; {error, Any} -> {error, Any} end. -spec select(page(), binary()) -> {ok, protocol@runtime:remote_object_id()} | {error, chrome:request_error()}. select(Page, Selector) -> Selector_code = <<<<"window.document.querySelector(\""/utf8, Selector/binary>>/binary, "\")"/utf8>>, _pipe = eval(Page, Selector_code), handle_object_id_response(_pipe). -spec encode_custom_arg(call_argument()) -> gleam@json:json(). encode_custom_arg(Arg) -> case Arg of {string_arg, Value} -> gleam@json:string(Value); {int_arg, Value@1} -> gleam@json:int(Value@1); {float_arg, Value@2} -> gleam@json:float(Value@2); {bool_arg, Value@3} -> gleam@json:bool(Value@3); {array_arg, Value@4} -> gleam@json:array(Value@4, fun encode_custom_arg/1) end. -spec encode_custom_arguments(list(call_argument())) -> gleam@json:json(). encode_custom_arguments(Input) -> gleam@json:array( Input, fun(Arg) -> gleam@json:object([{<<"value"/utf8>>, encode_custom_arg(Arg)}]) end ). -spec call_custom_function_on( fun((binary(), gleam@option:option(gleam@json:json())) -> {ok, gleam@dynamic:dynamic_()} | {error, chrome:request_error()}), binary(), protocol@runtime:remote_object_id(), list(call_argument()), fun((gleam@dynamic:dynamic_()) -> {ok, XKI} | {error, any()}) ) -> {ok, XKI} | {error, chrome:request_error()}. call_custom_function_on( Callback, Function_declaration, Object_id, Arguments, Value_decoder ) -> Encoded_arguments = encode_custom_arguments(Arguments), Payload = {some, gleam@json:object( [{<<"functionDeclaration"/utf8>>, gleam@json:string(Function_declaration)}, {<<"objectId"/utf8>>, protocol@runtime:encode__remote_object_id(Object_id)}, {<<"arguments"/utf8>>, Encoded_arguments}, {<<"returnByValue"/utf8>>, gleam@json:bool(true)}] )}, gleam@result:'try'( Callback(<<"Runtime.callFunctionOn"/utf8>>, Payload), fun(Result) -> gleam@result:'try'( begin _pipe = protocol@runtime:decode__call_function_on_response( Result ), gleam@result:replace_error(_pipe, protocol_error) end, fun(Decoded_response) -> case Decoded_response of {call_function_on_response, _, {some, Exception}} -> {error, {runtime_exception, erlang:element(3, Exception), erlang:element(4, Exception), erlang:element(5, Exception)}}; {call_function_on_response, {remote_object, _, _, _, {some, Value}, _, _, _}, none} -> _pipe@1 = Value_decoder(Value), gleam@result:replace_error(_pipe@1, protocol_error); _ -> {error, not_found_error} end end ) end ). -spec to_value( page(), protocol@runtime:remote_object_id(), fun((gleam@dynamic:dynamic_()) -> {ok, XKJ} | {error, any()}) ) -> {ok, XKJ} | {error, chrome:request_error()}. to_value(Page, Remote_object_id, Decoder) -> Declaration = <<"function to_value(){ return JSON.stringify(this) }"/utf8>>, call_custom_function_on( page_caller(Page), Declaration, Remote_object_id, [], Decoder ). -spec get_attribute(page(), protocol@runtime:remote_object_id(), binary()) -> {ok, binary()} | {error, chrome:request_error()}. get_attribute(Page, Item, Attribute_name) -> Declaration = <<"function get_arg(attribute_name) { return this.getAttribute(attribute_name) } "/utf8>>, call_custom_function_on( page_caller(Page), Declaration, Item, [{string_arg, Attribute_name}], fun gleam@dynamic:string/1 ). -spec get_property( page(), protocol@runtime:remote_object_id(), binary(), fun((gleam@dynamic:dynamic_()) -> {ok, XKP} | {error, any()}) ) -> {ok, XKP} | {error, chrome:request_error()}. get_property(Page, Item, Property_name, Property_decoder) -> Declaration = <<"function get_prop(property_name) { return this[property_name] } "/utf8>>, call_custom_function_on( page_caller(Page), Declaration, Item, [{string_arg, Property_name}], Property_decoder ). -spec get_text(page(), protocol@runtime:remote_object_id()) -> {ok, binary()} | {error, chrome:request_error()}. get_text(Page, Item) -> get_property(Page, Item, <<"innerText"/utf8>>, fun gleam@dynamic:string/1). -spec get_inner_html(page(), protocol@runtime:remote_object_id()) -> {ok, binary()} | {error, chrome:request_error()}. get_inner_html(Page, Item) -> get_property(Page, Item, <<"innerHTML"/utf8>>, fun gleam@dynamic:string/1). -spec get_outer_html(page(), protocol@runtime:remote_object_id()) -> {ok, binary()} | {error, chrome:request_error()}. get_outer_html(Page, Item) -> get_property(Page, Item, <<"outerHTML"/utf8>>, fun gleam@dynamic:string/1). -spec poll(fun(() -> {ok, XAS} | {error, XAT}), integer()) -> {ok, XAS} | {error, XAT}. poll(Callback, Remaining_time) -> case Callback() of {ok, A} -> {ok, A}; {error, B} when Remaining_time =< 0 -> {error, B}; {error, _} -> gleam_erlang_ffi:sleep(100), poll(Callback, Remaining_time - 100) end. -spec await_selector(page(), binary()) -> {ok, protocol@runtime:remote_object_id()} | {error, chrome:request_error()}. await_selector(Page, Selector) -> Polly = fun() -> _pipe = eval( Page, <<<<"window.document.querySelector(\""/utf8, Selector/binary>>/binary, "\")"/utf8>> ), handle_object_id_response(_pipe) end, poll(Polly, erlang:element(3, Page)). -spec create_page( gleam@erlang@process:subject(chrome:message()), binary(), integer() ) -> {ok, page()} | {error, chrome:request_error()}. create_page(Browser, Html, Time_out) -> gleam@result:'try'( open(Browser, <<"about:blank"/utf8>>, Time_out), fun(Created_page) -> gleam@result:'try'( await_selector(Created_page, <<"body"/utf8>>), fun(_) -> Payload = <<<<"window.document.open(); window.document.write(`"/utf8, Html/binary>>/binary, "`); window.document.close(); "/utf8>>, gleam@result:'try'( eval(Created_page, Payload), fun(_) -> {ok, Created_page} end ) end ) end ).