belfius-balance/source/app.d

398 lines
11 KiB
D

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);
}