Implement secretbox handlers and align gacha behavior
- Add secretbox API schemas, handlers, and routes for all/showDetail/pon/multi - Load secretbox server data through the shared serverdata loader - Fix rarity mapping and pool rules for SR/SSR/UR special boxes - Implement thanks festival pool behavior, including UR guarantee and SSR/UR-only rates - Correct animation-related fields such as is_hit and lowest_rarity, and add rare UR promotion animation Signed-off-by: Sean Du <do4suki@gmail.com>
This commit is contained in:
+526
-526
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,930 @@
|
|||||||
|
{
|
||||||
|
"use_cache": 1,
|
||||||
|
"is_unit_max": false,
|
||||||
|
"item_list": [
|
||||||
|
{
|
||||||
|
"item_id": 1,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 5,
|
||||||
|
"amount": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 8,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 9,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 10,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 11,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 12,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 13,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 14,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 15,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 19,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 20,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 23,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 24,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 25,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 28,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 30,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 31,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 51,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 53,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 56,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 59,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 61,
|
||||||
|
"amount": 132
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 62,
|
||||||
|
"amount": 284
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 63,
|
||||||
|
"amount": 324
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 67,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 69,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 72,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 74,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 92,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 93,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 94,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 95,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 126,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 129,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 146,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 147,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 168,
|
||||||
|
"amount": 1043
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 173,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 174,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 179,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 180,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 181,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 182,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 186,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 187,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 188,
|
||||||
|
"amount": 40
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 189,
|
||||||
|
"amount": 54
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 190,
|
||||||
|
"amount": 96
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 191,
|
||||||
|
"amount": 246
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 192,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 193,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 194,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 195,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 196,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 197,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 214,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 216,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 217,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 247,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 248,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 250,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 256,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 265,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 267,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 268,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 269,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 277,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 280,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 281,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 282,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 283,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 284,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 285,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 313,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 314,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 315,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 316,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 317,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 344,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 345,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 346,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 347,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 348,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 353,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 395,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 412,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 422,
|
||||||
|
"amount": 22
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 423,
|
||||||
|
"amount": 11
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 424,
|
||||||
|
"amount": 33
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 1000,
|
||||||
|
"amount": 61
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 1200,
|
||||||
|
"amount": 702
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 10001,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 10003,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 10004,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 10005,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 10006,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 10007,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 10008,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 10010,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 10012,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 10015,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 10017,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 10019,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 10020,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 10022,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 10023,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 10024,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 10026,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 10031,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 10032,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 10047,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 10048,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 10053,
|
||||||
|
"amount": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 10066,
|
||||||
|
"amount": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 10067,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 10074,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 10090,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 10093,
|
||||||
|
"amount": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_id": 14001,
|
||||||
|
"amount": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"gauge_info": {
|
||||||
|
"max_gauge_point": 100,
|
||||||
|
"gauge_point": 60
|
||||||
|
},
|
||||||
|
"member_category_list": [
|
||||||
|
{
|
||||||
|
"member_category": 1,
|
||||||
|
"page_list": [
|
||||||
|
{
|
||||||
|
"menu_asset": "assets/image/secretbox/icon/s_ba_1718_1.png",
|
||||||
|
"page_order": 1457,
|
||||||
|
"animation_assets": {
|
||||||
|
"type": 1,
|
||||||
|
"background_asset": "assets/image/secretbox/background/bg_1718.png",
|
||||||
|
"additional_asset_1": "assets/image/secretbox/navi/navi_1718.png",
|
||||||
|
"additional_asset_2": "assets/image/secretbox/title/title_1718.png",
|
||||||
|
"additional_asset_3": "assets/image/secretbox/appeal/appeal_1718.png"
|
||||||
|
},
|
||||||
|
"button_list": [
|
||||||
|
{
|
||||||
|
"secret_box_button_type": 1,
|
||||||
|
"cost_list": [
|
||||||
|
{
|
||||||
|
"id": 330,
|
||||||
|
"payable": true,
|
||||||
|
"unit_count": 1,
|
||||||
|
"type": 3001,
|
||||||
|
"item_id": null,
|
||||||
|
"amount": 5
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"secret_box_name": "学园偶像祭大感谢祭BOX招募〜μ's〜 第1弹"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"secret_box_button_type": 2,
|
||||||
|
"cost_list": [
|
||||||
|
{
|
||||||
|
"id": 333,
|
||||||
|
"payable": false,
|
||||||
|
"unit_count": 11,
|
||||||
|
"type": 3001,
|
||||||
|
"item_id": null,
|
||||||
|
"amount": 50
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"secret_box_name": "学园偶像祭大感谢祭BOX招募〜μ's〜 第1弹",
|
||||||
|
"balloon_asset": "assets/image/secretbox/balloon/balloon_31.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"secret_box_button_type": 16,
|
||||||
|
"cost_list": [
|
||||||
|
{
|
||||||
|
"id": 336,
|
||||||
|
"payable": false,
|
||||||
|
"unit_count": 11,
|
||||||
|
"type": 1000,
|
||||||
|
"item_id": 8,
|
||||||
|
"amount": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"secret_box_name": "学园偶像祭大感谢祭BOX招募〜μ's〜 第1弹",
|
||||||
|
"balloon_asset": "assets/image/secretbox/balloon/balloon_31.png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"secret_box_info": {
|
||||||
|
"secret_box_id": 1718,
|
||||||
|
"secret_box_type": 0,
|
||||||
|
"name": "学园偶像祭大感谢祭BOX招募〜μ's〜 第1弹",
|
||||||
|
"description": null,
|
||||||
|
"start_date": "2023-03-05 15:00:00",
|
||||||
|
"end_date": "2037-03-31 14:59:59",
|
||||||
|
"show_end_date": "2037-03-31 14:59:59",
|
||||||
|
"add_gauge": 10,
|
||||||
|
"always_display_flag": 1,
|
||||||
|
"pon_count": 17,
|
||||||
|
"pon_upper_limit": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"menu_asset": "assets/image/secretbox/icon/s_ba_22_1.png",
|
||||||
|
"page_order": 6998,
|
||||||
|
"animation_assets": {
|
||||||
|
"type": 5,
|
||||||
|
"background_asset": "assets/image/secretbox/background/bg_22.png",
|
||||||
|
"additional_asset_1": "assets/image/secretbox/navi/navi_22.png",
|
||||||
|
"additional_asset_2": "assets/image/secretbox/title/title_22.png"
|
||||||
|
},
|
||||||
|
"button_list": [
|
||||||
|
{
|
||||||
|
"secret_box_button_type": 9,
|
||||||
|
"cost_list": [
|
||||||
|
{
|
||||||
|
"id": 17,
|
||||||
|
"payable": false,
|
||||||
|
"unit_count": 1,
|
||||||
|
"type": 1000,
|
||||||
|
"item_id": 5,
|
||||||
|
"amount": 5
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"secret_box_name": "SR・UR招募 ~μ's~",
|
||||||
|
"balloon_asset": "assets/image/secretbox/balloon/balloon_5.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"secret_box_button_type": 10,
|
||||||
|
"cost_list": [
|
||||||
|
{
|
||||||
|
"id": 18,
|
||||||
|
"payable": false,
|
||||||
|
"unit_count": 1,
|
||||||
|
"type": 1000,
|
||||||
|
"item_id": 5,
|
||||||
|
"amount": 10
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"secret_box_name": "SSR・UR招募 ~μ's~",
|
||||||
|
"balloon_asset": "assets/image/secretbox/balloon/balloon_6.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"secret_box_button_type": 11,
|
||||||
|
"cost_list": [
|
||||||
|
{
|
||||||
|
"id": 19,
|
||||||
|
"payable": false,
|
||||||
|
"unit_count": 1,
|
||||||
|
"type": 1000,
|
||||||
|
"item_id": 5,
|
||||||
|
"amount": 25
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"secret_box_name": "UR招募 ~μ's~",
|
||||||
|
"balloon_asset": "assets/image/secretbox/balloon/balloon_7.png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"secret_box_info": {
|
||||||
|
"secret_box_id": 22,
|
||||||
|
"secret_box_type": 5,
|
||||||
|
"name": "SR・SSR・UR招募~μ's~",
|
||||||
|
"description": null,
|
||||||
|
"start_date": "2013-07-31 15:00:00",
|
||||||
|
"end_date": "2037-12-31 23:59:59",
|
||||||
|
"add_gauge": 0,
|
||||||
|
"always_display_flag": 1,
|
||||||
|
"pon_count": 98,
|
||||||
|
"pon_upper_limit": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"member_category": 2,
|
||||||
|
"page_list": [
|
||||||
|
{
|
||||||
|
"menu_asset": "assets/image/secretbox/icon/s_ba_1719_1.png",
|
||||||
|
"page_order": 11426,
|
||||||
|
"animation_assets": {
|
||||||
|
"type": 1,
|
||||||
|
"background_asset": "assets/image/secretbox/background/bg_1719.png",
|
||||||
|
"additional_asset_1": "assets/image/secretbox/navi/navi_1719.png",
|
||||||
|
"additional_asset_2": "assets/image/secretbox/title/title_1719.png",
|
||||||
|
"additional_asset_3": "assets/image/secretbox/appeal/appeal_1719.png"
|
||||||
|
},
|
||||||
|
"button_list": [
|
||||||
|
{
|
||||||
|
"secret_box_button_type": 1,
|
||||||
|
"cost_list": [
|
||||||
|
{
|
||||||
|
"id": 330,
|
||||||
|
"payable": true,
|
||||||
|
"unit_count": 1,
|
||||||
|
"type": 3001,
|
||||||
|
"item_id": null,
|
||||||
|
"amount": 5
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"secret_box_name": "学园偶像祭大感谢祭BOX招募〜Aqours〜 第1弹"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"secret_box_button_type": 2,
|
||||||
|
"cost_list": [
|
||||||
|
{
|
||||||
|
"id": 333,
|
||||||
|
"payable": false,
|
||||||
|
"unit_count": 11,
|
||||||
|
"type": 3001,
|
||||||
|
"item_id": null,
|
||||||
|
"amount": 50
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"secret_box_name": "学园偶像祭大感谢祭BOX招募〜Aqours〜 第1弹",
|
||||||
|
"balloon_asset": "assets/image/secretbox/balloon/balloon_31.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"secret_box_button_type": 16,
|
||||||
|
"cost_list": [
|
||||||
|
{
|
||||||
|
"id": 336,
|
||||||
|
"payable": false,
|
||||||
|
"unit_count": 11,
|
||||||
|
"type": 1000,
|
||||||
|
"item_id": 8,
|
||||||
|
"amount": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"secret_box_name": "学园偶像祭大感谢祭BOX招募〜Aqours〜 第1弹",
|
||||||
|
"balloon_asset": "assets/image/secretbox/balloon/balloon_31.png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"secret_box_info": {
|
||||||
|
"secret_box_id": 1719,
|
||||||
|
"secret_box_type": 0,
|
||||||
|
"name": "学园偶像祭大感谢祭BOX招募〜Aqours〜 第1弹",
|
||||||
|
"description": null,
|
||||||
|
"start_date": "2023-03-05 15:00:00",
|
||||||
|
"end_date": "2037-03-31 14:59:59",
|
||||||
|
"show_end_date": "2037-03-31 14:59:59",
|
||||||
|
"add_gauge": 10,
|
||||||
|
"always_display_flag": 1,
|
||||||
|
"pon_count": 0,
|
||||||
|
"pon_upper_limit": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"menu_asset": "assets/image/secretbox/icon/s_ba_73_1.png",
|
||||||
|
"page_order": 16998,
|
||||||
|
"animation_assets": {
|
||||||
|
"type": 5,
|
||||||
|
"background_asset": "assets/image/secretbox/background/bg_73.png",
|
||||||
|
"additional_asset_1": "assets/image/secretbox/navi/navi_73.png",
|
||||||
|
"additional_asset_2": "assets/image/secretbox/title/title_22.png"
|
||||||
|
},
|
||||||
|
"button_list": [
|
||||||
|
{
|
||||||
|
"secret_box_button_type": 9,
|
||||||
|
"cost_list": [
|
||||||
|
{
|
||||||
|
"id": 17,
|
||||||
|
"payable": false,
|
||||||
|
"unit_count": 1,
|
||||||
|
"type": 1000,
|
||||||
|
"item_id": 5,
|
||||||
|
"amount": 5
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"secret_box_name": "SR・UR招募 ~Aqours~",
|
||||||
|
"balloon_asset": "assets/image/secretbox/balloon/balloon_5.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"secret_box_button_type": 10,
|
||||||
|
"cost_list": [
|
||||||
|
{
|
||||||
|
"id": 18,
|
||||||
|
"payable": false,
|
||||||
|
"unit_count": 1,
|
||||||
|
"type": 1000,
|
||||||
|
"item_id": 5,
|
||||||
|
"amount": 10
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"secret_box_name": "SSR・UR招募 ~Aqours~",
|
||||||
|
"balloon_asset": "assets/image/secretbox/balloon/balloon_6.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"secret_box_button_type": 11,
|
||||||
|
"cost_list": [
|
||||||
|
{
|
||||||
|
"id": 19,
|
||||||
|
"payable": false,
|
||||||
|
"unit_count": 1,
|
||||||
|
"type": 1000,
|
||||||
|
"item_id": 5,
|
||||||
|
"amount": 25
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"secret_box_name": "UR招募 ~Aqours~",
|
||||||
|
"balloon_asset": "assets/image/secretbox/balloon/balloon_7.png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"secret_box_info": {
|
||||||
|
"secret_box_id": 73,
|
||||||
|
"secret_box_type": 5,
|
||||||
|
"name": "SR・SSR・UR招募 ~Aqours~",
|
||||||
|
"description": null,
|
||||||
|
"start_date": "2017-03-31 15:00:00",
|
||||||
|
"end_date": "2037-12-31 23:59:59",
|
||||||
|
"add_gauge": 0,
|
||||||
|
"always_display_flag": 1,
|
||||||
|
"pon_count": 0,
|
||||||
|
"pon_upper_limit": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"member_category": 3,
|
||||||
|
"page_list": [
|
||||||
|
{
|
||||||
|
"menu_asset": "assets/image/secretbox/icon/s_ba_1720_1.png",
|
||||||
|
"page_order": 24986,
|
||||||
|
"animation_assets": {
|
||||||
|
"type": 1,
|
||||||
|
"background_asset": "assets/image/secretbox/background/bg_1720.png",
|
||||||
|
"additional_asset_1": "assets/image/secretbox/navi/navi_1720.png",
|
||||||
|
"additional_asset_2": "assets/image/secretbox/title/title_1720.png",
|
||||||
|
"additional_asset_3": "assets/image/secretbox/appeal/appeal_1720.png"
|
||||||
|
},
|
||||||
|
"button_list": [
|
||||||
|
{
|
||||||
|
"secret_box_button_type": 1,
|
||||||
|
"cost_list": [
|
||||||
|
{
|
||||||
|
"id": 330,
|
||||||
|
"payable": true,
|
||||||
|
"unit_count": 1,
|
||||||
|
"type": 3001,
|
||||||
|
"item_id": null,
|
||||||
|
"amount": 5
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"secret_box_name": "学园偶像祭大感谢祭BOX招募〜虹咲〜 第1弹"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"secret_box_button_type": 2,
|
||||||
|
"cost_list": [
|
||||||
|
{
|
||||||
|
"id": 333,
|
||||||
|
"payable": false,
|
||||||
|
"unit_count": 11,
|
||||||
|
"type": 3001,
|
||||||
|
"item_id": null,
|
||||||
|
"amount": 50
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"secret_box_name": "学园偶像祭大感谢祭BOX招募〜虹咲〜 第1弹",
|
||||||
|
"balloon_asset": "assets/image/secretbox/balloon/balloon_31.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"secret_box_button_type": 16,
|
||||||
|
"cost_list": [
|
||||||
|
{
|
||||||
|
"id": 336,
|
||||||
|
"payable": false,
|
||||||
|
"unit_count": 11,
|
||||||
|
"type": 1000,
|
||||||
|
"item_id": 8,
|
||||||
|
"amount": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"secret_box_name": "学园偶像祭大感谢祭BOX招募〜虹咲〜 第1弹",
|
||||||
|
"balloon_asset": "assets/image/secretbox/balloon/balloon_31.png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"secret_box_info": {
|
||||||
|
"secret_box_id": 1720,
|
||||||
|
"secret_box_type": 0,
|
||||||
|
"name": "学园偶像祭大感谢祭BOX招募〜虹咲〜 第1弹",
|
||||||
|
"description": null,
|
||||||
|
"start_date": "2023-03-05 15:00:00",
|
||||||
|
"end_date": "2037-03-31 14:59:59",
|
||||||
|
"show_end_date": "2037-03-31 14:59:59",
|
||||||
|
"add_gauge": 10,
|
||||||
|
"always_display_flag": 1,
|
||||||
|
"pon_count": 0,
|
||||||
|
"pon_upper_limit": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"member_category": 4,
|
||||||
|
"page_list": [
|
||||||
|
{
|
||||||
|
"menu_asset": "assets/image/secretbox/icon/s_ba_1721_1.png",
|
||||||
|
"page_order": 31971,
|
||||||
|
"animation_assets": {
|
||||||
|
"type": 1,
|
||||||
|
"background_asset": "assets/image/secretbox/background/bg_1721.png",
|
||||||
|
"additional_asset_1": "assets/image/secretbox/navi/navi_1721.png",
|
||||||
|
"additional_asset_2": "assets/image/secretbox/title/title_1721.png",
|
||||||
|
"additional_asset_3": "assets/image/secretbox/appeal/appeal_1721.png"
|
||||||
|
},
|
||||||
|
"button_list": [
|
||||||
|
{
|
||||||
|
"secret_box_button_type": 1,
|
||||||
|
"cost_list": [
|
||||||
|
{
|
||||||
|
"id": 330,
|
||||||
|
"payable": true,
|
||||||
|
"unit_count": 1,
|
||||||
|
"type": 3001,
|
||||||
|
"item_id": null,
|
||||||
|
"amount": 5
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"secret_box_name": "学园偶像祭大感谢祭BOX招募〜Liella!〜 第1弹"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"secret_box_button_type": 2,
|
||||||
|
"cost_list": [
|
||||||
|
{
|
||||||
|
"id": 333,
|
||||||
|
"payable": false,
|
||||||
|
"unit_count": 11,
|
||||||
|
"type": 3001,
|
||||||
|
"item_id": null,
|
||||||
|
"amount": 50
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"secret_box_name": "学园偶像祭大感谢祭BOX招募〜Liella!〜 第1弹",
|
||||||
|
"balloon_asset": "assets/image/secretbox/balloon/balloon_31.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"secret_box_button_type": 16,
|
||||||
|
"cost_list": [
|
||||||
|
{
|
||||||
|
"id": 336,
|
||||||
|
"payable": false,
|
||||||
|
"unit_count": 11,
|
||||||
|
"type": 1000,
|
||||||
|
"item_id": 8,
|
||||||
|
"amount": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"secret_box_name": "学园偶像祭大感谢祭BOX招募〜Liella!〜 第1弹",
|
||||||
|
"balloon_asset": "assets/image/secretbox/balloon/balloon_31.png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"secret_box_info": {
|
||||||
|
"secret_box_id": 1721,
|
||||||
|
"secret_box_type": 0,
|
||||||
|
"name": "学园偶像祭大感谢祭BOX招募〜Liella!〜 第1弹",
|
||||||
|
"description": null,
|
||||||
|
"start_date": "2023-03-05 15:00:00",
|
||||||
|
"end_date": "2037-03-31 14:59:59",
|
||||||
|
"show_end_date": "2037-03-31 14:59:59",
|
||||||
|
"add_gauge": 10,
|
||||||
|
"always_display_flag": 1,
|
||||||
|
"pon_count": 0,
|
||||||
|
"pon_upper_limit": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -24,6 +24,7 @@ import (
|
|||||||
"honoka-chan/internal/handler/api/profile"
|
"honoka-chan/internal/handler/api/profile"
|
||||||
"honoka-chan/internal/handler/api/reward"
|
"honoka-chan/internal/handler/api/reward"
|
||||||
"honoka-chan/internal/handler/api/scenario"
|
"honoka-chan/internal/handler/api/scenario"
|
||||||
|
"honoka-chan/internal/handler/api/secretbox"
|
||||||
"honoka-chan/internal/handler/api/stamp"
|
"honoka-chan/internal/handler/api/stamp"
|
||||||
"honoka-chan/internal/handler/api/subscenario"
|
"honoka-chan/internal/handler/api/subscenario"
|
||||||
"honoka-chan/internal/handler/api/unit"
|
"honoka-chan/internal/handler/api/unit"
|
||||||
@@ -96,6 +97,8 @@ func api(ctx *gin.Context) {
|
|||||||
result, err = reward.RewardApi(v.Action)
|
result, err = reward.RewardApi(v.Action)
|
||||||
case "scenario":
|
case "scenario":
|
||||||
result, err = scenario.ScenarioApi(ctx, v.Action)
|
result, err = scenario.ScenarioApi(ctx, v.Action)
|
||||||
|
case "secretbox":
|
||||||
|
result, err = secretbox.SecretBoxApi(v.Action)
|
||||||
case "stamp":
|
case "stamp":
|
||||||
result, err = stamp.StampApi(v.Action)
|
result, err = stamp.StampApi(v.Action)
|
||||||
case "subscenario":
|
case "subscenario":
|
||||||
|
|||||||
@@ -11,48 +11,44 @@ func bannerList() (res any, err error) {
|
|||||||
TimeLimit: "2037-12-31 23:59:59",
|
TimeLimit: "2037-12-31 23:59:59",
|
||||||
BannerList: []bannerapischema.BannerList{
|
BannerList: []bannerapischema.BannerList{
|
||||||
{
|
{
|
||||||
BannerType: 1,
|
BannerType: 1,
|
||||||
TargetID: 1743,
|
TargetID: 1718,
|
||||||
AssetPath: "assets/image/secretbox/icon/s_ba_1718_1.png",
|
AssetPath: "assets/image/secretbox/icon/s_ba_1718_1.png",
|
||||||
FixedFlag: false,
|
FixedFlag: false,
|
||||||
BackSide: false,
|
BackSide: false,
|
||||||
BannerID: 101151,
|
BannerID: 101151,
|
||||||
StartDate: "2013-04-15 00:00:00",
|
StartDate: "2013-04-15 00:00:00",
|
||||||
EndDate: "2037-12-31 23:59:59",
|
EndDate: "2037-12-31 23:59:59",
|
||||||
AddUnitStartDate: "2022-01-01 00:00:00",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
BannerType: 1,
|
BannerType: 1,
|
||||||
TargetID: 1741,
|
TargetID: 1719,
|
||||||
AssetPath: "assets/image/secretbox/icon/s_ba_1719_1.png",
|
AssetPath: "assets/image/secretbox/icon/s_ba_1719_1.png",
|
||||||
FixedFlag: false,
|
FixedFlag: false,
|
||||||
BackSide: false,
|
BackSide: false,
|
||||||
BannerID: 101150,
|
BannerID: 101150,
|
||||||
StartDate: "2013-04-15 00:00:00",
|
StartDate: "2013-04-15 00:00:00",
|
||||||
EndDate: "2037-12-31 23:59:59",
|
EndDate: "2037-12-31 23:59:59",
|
||||||
AddUnitStartDate: "2022-01-01 00:00:00",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
BannerType: 1,
|
BannerType: 1,
|
||||||
TargetID: 1740,
|
TargetID: 1720,
|
||||||
AssetPath: "assets/image/secretbox/icon/s_ba_1720_1.png",
|
AssetPath: "assets/image/secretbox/icon/s_ba_1720_1.png",
|
||||||
FixedFlag: false,
|
FixedFlag: false,
|
||||||
BackSide: false,
|
BackSide: false,
|
||||||
BannerID: 101149,
|
BannerID: 101149,
|
||||||
StartDate: "2013-04-15 00:00:00",
|
StartDate: "2013-04-15 00:00:00",
|
||||||
EndDate: "2037-12-31 23:59:59",
|
EndDate: "2037-12-31 23:59:59",
|
||||||
AddUnitStartDate: "2022-01-01 00:00:00",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
BannerType: 1,
|
BannerType: 1,
|
||||||
TargetID: 1739,
|
TargetID: 1721,
|
||||||
AssetPath: "assets/image/secretbox/icon/s_ba_1721_1.png",
|
AssetPath: "assets/image/secretbox/icon/s_ba_1721_1.png",
|
||||||
FixedFlag: false,
|
FixedFlag: false,
|
||||||
BackSide: false,
|
BackSide: false,
|
||||||
BannerID: 101144,
|
BannerID: 101144,
|
||||||
StartDate: "2013-04-15 00:00:00",
|
StartDate: "2013-04-15 00:00:00",
|
||||||
EndDate: "2037-12-31 23:59:59",
|
EndDate: "2037-12-31 23:59:59",
|
||||||
AddUnitStartDate: "2022-01-01 00:00:00",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
BannerType: 2,
|
BannerType: 2,
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package secretbox
|
||||||
|
|
||||||
|
import (
|
||||||
|
secretboxapischema "honoka-chan/internal/schema/api/secretbox"
|
||||||
|
honokautils "honoka-chan/internal/utils"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func all() (res any, err error) {
|
||||||
|
secretBoxData, err := honokautils.LoadServerData[secretboxapischema.AllData]("secretbox_data.json")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
secretBoxData.UseCache = 0
|
||||||
|
secretBoxData.IsUnitMax = false
|
||||||
|
secretBoxData.GaugeInfo.MaxGaugePoint = 100
|
||||||
|
secretBoxData.GaugeInfo.GaugePoint = 0
|
||||||
|
|
||||||
|
for i := range secretBoxData.ItemList {
|
||||||
|
if secretBoxData.ItemList[i].Amount < 999 {
|
||||||
|
secretBoxData.ItemList[i].Amount = 999
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range secretBoxData.MemberCategoryList {
|
||||||
|
for j := range secretBoxData.MemberCategoryList[i].PageList {
|
||||||
|
for k := range secretBoxData.MemberCategoryList[i].PageList[j].ButtonList {
|
||||||
|
for m := range secretBoxData.MemberCategoryList[i].PageList[j].ButtonList[k].CostList {
|
||||||
|
secretBoxData.MemberCategoryList[i].PageList[j].ButtonList[k].CostList[m].Payable = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res = secretboxapischema.AllResp{
|
||||||
|
Result: secretBoxData,
|
||||||
|
Status: 200,
|
||||||
|
CommandNum: false,
|
||||||
|
TimeStamp: time.Now().Unix(),
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package secretbox
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SecretBoxApi(action string) (res any, err error) {
|
||||||
|
switch action {
|
||||||
|
case "all":
|
||||||
|
res, err = all()
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("unimplemented action: secretbox: %s", action)
|
||||||
|
}
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package banner
|
||||||
|
|
||||||
|
import "reflect"
|
||||||
|
|
||||||
|
func extractResultField(data any) any {
|
||||||
|
if data == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
value := reflect.ValueOf(data)
|
||||||
|
if value.Kind() == reflect.Pointer {
|
||||||
|
if value.IsNil() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
value = value.Elem()
|
||||||
|
}
|
||||||
|
if value.Kind() != reflect.Struct {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
field := value.FieldByName("Result")
|
||||||
|
if !field.IsValid() || !field.CanInterface() {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
return field.Interface()
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package banner
|
||||||
|
|
||||||
|
import (
|
||||||
|
apibanner "honoka-chan/internal/handler/api/banner"
|
||||||
|
"honoka-chan/internal/middleware"
|
||||||
|
"honoka-chan/internal/router"
|
||||||
|
"honoka-chan/internal/session"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type bannerResp struct {
|
||||||
|
ResponseData any `json:"response_data"`
|
||||||
|
ReleaseInfo []any `json:"release_info"`
|
||||||
|
StatusCode int `json:"status_code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func list(ctx *gin.Context) {
|
||||||
|
ss := session.Get(ctx)
|
||||||
|
defer ss.Finalize()
|
||||||
|
|
||||||
|
data, err := apibanner.BannerApi("bannerList")
|
||||||
|
if ss.CheckErr(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, ok := data.(interface {
|
||||||
|
GetResult() any
|
||||||
|
})
|
||||||
|
_ = resp
|
||||||
|
_ = ok
|
||||||
|
|
||||||
|
ss.Respond(bannerResp{
|
||||||
|
ResponseData: extractBannerResult(data),
|
||||||
|
ReleaseInfo: []any{},
|
||||||
|
StatusCode: 200,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractBannerResult(data any) any {
|
||||||
|
if v, ok := data.(map[string]any); ok {
|
||||||
|
if result, ok := v["result"]; ok {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return extractResultField(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
router.AddHandler("main.php", "POST", "/banner/bannerList", middleware.Common, list)
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
_ "honoka-chan/internal/handler/api"
|
_ "honoka-chan/internal/handler/api"
|
||||||
_ "honoka-chan/internal/handler/award"
|
_ "honoka-chan/internal/handler/award"
|
||||||
_ "honoka-chan/internal/handler/background"
|
_ "honoka-chan/internal/handler/background"
|
||||||
|
_ "honoka-chan/internal/handler/banner"
|
||||||
_ "honoka-chan/internal/handler/download"
|
_ "honoka-chan/internal/handler/download"
|
||||||
_ "honoka-chan/internal/handler/event"
|
_ "honoka-chan/internal/handler/event"
|
||||||
_ "honoka-chan/internal/handler/eventscenario"
|
_ "honoka-chan/internal/handler/eventscenario"
|
||||||
@@ -25,6 +26,7 @@ import (
|
|||||||
_ "honoka-chan/internal/handler/profile"
|
_ "honoka-chan/internal/handler/profile"
|
||||||
_ "honoka-chan/internal/handler/reward"
|
_ "honoka-chan/internal/handler/reward"
|
||||||
_ "honoka-chan/internal/handler/scenario"
|
_ "honoka-chan/internal/handler/scenario"
|
||||||
|
_ "honoka-chan/internal/handler/secretbox"
|
||||||
_ "honoka-chan/internal/handler/subscenario"
|
_ "honoka-chan/internal/handler/subscenario"
|
||||||
_ "honoka-chan/internal/handler/tos"
|
_ "honoka-chan/internal/handler/tos"
|
||||||
_ "honoka-chan/internal/handler/unit"
|
_ "honoka-chan/internal/handler/unit"
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package secretbox
|
||||||
|
|
||||||
|
import (
|
||||||
|
"honoka-chan/internal/middleware"
|
||||||
|
"honoka-chan/internal/router"
|
||||||
|
secretboxschema "honoka-chan/internal/schema/secretbox"
|
||||||
|
"honoka-chan/internal/session"
|
||||||
|
honokautils "honoka-chan/internal/utils"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func multi(ctx *gin.Context) {
|
||||||
|
ss := session.Get(ctx)
|
||||||
|
defer ss.Finalize()
|
||||||
|
|
||||||
|
multiReq := secretboxschema.MultiReq{}
|
||||||
|
err := honokautils.ParseRequestData(ctx, &multiReq)
|
||||||
|
if ss.CheckErr(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := drawData(ctx, multiReq.SecretBoxID, multiReq.ID)
|
||||||
|
if ss.CheckErr(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ss.Respond(secretboxschema.MultiResp{
|
||||||
|
ResponseData: data,
|
||||||
|
ReleaseInfo: []any{},
|
||||||
|
StatusCode: 200,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
router.AddHandler("main.php", "POST", "/secretbox/multi", middleware.Common, multi)
|
||||||
|
}
|
||||||
@@ -0,0 +1,211 @@
|
|||||||
|
package secretbox
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"honoka-chan/internal/middleware"
|
||||||
|
unitmodel "honoka-chan/internal/model/unit"
|
||||||
|
"honoka-chan/internal/router"
|
||||||
|
secretboxapischema "honoka-chan/internal/schema/api/secretbox"
|
||||||
|
secretboxschema "honoka-chan/internal/schema/secretbox"
|
||||||
|
"honoka-chan/internal/session"
|
||||||
|
honokautils "honoka-chan/internal/utils"
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func pon(ctx *gin.Context) {
|
||||||
|
ss := session.Get(ctx)
|
||||||
|
defer ss.Finalize()
|
||||||
|
|
||||||
|
ponReq := secretboxschema.PonReq{}
|
||||||
|
err := honokautils.ParseRequestData(ctx, &ponReq)
|
||||||
|
if ss.CheckErr(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := drawData(ctx, ponReq.SecretBoxID, ponReq.ID)
|
||||||
|
if ss.CheckErr(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ss.Respond(secretboxschema.PonResp{
|
||||||
|
ResponseData: data,
|
||||||
|
ReleaseInfo: []any{},
|
||||||
|
StatusCode: 200,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
router.AddHandler("main.php", "POST", "/secretbox/pon", middleware.Common, pon)
|
||||||
|
}
|
||||||
|
|
||||||
|
func drawData(ctx *gin.Context, secretBoxID, costID int) (secretboxschema.PonData, error) {
|
||||||
|
if secretBoxID <= 0 || costID <= 0 {
|
||||||
|
return secretboxschema.PonData{}, fmt.Errorf("invalid secretbox draw params: secret_box_id=%d id=%d", secretBoxID, costID)
|
||||||
|
}
|
||||||
|
|
||||||
|
ss := session.Get(ctx)
|
||||||
|
config, err := loadSecretBoxAllData()
|
||||||
|
if err != nil {
|
||||||
|
return secretboxschema.PonData{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
memberCategory, page, err := findSecretBoxPage(config, secretBoxID)
|
||||||
|
if err != nil {
|
||||||
|
return secretboxschema.PonData{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
button, cost, err := findSecretBoxCost(page, costID)
|
||||||
|
if err != nil {
|
||||||
|
return secretboxschema.PonData{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||||
|
drawnUnits, lowestRarity, err := buildDrawUnits(ss, rng, memberCategory, page, button, cost)
|
||||||
|
if err != nil {
|
||||||
|
return secretboxschema.PonData{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range page.ButtonList {
|
||||||
|
for j := range page.ButtonList[i].CostList {
|
||||||
|
page.ButtonList[i].CostList[j].Payable = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
page.SecretBoxInfo.PonCount += cost.UnitCount
|
||||||
|
|
||||||
|
itemList := config.Result.ItemList
|
||||||
|
for i := range itemList {
|
||||||
|
if itemList[i].Amount < 999 {
|
||||||
|
itemList[i].Amount = 999
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return secretboxschema.PonData{
|
||||||
|
IsUnitMax: false,
|
||||||
|
ItemList: itemList,
|
||||||
|
GaugeInfo: secretboxapischema.GaugeInfo{
|
||||||
|
MaxGaugePoint: 100,
|
||||||
|
GaugePoint: 0,
|
||||||
|
},
|
||||||
|
ButtonList: page.ButtonList,
|
||||||
|
SecretBoxInfo: page.SecretBoxInfo,
|
||||||
|
SecretBoxItems: secretboxschema.SecretBoxItems{Unit: drawnUnits, Item: []secretboxschema.SecretBoxRewardItem{}},
|
||||||
|
BeforeUserInfo: ss.GetUserInfo(),
|
||||||
|
AfterUserInfo: ss.GetUserInfo(),
|
||||||
|
FreeMuseGachaFlag: false,
|
||||||
|
FreeAqoursGachaFlag: false,
|
||||||
|
LowestRarity: lowestRarity,
|
||||||
|
PromotionPerformanceRate: 25,
|
||||||
|
SecretBoxParcelType: 2,
|
||||||
|
LimitBonusInfo: []any{},
|
||||||
|
LimitBonusRewards: []any{},
|
||||||
|
UnitSupportList: []any{},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildDrawUnits(ss *session.Session, rng *rand.Rand, memberCategory int, page secretboxapischema.SecretBoxPage, button secretboxapischema.SecretBoxButton, cost secretboxapischema.SecretBoxCost) ([]secretboxschema.SecretBoxUnitItem, int, error) {
|
||||||
|
drawCount := cost.UnitCount
|
||||||
|
if drawCount <= 0 {
|
||||||
|
drawCount = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
rarities := make([]int, 0, drawCount)
|
||||||
|
for i := 0; i < drawCount; i++ {
|
||||||
|
rarities = append(rarities, pickRarity(rng, page, button, i == drawCount-1 && drawCount > 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
if drawCount > 1 {
|
||||||
|
guaranteedRarity := raritySR
|
||||||
|
if hasGuaranteedUR(button, cost) {
|
||||||
|
guaranteedRarity = rarityUR
|
||||||
|
}
|
||||||
|
|
||||||
|
guaranteed := false
|
||||||
|
for _, rarity := range rarities {
|
||||||
|
if satisfiesGuaranteedRarity(rarity, guaranteedRarity) {
|
||||||
|
guaranteed = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !guaranteed {
|
||||||
|
if guaranteedRarity == rarityUR {
|
||||||
|
rarities[drawCount-1] = rarityUR
|
||||||
|
} else {
|
||||||
|
rarities[drawCount-1] = pickGuaranteedRarity(rng, page, button)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]secretboxschema.SecretBoxUnitItem, 0, drawCount)
|
||||||
|
lowestRarity := 0
|
||||||
|
for _, rarity := range rarities {
|
||||||
|
unitID, err := randomUnitID(ss, rng, memberCategory, page, button, rarity)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
unitData, err := getUserUnitByUnitID(ss, unitID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
item := buildUnitItem(unitData)
|
||||||
|
item.IsHit = shouldShowDirectURHit(rng, page, button, rarity)
|
||||||
|
result = append(result, item)
|
||||||
|
if lowestRarity == 0 || isLowerQualityRarity(rarity, lowestRarity) {
|
||||||
|
lowestRarity = rarity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, lowestRarity, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildUnitItem(unitData unitmodel.UnitDataMap) secretboxschema.SecretBoxUnitItem {
|
||||||
|
isSupportMember := unitData.MaxLove == 0 && unitData.UnitSkillLevel == 0 && unitData.UnitRemovableSkillCapacity == 0
|
||||||
|
insertDate := time.Unix(unitData.InsertDate, 0).Format("2006-01-02 15:04:05")
|
||||||
|
|
||||||
|
return secretboxschema.SecretBoxUnitItem{
|
||||||
|
UnitOwningUserID: unitData.UnitOwningUserID,
|
||||||
|
UnitOwningIDs: []int{unitData.UnitOwningUserID},
|
||||||
|
UnitID: unitData.UnitID,
|
||||||
|
Exp: unitData.Exp,
|
||||||
|
NextExp: 0,
|
||||||
|
Level: unitData.Level,
|
||||||
|
MaxLevel: unitData.MaxLevel,
|
||||||
|
LevelLimitID: unitData.LevelLimitID,
|
||||||
|
Rank: unitData.Rank,
|
||||||
|
MaxRank: unitData.MaxRank,
|
||||||
|
Love: unitData.Love,
|
||||||
|
MaxLove: unitData.MaxLove,
|
||||||
|
UnitSkillLevel: unitData.UnitSkillLevel,
|
||||||
|
SkillLevel: unitData.UnitSkillLevel,
|
||||||
|
UnitSkillExp: unitData.UnitSkillExp,
|
||||||
|
MaxHp: unitData.MaxHp,
|
||||||
|
FavoriteFlag: unitData.FavoriteFlag,
|
||||||
|
DisplayRank: unitData.DisplayRank,
|
||||||
|
UnitRemovableSkillCapacity: unitData.UnitRemovableSkillCapacity,
|
||||||
|
MaxRemovableSkillCapacity: unitData.UnitRemovableSkillCapacity,
|
||||||
|
Attribute: unitData.Attribute,
|
||||||
|
Smile: unitData.Smile,
|
||||||
|
Cute: unitData.Cute,
|
||||||
|
Cool: unitData.Cool,
|
||||||
|
IsRankMax: unitData.IsRankMax,
|
||||||
|
IsLoveMax: unitData.IsLoveMax,
|
||||||
|
IsLevelMax: unitData.IsLevelMax,
|
||||||
|
IsSigned: unitData.IsSigned,
|
||||||
|
IsSkillLevelMax: unitData.IsSkillLevelMax,
|
||||||
|
IsRemovableSkillCapacityMax: unitData.IsRemovableSkillCapacityMax,
|
||||||
|
IsSupportMember: isSupportMember,
|
||||||
|
NewUnitFlag: false,
|
||||||
|
UnitRarityID: unitData.Rarity,
|
||||||
|
IsHit: unitData.Rarity == rarityUR,
|
||||||
|
RemovableSkillIDs: []int{},
|
||||||
|
InsertDate: insertDate,
|
||||||
|
TotalSmile: unitData.Smile,
|
||||||
|
TotalCute: unitData.Cute,
|
||||||
|
TotalCool: unitData.Cool,
|
||||||
|
TotalHp: unitData.MaxHp,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,256 @@
|
|||||||
|
package secretbox
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
secretboxapischema "honoka-chan/internal/schema/api/secretbox"
|
||||||
|
secretboxschema "honoka-chan/internal/schema/secretbox"
|
||||||
|
"honoka-chan/internal/session"
|
||||||
|
"math/rand"
|
||||||
|
"slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
rarityN = 1
|
||||||
|
rarityR = 2
|
||||||
|
raritySR = 3
|
||||||
|
rarityUR = 4
|
||||||
|
raritySSR = 5
|
||||||
|
|
||||||
|
urPromotionAnimationRate = 10
|
||||||
|
)
|
||||||
|
|
||||||
|
type mainQuery interface {
|
||||||
|
findUnitIDs(memberCategory int, supportOnly bool, rarity int) ([]int, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type sessionMainQuery struct {
|
||||||
|
ss *session.Session
|
||||||
|
}
|
||||||
|
|
||||||
|
type sessionlessMainQuery struct{}
|
||||||
|
|
||||||
|
func (q sessionMainQuery) findUnitIDs(memberCategory int, supportOnly bool, rarity int) ([]int, error) {
|
||||||
|
return queryUnitIDs(q.ss, memberCategory, supportOnly, rarity)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sessionlessMainQuery) findUnitIDs(memberCategory int, supportOnly bool, rarity int) ([]int, error) {
|
||||||
|
return queryUnitIDs(nil, memberCategory, supportOnly, rarity)
|
||||||
|
}
|
||||||
|
|
||||||
|
type poolRule struct {
|
||||||
|
memberCategory int
|
||||||
|
supportOnly bool
|
||||||
|
allowedRarities []int
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildUnitLineUp(memberCategory int, page secretboxapischema.SecretBoxPage, button secretboxapischema.SecretBoxButton) []secretboxschema.UnitLineUp {
|
||||||
|
rarities := []int{rarityUR, raritySSR, raritySR, rarityR, rarityN}
|
||||||
|
result := make([]secretboxschema.UnitLineUp, 0, len(rarities))
|
||||||
|
for _, rarity := range rarities {
|
||||||
|
unitIDs, err := findUnitIDsByRule(sessionlessMainQuery{}, memberCategory, page, button, rarity)
|
||||||
|
if err != nil || len(unitIDs) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
result = append(result, secretboxschema.UnitLineUp{
|
||||||
|
Rarity: rarity,
|
||||||
|
UnitIDs: unitIDs,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func findUnitIDsByRule(q mainQuery, memberCategory int, page secretboxapischema.SecretBoxPage, button secretboxapischema.SecretBoxButton, rarity int) ([]int, error) {
|
||||||
|
rule := buildPoolRule(memberCategory, page, button)
|
||||||
|
if len(rule.allowedRarities) > 0 && !slices.Contains(rule.allowedRarities, rarity) {
|
||||||
|
return []int{}, nil
|
||||||
|
}
|
||||||
|
return q.findUnitIDs(rule.memberCategory, rule.supportOnly, rarity)
|
||||||
|
}
|
||||||
|
|
||||||
|
func randomUnitID(ss *session.Session, rng *rand.Rand, memberCategory int, page secretboxapischema.SecretBoxPage, button secretboxapischema.SecretBoxButton, rarity int) (int, error) {
|
||||||
|
unitIDs, err := findUnitIDsByRule(sessionMainQuery{ss: ss}, memberCategory, page, button, rarity)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if len(unitIDs) == 0 {
|
||||||
|
return 0, fmt.Errorf("no units available for secretbox=%d rarity=%d", page.SecretBoxInfo.SecretBoxID, rarity)
|
||||||
|
}
|
||||||
|
return unitIDs[rng.Intn(len(unitIDs))], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildPoolRule(memberCategory int, page secretboxapischema.SecretBoxPage, button secretboxapischema.SecretBoxButton) poolRule {
|
||||||
|
rule := poolRule{
|
||||||
|
memberCategory: memberCategory,
|
||||||
|
allowedRarities: []int{rarityR, raritySR, raritySSR, rarityUR},
|
||||||
|
}
|
||||||
|
|
||||||
|
if isThanksFestivalBox(page) {
|
||||||
|
rule.allowedRarities = []int{raritySSR, rarityUR}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch page.SecretBoxInfo.SecretBoxID {
|
||||||
|
case 1, 61:
|
||||||
|
rule.memberCategory = 0
|
||||||
|
rule.allowedRarities = []int{rarityN}
|
||||||
|
return rule
|
||||||
|
case 23:
|
||||||
|
rule.memberCategory = 1
|
||||||
|
rule.supportOnly = true
|
||||||
|
rule.allowedRarities = []int{rarityN, rarityR, raritySR, rarityUR, raritySSR}
|
||||||
|
return rule
|
||||||
|
}
|
||||||
|
|
||||||
|
switch button.SecretBoxButtonType {
|
||||||
|
case 6:
|
||||||
|
rule.memberCategory = 0
|
||||||
|
rule.allowedRarities = []int{rarityN}
|
||||||
|
case 9:
|
||||||
|
rule.allowedRarities = []int{raritySR, rarityUR}
|
||||||
|
case 10:
|
||||||
|
rule.allowedRarities = []int{raritySSR, rarityUR}
|
||||||
|
case 11:
|
||||||
|
rule.allowedRarities = []int{rarityUR}
|
||||||
|
case 14, 15:
|
||||||
|
rule.allowedRarities = []int{raritySR, raritySSR, rarityUR}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rule
|
||||||
|
}
|
||||||
|
|
||||||
|
func isThanksFestivalBox(page secretboxapischema.SecretBoxPage) bool {
|
||||||
|
switch page.SecretBoxInfo.SecretBoxID {
|
||||||
|
case 1718, 1719, 1720, 1721:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func pickRarity(rng *rand.Rand, page secretboxapischema.SecretBoxPage, button secretboxapischema.SecretBoxButton, guaranteed bool) int {
|
||||||
|
rule := buildPoolRule(0, page, button)
|
||||||
|
if len(rule.allowedRarities) == 1 {
|
||||||
|
return rule.allowedRarities[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
weights := map[int]int{
|
||||||
|
rarityR: 70,
|
||||||
|
raritySR: 20,
|
||||||
|
raritySSR: 7,
|
||||||
|
rarityUR: 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
if isThanksFestivalBox(page) {
|
||||||
|
weights = map[int]int{
|
||||||
|
raritySSR: 70,
|
||||||
|
rarityUR: 30,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch button.SecretBoxButtonType {
|
||||||
|
case 9:
|
||||||
|
weights = map[int]int{raritySR: 80, rarityUR: 20}
|
||||||
|
case 10:
|
||||||
|
weights = map[int]int{raritySSR: 80, rarityUR: 20}
|
||||||
|
case 11:
|
||||||
|
return rarityUR
|
||||||
|
case 14, 15:
|
||||||
|
weights = map[int]int{raritySR: 80, raritySSR: 15, rarityUR: 5}
|
||||||
|
}
|
||||||
|
|
||||||
|
if guaranteed {
|
||||||
|
return pickGuaranteedRarity(rng, page, button)
|
||||||
|
}
|
||||||
|
return weightedPick(rng, rule.allowedRarities, weights)
|
||||||
|
}
|
||||||
|
|
||||||
|
func pickGuaranteedRarity(rng *rand.Rand, page secretboxapischema.SecretBoxPage, button secretboxapischema.SecretBoxButton) int {
|
||||||
|
rule := buildPoolRule(0, page, button)
|
||||||
|
weights := map[int]int{raritySR: 20, raritySSR: 7, rarityUR: 3}
|
||||||
|
return weightedPick(rng, rule.allowedRarities, weights)
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasGuaranteedUR(button secretboxapischema.SecretBoxButton, cost secretboxapischema.SecretBoxCost) bool {
|
||||||
|
if cost.UnitCount <= 1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
switch button.SecretBoxButtonType {
|
||||||
|
case 2, 16:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func satisfiesGuaranteedRarity(rarity int, guaranteedRarity int) bool {
|
||||||
|
switch guaranteedRarity {
|
||||||
|
case rarityUR:
|
||||||
|
return rarity == rarityUR
|
||||||
|
case raritySR:
|
||||||
|
return rarity == raritySR || rarity == raritySSR || rarity == rarityUR
|
||||||
|
default:
|
||||||
|
return rarity == guaranteedRarity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func rarityQualityRank(rarity int) int {
|
||||||
|
switch rarity {
|
||||||
|
case rarityN:
|
||||||
|
return 1
|
||||||
|
case rarityR:
|
||||||
|
return 2
|
||||||
|
case raritySR:
|
||||||
|
return 3
|
||||||
|
case raritySSR:
|
||||||
|
return 4
|
||||||
|
case rarityUR:
|
||||||
|
return 5
|
||||||
|
default:
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isLowerQualityRarity(left int, right int) bool {
|
||||||
|
return rarityQualityRank(left) < rarityQualityRank(right)
|
||||||
|
}
|
||||||
|
|
||||||
|
func shouldShowDirectURHit(rng *rand.Rand, page secretboxapischema.SecretBoxPage, button secretboxapischema.SecretBoxButton, rarity int) bool {
|
||||||
|
if rarity != rarityUR {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
rule := buildPoolRule(0, page, button)
|
||||||
|
for _, allowedRarity := range rule.allowedRarities {
|
||||||
|
if allowedRarity != rarityUR {
|
||||||
|
return rng.Intn(100) >= urPromotionAnimationRate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func weightedPick(rng *rand.Rand, allowed []int, weights map[int]int) int {
|
||||||
|
filtered := make([]int, 0, len(allowed))
|
||||||
|
total := 0
|
||||||
|
for _, rarity := range allowed {
|
||||||
|
weight := weights[rarity]
|
||||||
|
if weight <= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
filtered = append(filtered, rarity)
|
||||||
|
total += weight
|
||||||
|
}
|
||||||
|
if len(filtered) == 0 {
|
||||||
|
return allowed[len(allowed)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
n := rng.Intn(total) + 1
|
||||||
|
acc := 0
|
||||||
|
for _, rarity := range filtered {
|
||||||
|
acc += weights[rarity]
|
||||||
|
if n <= acc {
|
||||||
|
return rarity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filtered[len(filtered)-1]
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
package secretbox
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
unitmodel "honoka-chan/internal/model/unit"
|
||||||
|
secretboxapischema "honoka-chan/internal/schema/api/secretbox"
|
||||||
|
"honoka-chan/internal/session"
|
||||||
|
honokautils "honoka-chan/internal/utils"
|
||||||
|
"honoka-chan/pkg/db"
|
||||||
|
"sort"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const secretBoxDataFile = "secretbox_data.json"
|
||||||
|
|
||||||
|
func loadSecretBoxAllData() (secretboxapischema.AllResp, error) {
|
||||||
|
data, err := honokautils.LoadServerData[secretboxapischema.AllData](secretBoxDataFile)
|
||||||
|
if err != nil {
|
||||||
|
return secretboxapischema.AllResp{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return secretboxapischema.AllResp{
|
||||||
|
Result: data,
|
||||||
|
Status: 200,
|
||||||
|
CommandNum: false,
|
||||||
|
TimeStamp: time.Now().Unix(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func findSecretBoxPage(config secretboxapischema.AllResp, secretBoxID int) (int, secretboxapischema.SecretBoxPage, error) {
|
||||||
|
for _, category := range config.Result.MemberCategoryList {
|
||||||
|
for _, page := range category.PageList {
|
||||||
|
if page.SecretBoxInfo.SecretBoxID == secretBoxID {
|
||||||
|
return category.MemberCategory, page, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, secretboxapischema.SecretBoxPage{}, fmt.Errorf("secretbox not found: %d", secretBoxID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func findSecretBoxCost(page secretboxapischema.SecretBoxPage, costID int) (secretboxapischema.SecretBoxButton, secretboxapischema.SecretBoxCost, error) {
|
||||||
|
for _, button := range page.ButtonList {
|
||||||
|
for _, cost := range button.CostList {
|
||||||
|
if cost.ID == costID {
|
||||||
|
return button, cost, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return secretboxapischema.SecretBoxButton{}, secretboxapischema.SecretBoxCost{}, fmt.Errorf("secretbox cost not found: %d", costID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func queryUnitIDs(ss *session.Session, memberCategory int, supportOnly bool, rarity int) ([]int, error) {
|
||||||
|
var (
|
||||||
|
rows []struct {
|
||||||
|
UnitID int `xorm:"unit_id"`
|
||||||
|
}
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
sql := `
|
||||||
|
SELECT u.unit_id
|
||||||
|
FROM unit_m u
|
||||||
|
LEFT JOIN unit_type_m ut ON ut.unit_type_id = u.unit_type_id
|
||||||
|
WHERE u.rarity = ?`
|
||||||
|
args := []any{rarity}
|
||||||
|
|
||||||
|
if supportOnly {
|
||||||
|
sql += ` AND u.disable_rank_up != 0`
|
||||||
|
} else if memberCategory >= 0 {
|
||||||
|
sql += ` AND ut.member_category = ?`
|
||||||
|
args = append(args, memberCategory)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ss != nil {
|
||||||
|
err = ss.MainEng.SQL(sql, args...).Find(&rows)
|
||||||
|
} else {
|
||||||
|
err = db.MainEng.SQL(sql, args...).Find(&rows)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]int, 0, len(rows))
|
||||||
|
for _, row := range rows {
|
||||||
|
result = append(result, row.UnitID)
|
||||||
|
}
|
||||||
|
sort.Ints(result)
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUserUnitByUnitID(ss *session.Session, unitID int) (unitmodel.UnitDataMap, error) {
|
||||||
|
unitData := unitmodel.UnitDataMap{}
|
||||||
|
has, err := ss.GetBasicUnitInfo().
|
||||||
|
Where("a.user_id = ?", ss.UserID).
|
||||||
|
Where("a.unit_id = ?", unitID).
|
||||||
|
Get(&unitData)
|
||||||
|
if err != nil {
|
||||||
|
return unitmodel.UnitDataMap{}, err
|
||||||
|
}
|
||||||
|
if !has {
|
||||||
|
return unitmodel.UnitDataMap{}, fmt.Errorf("user %d does not own unit %d", ss.UserID, unitID)
|
||||||
|
}
|
||||||
|
return unitData, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package secretbox
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"honoka-chan/internal/middleware"
|
||||||
|
"honoka-chan/internal/router"
|
||||||
|
secretboxschema "honoka-chan/internal/schema/secretbox"
|
||||||
|
"honoka-chan/internal/session"
|
||||||
|
honokautils "honoka-chan/internal/utils"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func showDetail(ctx *gin.Context) {
|
||||||
|
ss := session.Get(ctx)
|
||||||
|
defer ss.Finalize()
|
||||||
|
|
||||||
|
showDetailReq := secretboxschema.ShowDetailReq{}
|
||||||
|
err := honokautils.ParseRequestData(ctx, &showDetailReq)
|
||||||
|
if ss.CheckErr(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := showDetailData(showDetailReq.SecretBoxID)
|
||||||
|
if ss.CheckErr(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ss.Respond(secretboxschema.ShowDetailResp{
|
||||||
|
ResponseData: data,
|
||||||
|
ReleaseInfo: []any{},
|
||||||
|
StatusCode: 200,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
router.AddHandler("main.php", "POST", "/secretbox/showDetail", middleware.Common, showDetail)
|
||||||
|
}
|
||||||
|
|
||||||
|
func showDetailData(secretBoxID int) (secretboxschema.ShowDetailData, error) {
|
||||||
|
if secretBoxID <= 0 {
|
||||||
|
return secretboxschema.ShowDetailData{}, fmt.Errorf("invalid secret_box_id: %d", secretBoxID)
|
||||||
|
}
|
||||||
|
|
||||||
|
config, err := loadSecretBoxAllData()
|
||||||
|
if err != nil {
|
||||||
|
return secretboxschema.ShowDetailData{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
memberCategory, page, err := findSecretBoxPage(config, secretBoxID)
|
||||||
|
if err != nil {
|
||||||
|
return secretboxschema.ShowDetailData{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := secretboxschema.ShowDetailData{
|
||||||
|
URL: fmt.Sprintf("/webview.php/secretbox/detail?id=%d&no_title=1", secretBoxID),
|
||||||
|
}
|
||||||
|
|
||||||
|
switch page.SecretBoxInfo.SecretBoxType {
|
||||||
|
case 5:
|
||||||
|
buttons := make([]secretboxschema.ButtonTypeUnitLineUp, 0, len(page.ButtonList))
|
||||||
|
for _, button := range page.ButtonList {
|
||||||
|
buttons = append(buttons, secretboxschema.ButtonTypeUnitLineUp{
|
||||||
|
SecretBoxButtonType: button.SecretBoxButtonType,
|
||||||
|
SecretBoxName: button.SecretBoxName,
|
||||||
|
UnitLineUp: buildUnitLineUp(memberCategory, page, button),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
result.ButtonTypeUnitLineUp = buttons
|
||||||
|
default:
|
||||||
|
if len(page.ButtonList) == 0 {
|
||||||
|
return secretboxschema.ShowDetailData{}, fmt.Errorf("secretbox %d has no buttons", secretBoxID)
|
||||||
|
}
|
||||||
|
result.UnitLineUp = buildUnitLineUp(memberCategory, page, page.ButtonList[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
package apischema
|
package apischema
|
||||||
|
|
||||||
type ApiReq struct {
|
type ApiReq struct {
|
||||||
Module string `json:"module"`
|
Module string `json:"module"`
|
||||||
UserID int `json:"user_id"`
|
UserID int `json:"user_id"`
|
||||||
Action string `json:"action"`
|
Action string `json:"action"`
|
||||||
Timestamp int64 `json:"timeStamp"`
|
ID int `json:"id"`
|
||||||
|
SecretBoxID int `json:"secret_box_id"`
|
||||||
|
Timestamp int64 `json:"timeStamp"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ApiResp struct {
|
type ApiResp struct {
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
package bannerapischema
|
package bannerapischema
|
||||||
|
|
||||||
type BannerList struct {
|
type BannerList struct {
|
||||||
BannerType int `json:"banner_type"`
|
BannerType int `json:"banner_type"`
|
||||||
TargetID int `json:"target_id"`
|
TargetID int `json:"target_id"`
|
||||||
AssetPath string `json:"asset_path"`
|
AssetPath string `json:"asset_path"`
|
||||||
FixedFlag bool `json:"fixed_flag"`
|
FixedFlag bool `json:"fixed_flag"`
|
||||||
BackSide bool `json:"back_side"`
|
BackSide bool `json:"back_side"`
|
||||||
BannerID int `json:"banner_id"`
|
BannerID int `json:"banner_id"`
|
||||||
StartDate string `json:"start_date"`
|
StartDate string `json:"start_date"`
|
||||||
EndDate string `json:"end_date"`
|
EndDate string `json:"end_date"`
|
||||||
AddUnitStartDate string `json:"add_unit_start_date,omitempty"`
|
WebviewURL string `json:"webview_url,omitempty"`
|
||||||
WebviewURL string `json:"webview_url,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ListData struct {
|
type ListData struct {
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package secretboxapischema
|
||||||
|
|
||||||
|
type ItemAmount struct {
|
||||||
|
ItemID int `json:"item_id"`
|
||||||
|
Amount int `json:"amount"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GaugeInfo struct {
|
||||||
|
MaxGaugePoint int `json:"max_gauge_point"`
|
||||||
|
GaugePoint int `json:"gauge_point"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SecretBoxAnimationAssets struct {
|
||||||
|
Type int `json:"type"`
|
||||||
|
BackgroundAsset string `json:"background_asset"`
|
||||||
|
AdditionalAsset1 string `json:"additional_asset_1"`
|
||||||
|
AdditionalAsset2 string `json:"additional_asset_2"`
|
||||||
|
AdditionalAsset3 string `json:"additional_asset_3"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SecretBoxCost struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Type int `json:"type"`
|
||||||
|
Amount int `json:"amount"`
|
||||||
|
ItemID *int `json:"item_id"`
|
||||||
|
UnitCount int `json:"unit_count"`
|
||||||
|
Payable bool `json:"payable"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SecretBoxButton struct {
|
||||||
|
SecretBoxButtonType int `json:"secret_box_button_type"`
|
||||||
|
CostList []SecretBoxCost `json:"cost_list"`
|
||||||
|
SecretBoxName string `json:"secret_box_name"`
|
||||||
|
LimitCount int `json:"limit_count,omitempty"`
|
||||||
|
LimitUnderMessage string `json:"limit_under_message,omitempty"`
|
||||||
|
LimitUnderMessageSuffix string `json:"limit_under_message_suffix,omitempty"`
|
||||||
|
BalloonAsset string `json:"balloon_asset,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SecretBoxInfo struct {
|
||||||
|
SecretBoxID int `json:"secret_box_id"`
|
||||||
|
SecretBoxType int `json:"secret_box_type"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
StartDate string `json:"start_date"`
|
||||||
|
EndDate string `json:"end_date"`
|
||||||
|
ShowEndDate string `json:"show_end_date"`
|
||||||
|
AddGauge int `json:"add_gauge"`
|
||||||
|
AlwaysDisplayFlag int `json:"always_display_flag"`
|
||||||
|
PonCount int `json:"pon_count"`
|
||||||
|
PonUpperLimit int `json:"pon_upper_limit"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SecretBoxPage struct {
|
||||||
|
MenuAsset string `json:"menu_asset"`
|
||||||
|
PageOrder int `json:"page_order"`
|
||||||
|
AnimationAssets SecretBoxAnimationAssets `json:"animation_assets"`
|
||||||
|
ButtonList []SecretBoxButton `json:"button_list"`
|
||||||
|
SecretBoxInfo SecretBoxInfo `json:"secret_box_info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MemberCategoryPageList struct {
|
||||||
|
MemberCategory int `json:"member_category"`
|
||||||
|
PageList []SecretBoxPage `json:"page_list"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AllData struct {
|
||||||
|
UseCache int `json:"use_cache"`
|
||||||
|
IsUnitMax bool `json:"is_unit_max"`
|
||||||
|
ItemList []ItemAmount `json:"item_list"`
|
||||||
|
GaugeInfo GaugeInfo `json:"gauge_info"`
|
||||||
|
MemberCategoryList []MemberCategoryPageList `json:"member_category_list"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AllResp struct {
|
||||||
|
Result AllData `json:"result"`
|
||||||
|
Status int `json:"status"`
|
||||||
|
CommandNum bool `json:"commandNum"`
|
||||||
|
TimeStamp int64 `json:"timeStamp"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package secretboxschema
|
||||||
|
|
||||||
|
type MultiReq struct {
|
||||||
|
Module string `json:"module"`
|
||||||
|
Action string `json:"action"`
|
||||||
|
Mgd int `json:"mgd"`
|
||||||
|
ID int `json:"id"`
|
||||||
|
SecretBoxID int `json:"secret_box_id"`
|
||||||
|
Count int `json:"count"`
|
||||||
|
TimeStamp int64 `json:"timeStamp"`
|
||||||
|
CommandNum string `json:"commandNum"`
|
||||||
|
UnitTypeIds []any `json:"unit_type_ids"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MultiResp struct {
|
||||||
|
ResponseData PonData `json:"response_data"`
|
||||||
|
ReleaseInfo []any `json:"release_info"`
|
||||||
|
StatusCode int `json:"status_code"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
package secretboxschema
|
||||||
|
|
||||||
|
import secretboxapischema "honoka-chan/internal/schema/api/secretbox"
|
||||||
|
|
||||||
|
type PonReq struct {
|
||||||
|
Module string `json:"module"`
|
||||||
|
Mgd int `json:"mgd"`
|
||||||
|
Action string `json:"action"`
|
||||||
|
TimeStamp int64 `json:"timeStamp"`
|
||||||
|
ID int `json:"id"`
|
||||||
|
CommandNum string `json:"commandNum"`
|
||||||
|
SecretBoxID int `json:"secret_box_id"`
|
||||||
|
UnitTypeIds []any `json:"unit_type_ids"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SecretBoxUnitItem struct {
|
||||||
|
UnitOwningUserID int `json:"unit_owning_user_id"`
|
||||||
|
UnitOwningIDs []int `json:"unit_owning_ids"`
|
||||||
|
UnitID int `json:"unit_id"`
|
||||||
|
Exp int `json:"exp"`
|
||||||
|
NextExp int `json:"next_exp"`
|
||||||
|
Level int `json:"level"`
|
||||||
|
MaxLevel int `json:"max_level"`
|
||||||
|
LevelLimitID int `json:"level_limit_id"`
|
||||||
|
Rank int `json:"rank"`
|
||||||
|
MaxRank int `json:"max_rank"`
|
||||||
|
Love int `json:"love"`
|
||||||
|
MaxLove int `json:"max_love"`
|
||||||
|
UnitSkillLevel int `json:"unit_skill_level"`
|
||||||
|
SkillLevel int `json:"skill_level"`
|
||||||
|
UnitSkillExp int `json:"unit_skill_exp"`
|
||||||
|
MaxHp int `json:"max_hp"`
|
||||||
|
FavoriteFlag bool `json:"favorite_flag"`
|
||||||
|
DisplayRank int `json:"display_rank"`
|
||||||
|
UnitRemovableSkillCapacity int `json:"unit_removable_skill_capacity"`
|
||||||
|
MaxRemovableSkillCapacity int `json:"max_removable_skill_capacity"`
|
||||||
|
Attribute int `json:"attribute"`
|
||||||
|
Smile int `json:"smile"`
|
||||||
|
Cute int `json:"cute"`
|
||||||
|
Cool int `json:"cool"`
|
||||||
|
IsRankMax bool `json:"is_rank_max"`
|
||||||
|
IsLoveMax bool `json:"is_love_max"`
|
||||||
|
IsLevelMax bool `json:"is_level_max"`
|
||||||
|
IsSigned bool `json:"is_signed"`
|
||||||
|
IsSkillLevelMax bool `json:"is_skill_level_max"`
|
||||||
|
IsRemovableSkillCapacityMax bool `json:"is_removable_skill_capacity_max"`
|
||||||
|
IsSupportMember bool `json:"is_support_member"`
|
||||||
|
NewUnitFlag bool `json:"new_unit_flag"`
|
||||||
|
UnitRarityID int `json:"unit_rarity_id"`
|
||||||
|
IsHit bool `json:"is_hit"`
|
||||||
|
RemovableSkillIDs []int `json:"removable_skill_ids"`
|
||||||
|
InsertDate string `json:"insert_date"`
|
||||||
|
TotalSmile int `json:"total_smile"`
|
||||||
|
TotalCute int `json:"total_cute"`
|
||||||
|
TotalCool int `json:"total_cool"`
|
||||||
|
TotalHp int `json:"total_hp"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SecretBoxRewardItem struct {
|
||||||
|
ItemID int `json:"item_id"`
|
||||||
|
AddType int `json:"add_type"`
|
||||||
|
Amount int `json:"amount"`
|
||||||
|
ItemCategoryID int `json:"item_category_id"`
|
||||||
|
RewardBoxFlag bool `json:"reward_box_flag"`
|
||||||
|
InsertDate string `json:"insert_date"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SecretBoxItems struct {
|
||||||
|
Unit []SecretBoxUnitItem `json:"unit"`
|
||||||
|
Item []SecretBoxRewardItem `json:"item"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PonData struct {
|
||||||
|
IsUnitMax bool `json:"is_unit_max"`
|
||||||
|
ItemList []secretboxapischema.ItemAmount `json:"item_list"`
|
||||||
|
GaugeInfo secretboxapischema.GaugeInfo `json:"gauge_info"`
|
||||||
|
ButtonList []secretboxapischema.SecretBoxButton `json:"button_list"`
|
||||||
|
SecretBoxInfo secretboxapischema.SecretBoxInfo `json:"secret_box_info"`
|
||||||
|
SecretBoxItems SecretBoxItems `json:"secret_box_items"`
|
||||||
|
BeforeUserInfo any `json:"before_user_info"`
|
||||||
|
AfterUserInfo any `json:"after_user_info"`
|
||||||
|
FreeMuseGachaFlag bool `json:"free_muse_gacha_flag"`
|
||||||
|
FreeAqoursGachaFlag bool `json:"free_aqours_gacha_flag"`
|
||||||
|
LowestRarity int `json:"lowest_rarity"`
|
||||||
|
PromotionPerformanceRate int `json:"promotion_performance_rate"`
|
||||||
|
SecretBoxParcelType int `json:"secret_box_parcel_type"`
|
||||||
|
LimitBonusInfo []any `json:"limit_bonus_info"`
|
||||||
|
LimitBonusRewards []any `json:"limit_bonus_rewards"`
|
||||||
|
UnitSupportList []any `json:"unit_support_list"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PonResp struct {
|
||||||
|
ResponseData PonData `json:"response_data"`
|
||||||
|
ReleaseInfo []any `json:"release_info"`
|
||||||
|
StatusCode int `json:"status_code"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package secretboxschema
|
||||||
|
|
||||||
|
type ShowDetailReq struct {
|
||||||
|
Module string `json:"module"`
|
||||||
|
Action string `json:"action"`
|
||||||
|
TimeStamp int64 `json:"timeStamp"`
|
||||||
|
Mgd int `json:"mgd"`
|
||||||
|
SecretBoxID int `json:"secret_box_id"`
|
||||||
|
CommandNum string `json:"commandNum"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UnitLineUp struct {
|
||||||
|
Rarity int `json:"rarity"`
|
||||||
|
UnitIDs []int `json:"unit_ids"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ButtonTypeUnitLineUp struct {
|
||||||
|
SecretBoxButtonType int `json:"secret_box_button_type"`
|
||||||
|
SecretBoxName string `json:"secret_box_name"`
|
||||||
|
UnitLineUp []UnitLineUp `json:"unit_line_up"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ShowDetailData struct {
|
||||||
|
ButtonTypeUnitLineUp []ButtonTypeUnitLineUp `json:"button_type_unit_line_up,omitempty"`
|
||||||
|
UnitLineUp []UnitLineUp `json:"unit_line_up,omitempty"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ShowDetailResp struct {
|
||||||
|
ResponseData ShowDetailData `json:"response_data"`
|
||||||
|
ReleaseInfo []any `json:"release_info"`
|
||||||
|
StatusCode int `json:"status_code"`
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user