flowerpicker 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +69 -0
- package/index.js +216 -0
- package/package.json +13 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Boop Dog
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# FlowerPicker
|
|
2
|
+
This project is for getting information from a rhythm game website.
|
|
3
|
+
|
|
4
|
+
> [!NOTE]
|
|
5
|
+
> This package was made in mind for the non US version of the server/dashboard. If you try to use this package for the US server, you may run into issues!
|
|
6
|
+
|
|
7
|
+
## Getting Started
|
|
8
|
+
Currently only Jubeat is supported as far as games go.
|
|
9
|
+
|
|
10
|
+
Below is some example code with their explanations to get started:
|
|
11
|
+
```js
|
|
12
|
+
import FlowerPicker from "../FlowerPicker/index.js";
|
|
13
|
+
|
|
14
|
+
// The constructor has 2 parameters: The url to the server dashboard, and your cookie to said dashboard
|
|
15
|
+
const SESSION_COOKIE = "abc123";
|
|
16
|
+
const picker = new FlowerPicker('https://server.link', SESSION_COOKIE);
|
|
17
|
+
|
|
18
|
+
// This verifies the validity of your session cookie and populates the info necessary for the package to function
|
|
19
|
+
// (Besides game IDs, it also gets the info mentioned under the supported data)
|
|
20
|
+
await picker.setup();
|
|
21
|
+
|
|
22
|
+
// This is an example of how to populate a score log for a game. Currently only Jubeat is supported
|
|
23
|
+
await picker.setupJubeatScoreLog();
|
|
24
|
+
|
|
25
|
+
// This is an example of how to export the score log from the package to a text file
|
|
26
|
+
// Temporarily incredibly scuffed as I get the basic functionality up and running
|
|
27
|
+
fs.writeFileSync(`./data/data-${Date.now()}-${picker._gameScoreLogs.jubeat.length}.json`, JSON.stringify(picker._gameScoreLogs.jubeat, null, 2) , 'utf-8');
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Supported data (Accessible via the package)
|
|
31
|
+
The following account information is accessible via this package:
|
|
32
|
+
* Account ID
|
|
33
|
+
* Profile ID
|
|
34
|
+
* Account Petal Wallet IDs ( [arcadeName, walletID, walletBalance] )
|
|
35
|
+
* Profile Info
|
|
36
|
+
* Name
|
|
37
|
+
* Email
|
|
38
|
+
* Home arcade
|
|
39
|
+
* Date of your first play
|
|
40
|
+
|
|
41
|
+
The following games may not be updated by me, PRs may be reviewed and accepted
|
|
42
|
+
|
|
43
|
+
| Game | Profile Information[^1] | Score Log | Game Specific Data |
|
|
44
|
+
| :------------------------------ | :---------------------: | :-------: | :----------------: |
|
|
45
|
+
| beatmania IIDX | ❌ | ❌ | ❌ |
|
|
46
|
+
| DanceDanceRevolution | ❌ | ❌ | ❌ |
|
|
47
|
+
| GITADORA | ❌ | ❌ | ❌ |
|
|
48
|
+
| Jubeat | ⚠️[^2] | ✅ | ❌[^3] |
|
|
49
|
+
| NOSTALGIA | ❌ | ❌ | ❌ |
|
|
50
|
+
| pop'n music | ❌ | ❌ | ❌ |
|
|
51
|
+
| REFLEC BEAT | ❌ | ❌ | ❌ |
|
|
52
|
+
| Sound Voltex | ❌ | ❌ | ❌ |
|
|
53
|
+
| PASELI Charging Machine (Soon™️) | ❌ | ❌ | ❌ |
|
|
54
|
+
|
|
55
|
+
[^1]: Profile information includes profile id(s) on the site, game display name, and unlock statuses
|
|
56
|
+
|
|
57
|
+
[^2]: Reading the status of the unlock are not currently implemented
|
|
58
|
+
|
|
59
|
+
[^3]: The Jubility table may or may not be done at a later time
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
The following games will most likely never be supported by me
|
|
63
|
+
* Beatstream
|
|
64
|
+
* DanceEvolution ARCADE
|
|
65
|
+
* DANCERUSH
|
|
66
|
+
* Future TomTom
|
|
67
|
+
* GuitarFreaks & DrumMania
|
|
68
|
+
* HELLO! POP'N MUSIC
|
|
69
|
+
* MÚSECA
|
package/index.js
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import * as cheerio from "cheerio";
|
|
2
|
+
|
|
3
|
+
class FlowerPicker {
|
|
4
|
+
constructor(baseURL, flowerSessionIDValue) {
|
|
5
|
+
this._baseURL = baseURL;
|
|
6
|
+
this._cookie = `flower_session=${flowerSessionIDValue}`;
|
|
7
|
+
|
|
8
|
+
// Account IDs
|
|
9
|
+
this._accountIDs = {
|
|
10
|
+
profileID: null,
|
|
11
|
+
accountID: null,
|
|
12
|
+
petalWalletIDs: null, // [arcadeName, walletID, walletBalance]
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Account Info
|
|
16
|
+
this._accountInfo = {
|
|
17
|
+
profileName: null,
|
|
18
|
+
email: null,
|
|
19
|
+
homeArcade: null,
|
|
20
|
+
firstPlayedDateString: null,
|
|
21
|
+
}
|
|
22
|
+
// Game Info
|
|
23
|
+
this._gameIDs = {
|
|
24
|
+
iidxAccountID: null,
|
|
25
|
+
ddrAccountID: null,
|
|
26
|
+
jubeatAccountID: null,
|
|
27
|
+
nostalgiaAccountID: null,
|
|
28
|
+
popnAccountID: null,
|
|
29
|
+
rbAccountID: null,
|
|
30
|
+
sdvxAccountID: null
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
this._gameScoreLogs = {
|
|
34
|
+
jubeat: [],
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// TODO: This maybe? (Object of IDs)
|
|
38
|
+
this.gameIDsNEW = new Map();
|
|
39
|
+
// TODO: Also this but actually do this one (Object of [Arcade, ID])
|
|
40
|
+
this.arcades = new Map();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async setup() {
|
|
44
|
+
await this._accountInfoSetup();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async setupJubeatScoreLog() {
|
|
48
|
+
await this._fetchWithCookie(`${this._baseURL}/game/jubeat/profile/${this._gameIDs.jubeatAccountID}`)
|
|
49
|
+
.then((response) => response.text())
|
|
50
|
+
.then(async (text) => {
|
|
51
|
+
const $ = cheerio.load(text);
|
|
52
|
+
/* FIXME: Not all accounts will always have a page selector (ul.pagination) above and below the scores as they have not played enough
|
|
53
|
+
// Watch out for the player news pagination as the pagination buttons are reused there
|
|
54
|
+
*/
|
|
55
|
+
const pages = parseInt($('div.col-lg-8.col-lg-pull-4 > div.text-center > ul.pagination > li:nth-last-child(2) > a').first().text());
|
|
56
|
+
|
|
57
|
+
// FIXME: Toggle these 2 lines for normal loop
|
|
58
|
+
for (let i = pages; i > 0; i--) {
|
|
59
|
+
console.log("On page " + i)
|
|
60
|
+
await this._fetchWithCookie(`${this._baseURL}/game/jubeat/profile/${this._gameIDs.jubeatAccountID}?page=${i}`)
|
|
61
|
+
// await this._fetchWithCookie(`${this._baseURL}/game/jubeat/profile/${this._gameIDs.jubeatAccountID}?page=22`)
|
|
62
|
+
.then((response) => response.text())
|
|
63
|
+
.then((text) => {
|
|
64
|
+
const $$ = cheerio.load(text);
|
|
65
|
+
const scores = $$('tbody').children('tr.accordion-toggle');
|
|
66
|
+
const scoresDiv = $$('tbody').children('tr:not(.accordion-toggle)');
|
|
67
|
+
|
|
68
|
+
let scoreArray = [];
|
|
69
|
+
scores.each((j, element) => {
|
|
70
|
+
const songTr = $(element); // If it has an i element inside the first td, then "⭐ This is the player's hiscore for this chart!"
|
|
71
|
+
const songTrDiv = $(scoresDiv).eq(j).find('td > div > div');
|
|
72
|
+
let playID = this._trimToNumber(songTr.attr('data-target'));
|
|
73
|
+
let songTitle = songTr.find('td > a > b').text();
|
|
74
|
+
let songID = songTr.find('td > a').attr('href').split('/')[7];
|
|
75
|
+
let songDifficultyID = songTr.find('td > a').attr('href').split('/').pop();
|
|
76
|
+
let songIsHardPlay = songTr.children('td').eq(1).find('div.pull-right').length ? true : false;
|
|
77
|
+
let songChart = songTr.children('td').eq(2).text().trim();
|
|
78
|
+
let songLetterScore = songTr.children('td').eq(3).find('div > strong').text().trim();
|
|
79
|
+
let songNumberScore = songTr.children('td').eq(3).find('small').text().trim();
|
|
80
|
+
let songMusicRate = songTr.children('td').eq(4).find('div > strong').text().trim();
|
|
81
|
+
let songJubility = songTr.children('td').eq(4).find('small').text().trim();
|
|
82
|
+
let songClearStatus = songTr.children('td').eq(5).find('strong').text().trim();
|
|
83
|
+
let songMaxCombo = songTr.children('td').eq(6).find('strong').text().trim();
|
|
84
|
+
let songTimestampString = songTr.children('td').eq(7).find('small').text().trim();
|
|
85
|
+
|
|
86
|
+
// Only obtainable from other div
|
|
87
|
+
let arcadePlayedAtString = $(songTrDiv).find("div:contains('Played at')").text().replace('Played at', '');
|
|
88
|
+
let arcadePlayedAtID = $(songTrDiv).find("div:contains('Played at') > a").attr('href')?.split('/').pop();
|
|
89
|
+
let machinePlayedWithString = $(songTrDiv).find("div:contains('Played with')").text().replace('Played with', '');;
|
|
90
|
+
let scoreData = {
|
|
91
|
+
perfects: this._trimToNumber($(songTrDiv).find("div:contains('Perfects')").text()),
|
|
92
|
+
greats: this._trimToNumber($(songTrDiv).find("div:contains('Greats')").text()),
|
|
93
|
+
goods: this._trimToNumber($(songTrDiv).find("div:contains('Goods')").text()),
|
|
94
|
+
poors: this._trimToNumber($(songTrDiv).find("div:contains('Poors')").text()),
|
|
95
|
+
misses: this._trimToNumber($(songTrDiv).find("div:contains('Misses')").text()),
|
|
96
|
+
};
|
|
97
|
+
let onPage = i;
|
|
98
|
+
|
|
99
|
+
scoreArray.push({
|
|
100
|
+
playID, songTitle, songID, songDifficultyID, songIsHardPlay, songChart,
|
|
101
|
+
songLetterScore, songNumberScore, songMusicRate, songJubility,
|
|
102
|
+
songClearStatus, songMaxCombo, songTimestampString,
|
|
103
|
+
arcadePlayedAtString, arcadePlayedAtID, machinePlayedWithString, scoreData, onPage
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
scoreArray.reverse();
|
|
108
|
+
this._gameScoreLogs.jubeat.push(...scoreArray); // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
|
|
109
|
+
})
|
|
110
|
+
// FIXME: This one as well
|
|
111
|
+
}
|
|
112
|
+
console.log(this._gameScoreLogs.jubeat.length)
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async _fetchWithCookie(url) {
|
|
117
|
+
return fetch(url, {
|
|
118
|
+
headers: {
|
|
119
|
+
cookie: this._cookie,
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async _accountInfoSetup() {
|
|
125
|
+
const userPage = await this._verifySessionIDValidityReturnUserBody();
|
|
126
|
+
const $ = cheerio.load(userPage)
|
|
127
|
+
|
|
128
|
+
// Account IDs
|
|
129
|
+
// Gets 1234 from https://base.url/account/1234/petals
|
|
130
|
+
this._accountIDs.profileID = $("a[title='Profile & Settings']").attr('href').split('/').pop();
|
|
131
|
+
this._accountIDs.accountID = $('.fa-money').parent().attr('href').split('/')[4];
|
|
132
|
+
this._accountIDs.petalWalletIDs = await this._getPetalWalletsInfo();
|
|
133
|
+
|
|
134
|
+
// Account Info
|
|
135
|
+
this._accountInfo.profileName = $("a[title='Profile & Settings'] > b").text();
|
|
136
|
+
this._accountInfo.email = $("tbody > tr:nth-child(2) > td.text-right").text();
|
|
137
|
+
this._accountInfo.homeArcade = $("tbody > tr:nth-child(3) > td.text-right").text();
|
|
138
|
+
this._accountInfo.firstPlayedDateString = $("tbody > tr:nth-child(4) > td.text-right").text();
|
|
139
|
+
|
|
140
|
+
// Game IDs
|
|
141
|
+
this._gameIDs.iidxAccountID = $("a[title='beatmania IIDX']").attr('href')?.split('/').pop();
|
|
142
|
+
this._gameIDs.ddrAccountID = $("a[title='DanceDanceRevolution']").attr('href')?.split('/').pop();
|
|
143
|
+
this._gameIDs.jubeatAccountID = $("a[title='Jubeat']").attr('href')?.split('/').pop();
|
|
144
|
+
this._gameIDs.nostalgiaAccountID = $("a[title='NOSTALGIA']").attr('href')?.split('/').pop();
|
|
145
|
+
this._gameIDs.popnAccountID = $(`a[title="pop'n music"]`).attr('href')?.split('/').pop();
|
|
146
|
+
this._gameIDs.rbAccountID = $("a[title='REFLEC BEAT']").attr('href')?.split('/').pop();
|
|
147
|
+
this._gameIDs.sdvxAccountID = $("a[title='Sound Voltex']").attr('href')?.split('/').pop();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// TODO: Remove
|
|
151
|
+
async __logData() {
|
|
152
|
+
console.log('\nAccount IDs:');
|
|
153
|
+
console.log(` profileID: ${this._accountIDs.profileID}`);
|
|
154
|
+
console.log(` accountID: ${this._accountIDs.accountID}`);
|
|
155
|
+
console.log(` petalWalletIDs: ${this._accountIDs.petalWalletIDs}`);
|
|
156
|
+
|
|
157
|
+
console.log('\nAccount Info:');
|
|
158
|
+
console.log(` profileName: ${this._accountInfo.profileName}`);
|
|
159
|
+
console.log(` email: ${this._accountInfo.email}`);
|
|
160
|
+
console.log(` homeArcade: ${this._accountInfo.homeArcade}`);
|
|
161
|
+
console.log(` firstPlayedDateString: ${this._accountInfo.firstPlayedDateString}`);
|
|
162
|
+
|
|
163
|
+
console.log('\nGame IDs:')
|
|
164
|
+
console.log(` IIDX Account ID: ${this._gameIDs.iidxAccountID}`)
|
|
165
|
+
console.log(` DDR Account ID: ${this._gameIDs.ddrAccountID}`)
|
|
166
|
+
console.log(` Jubeat Account ID: ${this._gameIDs.jubeatAccountID}`)
|
|
167
|
+
console.log(` Nostalgia Account ID: ${this._gameIDs.nostalgiaAccountID}`)
|
|
168
|
+
console.log(` Pop'n Account ID: ${this._gameIDs.popnAccountID}`)
|
|
169
|
+
console.log(` RB Account ID: ${this._gameIDs.rbAccountID}`)
|
|
170
|
+
console.log(` SDVX Account ID: ${this._gameIDs.sdvxAccountID}`)
|
|
171
|
+
|
|
172
|
+
console.log('\nGame Scores:')
|
|
173
|
+
console.log(` Jubeat: ${this._gameScoreLogs.jubeat}`)
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Verifies whether a given session is valid by making a request
|
|
177
|
+
* @returns Response body text (If an error isn't fired first)
|
|
178
|
+
*/
|
|
179
|
+
async _verifySessionIDValidityReturnUserBody() {
|
|
180
|
+
return await this._fetchWithCookie(`${this._baseURL}/user`)
|
|
181
|
+
.then((response) => {
|
|
182
|
+
// Redirection means an invalid session ID
|
|
183
|
+
if (response.redirected) {
|
|
184
|
+
throw new Error("The Session ID is not valid.");
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
return response.text();
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async _getPetalWalletsInfo() {
|
|
192
|
+
return await this._fetchWithCookie(`${this._baseURL}/account/${this._accountIDs.accountID}/petals`)
|
|
193
|
+
.then((response) => response.text())
|
|
194
|
+
.then((text) => {
|
|
195
|
+
const $ = cheerio.load(text);
|
|
196
|
+
// return $('.list-group-item').attr('href').split('/')[6]; // Works for singular
|
|
197
|
+
const walletIDs = [];
|
|
198
|
+
$('.list-group').children().each((i, element) => {
|
|
199
|
+
const theElement = $(element);
|
|
200
|
+
let arcadeName = theElement.children('.list-group-item-heading').text().trim();
|
|
201
|
+
let walletID = theElement.attr('href')?.split('/').pop();
|
|
202
|
+
let walletBalance = this._trimToNumber(theElement.children('.list-group-item-text').text());
|
|
203
|
+
|
|
204
|
+
walletIDs.push({arcadeName, walletID, walletBalance});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
return walletIDs;
|
|
208
|
+
})
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
_trimToNumber(string) {
|
|
212
|
+
return string.replace(/\D/g, '');
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export default FlowerPicker;
|
package/package.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "flowerpicker",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Data scraper for a certain rhythm game website",
|
|
5
|
+
"author": "BoopDog",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "index.js",
|
|
8
|
+
"module": "index.js",
|
|
9
|
+
"type": "module",
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"cheerio": "^1.0.0-rc.12"
|
|
12
|
+
}
|
|
13
|
+
}
|