import std.stdio; import std.conv; import std.algorithm; import std.range; import core.time : msecs; import core.sys.linux.unistd; import vibe.d; import vibe.data.json; import vibe.textfilter.urlencode; class ApiRequest { } struct ApiResponse { ResponseSet[] responseSets; bool pendingResponseSets; } struct ResponseSet { string applicationId; int requestCounter; Json[] responses; } class OpenSession : ApiRequest { string applicationType; string locale; string[string] attributes; this(string applicationType, string locale, string[string] attributes) { this.applicationType = applicationType; this.locale = locale; this.attributes = attributes; } } class StartFlow : ApiRequest { string applicationType; string applicationId; string flowName; string[string] attributes; string[string] privateAttributes; this(string applicationType, string applicationId, string flowName, string[string] attributes, string[string] privateAttributes) { this.applicationType = applicationType; this.applicationId = applicationId; this.flowName = flowName; this.attributes = attributes; this.privateAttributes = privateAttributes; } } class WidgetEvents : ApiRequest { string applicationId; Json[] widgetEventInformations; this(string applicationId, WidgetEventInformation[] events) { this.applicationId = applicationId; foreach(event; events) { widgetEventInformations ~= event.serializeToJson; } } } struct WidgetEventInformation { string widgetId; string eventType; string widgetType; string[string] attributes; } class TechnicalRequest : ApiRequest { string requestType; this(string requestType) { this.requestType = requestType; } } class ApiMessage { string executionMode; string protocolVersion; Json[string][] requests; this(string executionMode) { this.executionMode = executionMode; this.protocolVersion = "2.1"; Json[string] requestArray; this.requests = [requestArray]; } void addRequest(T : ApiRequest)(string name, T request) { requests[0][name] = request.serializeToJson; } } class SessionApiMessage : ApiMessage { string sessionId; string applicationId; string requestCounter; this(string executionMode, string sessionId, string applicationId, int requestCounter) { super(executionMode); this.sessionId = sessionId; this.applicationId = applicationId; this.requestCounter = requestCounter.to!string; } } class ApiClient { enum string Endpoint = "https://m.belfius.be/F2CRenderingDexiaToClient/GEPARendering/machineIdentifier=s5/"; private string _locale; private string _sessionId; private int _requestCounter; private bool _busy; this(string locale) { _locale = locale; } void connect() { auto openSessionRequest = new OpenSession("yui3a", _locale, ["application": "Container", "P": "P055"]); auto response = sendRequest("sequential", openSessionRequest); _sessionId = response.responseSets[0].responses[0]["SessionOpened"][0]["sessionId"].get!string; _requestCounter = 1; auto startFlowRequest = new StartFlow("yui3a", "Container", "gef0.gef1.gemd.Flow_MobileApplication.diamlflow", ["deviceType": "simple", "language": "2", "smsType": "otpUrl"], null); response = sendRequest("sequential", startFlowRequest); } void login1(string cardNumber, string phoneZone, string phoneSubscriber) { auto widgetEvent = createClickImageEvent("Container@reuse_Screen@img_Operations"); sendRequest("sequential", new WidgetEvents("Container", [widgetEvent])); widgetEvent = createChangeInputValueEvent("Container@reuse_flowAuthentication@inp_PanNumbersimple", cardNumber[0 .. 4]); sendRequest("sequential", new WidgetEvents("Container", [widgetEvent])); widgetEvent = createChangeInputValueEvent("Container@reuse_flowAuthentication@inp_PanNumbersimple", cardNumber); sendRequest("sequential", new WidgetEvents("Container", [widgetEvent])); widgetEvent = createChangeInputValueEvent("Container@reuse_flowAuthentication@inp_PhoneZone", phoneZone); sendRequest("sequential", new WidgetEvents("Container", [widgetEvent])); widgetEvent = createChangeInputValueEvent("Container@reuse_flowAuthentication@inp_PhoneSubscriber", phoneSubscriber); sendRequest("sequential", new WidgetEvents("Container", [widgetEvent])); widgetEvent = createClickButtonEvent("Container@reuse_flowAuthentication@btn_Identification"); sendRequest("sequential", new WidgetEvents("Container", [widgetEvent])); } void login2(string otp, string password) { auto widgetEvent = createClickImageEvent("Container@reuse_Screen@img_Operations"); sendRequest("sequential", new WidgetEvents("Container", [widgetEvent])); widgetEvent = createChangeInputValueEvent("Container@reuse_flowAuthentication@inp_otp", otp); sendRequest("sequential", new WidgetEvents("Container", [widgetEvent])); widgetEvent = createChangeInputValueEvent("Container@reuse_flowAuthentication@inp_pw", password); sendRequest("sequential", new WidgetEvents("Container", [widgetEvent])); widgetEvent = createClickButtonEvent("Container@reuse_flowAuthentication@btn_Identification"); sendRequest("sequential", new WidgetEvents("Container", [widgetEvent])); runTask(&heartbeatLoop); } string getAccountAvailable() { while(_busy) { sleep(10.msecs); } _busy = true; auto clickImageEvent = createClickImageEvent("Container@reuse_Screen@img_Account"); auto widgetEventsRequest = new WidgetEvents("Container", [clickImageEvent]); sendRequest("sequential", widgetEventsRequest); auto repeaterEvent = createChangeRepeaterValueEvent("Container@reuse_flowApplication@repeater_main", "1"); auto labelValueEvent = createChangeLabelValueEvent("Container@reuse_flowApplication@lbl_AccountLabel", "1", "INFO"); auto clickLabelEvent = createClickLabelEvent("Container@reuse_flowApplication@lbl_AccountLabel"); widgetEventsRequest = new WidgetEvents("Container", [repeaterEvent, labelValueEvent, repeaterEvent, clickLabelEvent]); auto response = sendRequest("sequential", widgetEventsRequest); goHome(); _busy = false; foreach(widgetUpdate; response.responseSets[0].responses[0]["ScreenUpdate"]) { if(widgetUpdate["widgetId"] == "Container@reuse_flowApplication@repeater_1292926459910") { string available = widgetUpdate["properties"][0]["contentList"]["dynamiccontent"][0]["repeatedPane_1292926472020"]["VerticalPane_1290438166429"]["gridPane_main"]["gridRow_available"]["gridItem_available_value"]["lbl_valueavailable"]["htmlText"].get!string; return available.map!(c => c == '.' ? ',' : (c == ',' ? '.' : c)).map!(to!char).array; } } return null; } string getCardAvailable() { while(_busy) { sleep(10.msecs); } _busy = true; auto clickImageEvent = createClickImageEvent("Container@reuse_Screen@img_card"); auto widgetEventsRequest = new WidgetEvents("Container", [clickImageEvent]); sendRequest("sequential", widgetEventsRequest); auto repeaterEvent = createChangeRepeaterValueEvent("Container@reuse_flowApplication@repeater_cards", "1"); auto labelValueEvent = createChangeLabelValueEvent("Container@reuse_flowApplication@lbl_cardNumber", "1", "INFO"); auto clickLabelEvent = createClickLabelEvent("Container@reuse_flowApplication@lbl_cardNumber"); widgetEventsRequest = new WidgetEvents("Container", [repeaterEvent, labelValueEvent, repeaterEvent, clickLabelEvent]); auto response = sendRequest("sequential", widgetEventsRequest); goHome(); _busy = false; foreach(widgetUpdate; response.responseSets[0].responses[0]["ScreenUpdate"]) { if(widgetUpdate["widgetId"] == "Container@reuse_flowApplication@reuse_CreditCardCommonData@lbl_available_amount_value") { string available = widgetUpdate["properties"][1]["htmlText"].get!string; return available.map!(c => c == '.' ? ',' : (c == ',' ? '.' : c)).map!(to!char).array; } } return null; } private void goHome() { auto clickImageEvent = createClickImageEvent("Container@img_Home"); auto widgetEventsRequest = new WidgetEvents("Container", [clickImageEvent]); sendRequest("sequential", widgetEventsRequest); } WidgetEventInformation createClickImageEvent(string widgetId) { return WidgetEventInformation(widgetId, "clicked", "Image", null); } WidgetEventInformation createClickButtonEvent(string widgetId) { return WidgetEventInformation(widgetId, "clicked", "ActionButton", null); } WidgetEventInformation createClickLabelEvent(string widgetId) { return WidgetEventInformation(widgetId, "linkClicked", "Label", null); } WidgetEventInformation createChangeInputValueEvent(string widgetId, string value) { return WidgetEventInformation(widgetId, "valueChanged", "Input", ["text": value]); } WidgetEventInformation createChangeRepeaterValueEvent(string widgetId, string activeElement) { return WidgetEventInformation(widgetId, "valueChanged", "Repeater", ["activeElement": activeElement]); } WidgetEventInformation createChangeLabelValueEvent(string widgetId, string currentLink, string currentTarget) { return WidgetEventInformation(widgetId, "valueChanged", "Label", ["currentLink": currentLink, "currentTarget": currentTarget]); } private void heartbeatLoop() { while(_busy) { sleep(10.msecs); } _busy = true; auto technicalRequest = new TechnicalRequest("heartbeat"); sendRequest("sequential", technicalRequest); _busy = false; sleep(10000.msecs); //TODO: Add a random action to prevent timeout heartbeatLoop(); } private ApiResponse sendRequest(T : ApiRequest)(string executionMode, T request) { string requestString; if(_sessionId !is null) { auto message = new SessionApiMessage(executionMode, _sessionId, "Container", ++_requestCounter); message.addRequest(T.stringof, request); requestString = message.serializeToJsonString; } else { auto message = new ApiMessage(executionMode); message.addRequest(T.stringof, request); requestString = message.serializeToJsonString; } auto httpResponse = requestHTTP(Endpoint, (scope request) { request.method = HTTPMethod.POST; request.headers["Accept"] = "*/*"; request.headers["Content-Type"] = "application/x-www-form-urlencoded"; request.headers["User-Agent"] = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.76 Safari/537.36"; request.writeBody(cast(ubyte[])("request=" ~ requestString.urlEncode)); } ); auto response = httpResponse.bodyReader.readAllUTF8; httpResponse.dropBody(); return response.parseJsonString.deserializeJson!ApiResponse; } } shared static this() { auto apiClient = new ApiClient("nl_BE"); apiClient.connect(); auto config = File("config").byLine.map!(l => l.idup).array; auto cardNumber = config[0]; auto phoneZone = config[1]; auto phoneSubscriber = config[2]; apiClient.login1(cardNumber, phoneZone, phoneSubscriber); write("OTP: "); auto otp = readln()[0 .. $-1]; auto password = fromStringz(getpass("Password: ")).idup; apiClient.login2(otp, password); auto router = new URLRouter; router.get("/account/available", delegate(HTTPServerRequest, HTTPServerResponse response) { response.writeBody(apiClient.getAccountAvailable()); }); router.get("/card/available", delegate(HTTPServerRequest, HTTPServerResponse response) { response.writeBody(apiClient.getCardAvailable()); }); auto settings = new HTTPServerSettings; settings.sessionStore = new MemorySessionStore; settings.port = 8080; settings.bindAddresses = ["::1", "127.0.0.1"]; listenHTTP(settings, router); }