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.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +69 -0
  3. package/index.js +216 -0
  4. 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
+ }