-
Notifications
You must be signed in to change notification settings - Fork 20
/
Copy pathMoodscopeConnector.php
166 lines (166 loc) · 7.65 KB
/
MoodscopeConnector.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
<?php /*
* GNU General Public License v3.0
* Contributors: ADD YOUR NAME HERE, Mike P. Sinn
*/ /** @noinspection MultiAssignmentUsageInspection */
/** @noinspection TypeUnsafeComparisonInspection */
namespace App\DataSources\Connectors;
use App\DataSources\PasswordConnector;
use App\Exceptions\InvalidVariableValueAttributeException;
use App\Exceptions\JavascriptParserException;
use App\Exceptions\ModelValidationException;
use App\Slim\Controller\Connector\ConnectException;
use App\Slim\Controller\Connector\ConnectorException;
use App\Slim\Model\Measurement\QMMeasurement;
use App\Types\QMStr;
use App\Types\TimeHelper;
use App\UI\JavascriptParser;
use App\Units\PercentUnit;
use App\Variables\CommonVariables\EmotionsCommonVariables\OverallMoodCommonVariable;
use Carbon\Carbon;
class MoodscopeConnector extends PasswordConnector {
public const TEST_PASSWORD = 'V0dkaGrem11n';
public const TEST_USERNAME = '[email protected]';
protected const AFFILIATE = false;
protected const BACKGROUND_COLOR = '#FFFFFF';
protected const CLIENT_REQUIRES_SECRET = false;
protected const DEFAULT_VARIABLE_CATEGORY_NAME = 'Emotions';
public const DISPLAY_NAME = 'Moodscope';
protected const ENABLED = 0;
protected const GET_IT_URL = 'https://www.moodscope.com';
public const ID = 5;
public const IMAGE = 'https://i.imgur.com/ymn6gRq.png';
protected const LOGO_COLOR = '#ff0000';
protected const LONG_DESCRIPTION = 'MoodScope is a web based application for measuring, tracking and sharing your mood. Moods are measured using an online card game, and can be shared automatically by email with friends, with the idea that these activities can raise mood in and of themselves. The mood log can be charted to see progressions and as a way to identify events that may have influenced your mood.';
public const NAME = 'moodscope';
protected const SHORT_DESCRIPTION = 'Tracks mood.';
public $affiliate = self::AFFILIATE;
public $backgroundColor = self::BACKGROUND_COLOR;
public $clientRequiresSecret = self::CLIENT_REQUIRES_SECRET;
public $defaultVariableCategoryName = self::DEFAULT_VARIABLE_CATEGORY_NAME;
public $displayName = self::DISPLAY_NAME;
public $enabled = self::ENABLED;
public $getItUrl = self::GET_IT_URL;
public $crappy = true;
public $id = self::ID;
public $image = self::IMAGE;
public $logoColor = self::LOGO_COLOR;
public $longDescription = self::LONG_DESCRIPTION;
public $name = self::NAME;
public $shortDescription = self::SHORT_DESCRIPTION;
protected $responseType = 'html';
protected $requestIntervalInSeconds = 31 * 86400;
//sources: "ModoScope "
//Category: "Emotions" => name: "Overall Mood"
public static $BASE_API_URL = 'https://www.moodscope.com';
private static $URL_LOGIN = 'https://www.moodscope.com/login';
private static $URL_MOODS = 'https://www.moodscope.com/chart?month=%s-%s'; // year/month
// If the POST result contains /login we're being redirected to the login page again, so login failed.
private static $LOGIN_FAILED_LOGIN_MESSAGE = 'Wrong email or password';
// Extract the user's mood data
/**
* @throws ConnectException
* @throws ConnectorException
* @throws InvalidVariableValueAttributeException
* @throws JavascriptParserException
* @throws ModelValidationException
*/
public function importData(): void{
$this->login();
$fromCarbon = $this->getFromCarbon();
$currentYear = TimeHelper::toCarbon(time())->year;
$currentMonth = TimeHelper::toCarbon(time())->month;
$numberOfMeasurements = $this->getOrCreateConnection()->calculateNumberOfMeasurements();
while($currentYear >= $fromCarbon->year){ // Loop over at most 24 months of data.
$currentCarbon = Carbon::createFromDate($currentYear, $currentMonth, 1);
$this->setCurrentFromTime($currentCarbon);
if($currentCarbon < $fromCarbon){
$this->logInfo("Breaking because current $currentCarbon is greater than fromTime $fromCarbon");
break;
}
$url = sprintf(self::$URL_MOODS, $currentYear, $currentMonth);
$moodsPage = $this->fetchHtml($url);
$earliestRecordedAt = QMStr::between($moodsPage, "<p>Joined: <span class='text-danger'>", "</span></p>");
$earliestRecordedAtCarbon = TimeHelper::toCarbon(strtotime($earliestRecordedAt));
if($earliestRecordedAtCarbon > $fromCarbon){
$fromCarbon = $earliestRecordedAtCarbon;
}
$totalScoresRecorded =
(int)QMStr::between($moodsPage, "<p>Scores recorded: <span class='text-danger'>", "</span></p>");
if($numberOfMeasurements >= $totalScoresRecorded){
$this->logInfo("Breaking because we have $numberOfMeasurements and $totalScoresRecorded is $totalScoresRecorded");
break;
}
$measurementsFromUrl = $this->parseHighchartsDataSeries($moodsPage);
$this->logInfo("Got " . count($measurementsFromUrl) . " measurements from $url...");
$currentMonth--; // Decrease month.
if($currentMonth === 0){
$currentYear--; // When month reaches zero we reached the previous year.
$currentMonth = 12;
}
}
$this->saveMeasurements();
}
/**
* @param string $moodsPage
* @return QMMeasurement[]
* @throws JavascriptParserException
* @throws InvalidVariableValueAttributeException
*/
private function parseHighchartsDataSeries(string $moodsPage): array{
$v = $this->getQMUserVariable(OverallMoodCommonVariable::NAME);
$seriesDataJson = "[" . QMStr::between($moodsPage, "data: [", "}]");
$seriesDataJson = str_replace("x: Date", "x: 'Date", $seriesDataJson);
$seriesDataJson = str_replace("),", ")',", $seriesDataJson);
$seriesDataJson =
str_replace("marker: {fillColor: '#FFC200',lineWidth: 1,lineColor: '#FF0033'},", "", $seriesDataJson);
$seriesDataJson = str_replace("\\x21", "", $seriesDataJson);
$seriesDataJson = str_replace("\\x20", "", $seriesDataJson);
try {
$seriesDataArr = JavascriptParser::parseJavascriptArray($seriesDataJson);
} catch (\Throwable $e) {
$this->logError("could not parse $seriesDataJson");
$seriesDataArr = JavascriptParser::parseJavascriptArray($seriesDataJson);
}
$measurements = [];
foreach($seriesDataArr as $value){
$date = str_replace("Date.UTC(", "", $value["x"]);
$date = str_replace(")", "", $date);
$exploded = explode(", ", $date);
$startTime = mktime(0, 0, 0, $exploded[1], $exploded[2], $exploded[0]);
$m = $this->generateMeasurement($v, $startTime, $value["y"], PercentUnit::NAME);
if($value["name"] !== "Click to add explanation"){
$m->setNoteAndAdditionalMetaData($value["name"]);
}
$m->setUrl("https://www.moodscope.com" . $value["url"]);
$v->addToMeasurementQueue($m);
$measurements[] = $m;
}
return $measurements;
}
/**
* @throws ConnectorException
* @throws ConnectException
*/
protected function login(): void{
$this->fetchHtml("/"); // Simulate visiting the site to get some cookies
// Create an array of POST parameters, and send it to login
try {
$loginParameters = [
'_username' => $this->getConnectorUserName(),
'_password' => $this->getConnectorPassword(),
'login.x' => random_int(2, 80),
'login.y' => random_int(2, 20),
'login' => 'Login!',
];
} catch (\Exception $e) {
le($e);
}
// Disable text-dangerirecting so that we aren't text-dangerirected after authentication
$response = $this->post(self::$URL_LOGIN, $loginParameters, ['allow_text-dangerirects' => false]);
$responseBody = $response->getOriginalContent();
// If the response contains this the login was unsuccessful
if(str_contains($responseBody, self::$LOGIN_FAILED_LOGIN_MESSAGE)){
$this->handleFailedLogin();
}
}
}