├── .github
└── workflows
│ └── build.yml
├── README.md
├── app.go
├── build
└── appicon.png
├── config
└── init.go
├── frontend
├── auto-imports.d.ts
├── components.d.ts
├── index.html
├── package.json
├── package.json.md5
├── src
│ ├── App.vue
│ ├── assets
│ │ └── fonts
│ │ │ └── ChillRoundM.otf
│ ├── components
│ │ ├── CaptureDialog.vue
│ │ ├── PoolCard.vue
│ │ └── SettingDialog.vue
│ ├── main.ts
│ ├── stores
│ │ └── layout.ts
│ ├── style.css
│ └── vite-env.d.ts
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.node.json
├── vite.config.ts
└── wailsjs
│ ├── go
│ ├── main
│ │ ├── App.d.ts
│ │ └── App.js
│ └── models.ts
│ └── runtime
│ ├── package.json
│ ├── runtime.d.ts
│ └── runtime.js
├── go.mod
├── go.sum
├── logger
└── init.go
├── logic
├── AppendLog.go
├── ExportRecord.go
├── FetchRemoteData.go
├── GetCommunityExchangeList.go
├── GetLatestVersion.go
├── GetLocalRecord.go
├── GetPoolInfo.go
├── GetUserInfoFromBBS.go
├── GetUserList.go
├── HandleCommunityTasks.go
├── ImportRecord.go
├── MergeEreRecord.go
├── MergeRecord.go
├── ParseEreExcelData.go
├── ParseEreJsonData.go
├── RemoveLocalRecord.go
├── SaveLocalRecord.go
├── UpdatePoolInfo.go
└── UpdateTo.go
├── main.go
├── model
├── community.go
├── init.go
├── logInfo.go
├── pool.go
├── record.go
└── request.go
├── pb
├── GachaData.pb.go
├── GachaTypeListData.pb.go
├── ItemData.pb.go
├── LangPackageTableCnData.pb.go
└── LanguageStringData.pb.go
├── preload
└── init.go
├── request
├── CommunityCommon.go
├── CommunityExchange.go
├── CommunityExchangeList.go
├── CommunityLogin.go
├── CommunitySign.go
├── CommunityTaskList.go
├── CommunityTopicLike.go
├── CommunityTopicList.go
├── CommunityTopicShare.go
├── CommunityTopicView.go
└── CommunityUserInfo.go
├── sample.png
├── util
├── BackupDB.go
├── Cert.go
├── GetGameDataDir.go
├── GetLogInfo.go
├── GetTableData.go
├── SysProxy.go
└── Version.go
└── wails.json
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: release by tag
2 | on:
3 | push:
4 | tags:
5 | - 'v*'
6 | permissions:
7 | contents: write
8 | jobs:
9 | release-gf2gacha:
10 | runs-on: windows-latest
11 | steps:
12 | - name: checkout
13 | uses: actions/checkout@v4
14 | with:
15 | fetch-depth: 0
16 |
17 | - name: Write Version
18 | run: |
19 | (Get-Content "util/Version.go") -replace 'const version = ""', 'const version = "${{ github.ref_name }}"' | Set-Content "util/Version.go"
20 | (Get-Content "wails.json") -replace '"productVersion": ""', '"productVersion": "${{ github.ref_name }}"' | Set-Content "wails.json"
21 |
22 | - name: Setup GoLang
23 | uses: actions/setup-go@v5
24 | with:
25 | check-latest: true
26 | go-version: 1.23
27 |
28 | - name: Setup NodeJS
29 | uses: actions/setup-node@v3
30 | with:
31 | node-version: '20.x'
32 |
33 | - name: Install Wails
34 | run: go install github.com/wailsapp/wails/v2/cmd/wails@latest
35 |
36 | - name: Build App
37 | working-directory: .
38 | run: wails build -webview2 embed -skipbindings -o gf2gacha.exe
39 |
40 | - name: Create folder and copy files
41 | run: |
42 | mkdir gf2gacha
43 | xcopy .\build\bin\gf2gacha.exe .\gf2gacha\ /y
44 | xcopy .\build\bin\gf2gacha.exe . /y
45 |
46 | - name: Zip the folder
47 | run: powershell Compress-Archive -Path gf2gacha -DestinationPath gf2gacha.zip
48 |
49 | - name: Upload to R2
50 | uses: ryand56/r2-upload-action@latest
51 | with:
52 | r2-account-id: ${{ secrets.R2_ACCOUNT_ID }}
53 | r2-access-key-id: ${{ secrets.R2_ACCESS_KEY_ID }}
54 | r2-secret-access-key: ${{ secrets.R2_SECRET_ACCESS_KEY }}
55 | r2-bucket: ${{ secrets.R2_BUCKET }}
56 | source-dir: |
57 | build/bin/gf2gacha.exe
58 | gf2gacha.zip
59 | destination-dir: gf2gacha/${{ github.ref_name }}/
60 |
61 | - name: Upload Release Asset
62 | uses: softprops/action-gh-release@v2
63 | with:
64 | files: |
65 | gf2gacha.zip
66 | gf2gacha.exe
67 |
68 |
69 | - name: Build Changelog
70 | run: npx changelogithub
71 | env:
72 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 少女前线2:追放 抽卡导出分析工具
2 |
3 | ## 为什么做这个工具?
4 |
5 | [追放采购记录导出工具](https://github.com/EtherealAO/exilium-recruit-export)作者在其[NGA发布贴](https://bbs.nga.cn/read.php?tid=38812531)中表示不搞了,希望有人接盘
6 |
7 | 但我并不熟悉`Electron`,也不太喜欢`Electron`直接包一个巨大浏览器的做法
8 |
9 | 于是把之前自用的导出程序用更加轻量的`Wails`包了个界面传了上来
10 |
11 | 
12 |
13 | ## 主要功能
14 |
15 | - [x] 增量更新 `拉取服务端最新的抽卡数据,与本地数据合并`
16 | - [x] 全量更新 `拉取服务端所有抽卡数据,与本地数据合并(目前合并算法准备重写,完成后再做)`
17 | - [x] 一键社区 `一键完成社区签到与任务,并根据自定义设置兑换道具`
18 | - [x] 导入Ere数据 `导入EreJson或EreExcel格式的数据,与本地数据合并(合并前会自动备份数据库)`
19 | - [x] 导入/导出RawJson `导入/导出服务器原始格式的数据的map`
20 | - [x] 导入/导出MccExcel `导入/导出Mcc格式的Excel`
21 |
22 | ## 基本原理
23 |
24 | 本软件通过读取日志获取`游戏路径`、`抽卡链接`、`UID`、`AccessToken`,进而从服务器获取抽卡记录
25 |
26 | 本软件会在当前目录下的生成名为`gf2gacha.db`的`sqlite`数据库,这就是你的抽卡数据了,你可以使用任何支持sqlite的数据库管理工具查看其内容
27 |
28 | ## 特别提醒
29 |
30 | 本软件不会将用户数据传输到任何第三方服务上,数据仅存储在你本地
31 |
32 | 进行高危操作时(如合并数据),数据库会自动备份
33 |
34 | 如果你有云端托管数据的需求,请点击软件右上角的`回形针图标`查看你当前日志信息,将其复制给第三方服务提供方
35 |
36 | 注意!`AccessToken`是你的临时登录凭证,请勿随意泄露
37 |
38 | ## FAQ
39 |
40 | ### 为什么看不到新手池数据?为什么角色池/武器池/常驻池缺失数据?
41 |
42 | 追放服务器只保留最近180天的数据,大多数抽卡游戏都有类似的限制,所以有需求的用户才会采取多种办法来保存抽卡记录
43 |
44 | ### 如何导入来自[追放采购记录导出工具](https://github.com/EtherealAO/exilium-recruit-export)的数据?
45 |
46 | 由于ERE的UID使用的UID并不是游戏内的UID,我无法判断原数据是属于哪个用户
47 |
48 | **请务必先在主界面手动选择你要导入旧数据的用户对应的UID**
49 |
50 | 然后点击`导入ERE数据`按钮,在ERE程序目录中找到`userData`文件夹,选择里面类似`gacha-list-1234567890.json`的文件,点击确定即可
51 |
52 | ## 附录
53 |
54 | ### 卡池类型
55 |
56 | | PoolType | 备注 |
57 | |----------|-------|
58 | | 1 | 常驻池 |
59 | | 2 | 暂无 |
60 | | 3 | 角色池 |
61 | | 4 | 武器池 |
62 | | 5 | 新手池 |
63 | | 6 | 自选角色池 |
64 | | 7 | 自选武器池 |
65 | | 8 | 神秘箱 |
66 |
67 | ### 卡池信息
68 |
69 | | PoolId | 备注 | 时间戳起 | 时间戳止 | 时间起 | 时间止 |
70 | |--------|--------------|------------|------------|-------------------------------|-------------------------------|
71 | | 1001 | 常规采购 | | | | |
72 | | 2001 | 军备提升β-托洛洛武器 | 1703124000 | 1704358799 | 2023-12-21 10:00:00 +0800 CST | 2024-01-04 16:59:59 +0800 CST |
73 | | 3001 | 定向采购β-托洛洛 | 1703124000 | 1704358799 | 2023-12-21 10:00:00 +0800 CST | 2024-01-04 16:59:59 +0800 CST |
74 | | 4001 | 初始采购-新手池 | | | | |
75 | | 5001 | 军备提升α-黛烟武器 | 1703494800 | 1705913999 | 2023-12-25 17:00:00 +0800 CST | 2024-01-22 16:59:59 +0800 CST |
76 | | 6001 | 定向采购α-黛烟 | 1703494800 | 1705913999 | 2023-12-25 17:00:00 +0800 CST | 2024-01-22 16:59:59 +0800 CST |
77 | | 7001 | 定向采购β-塞布丽娜 | 1705395600 | 1706605199 | 2024-01-16 17:00:00 +0800 CST | 2024-01-30 16:59:59 +0800 CST |
78 | | 8001 | 军备提升β-塞布丽娜武器 | 1705395600 | 1706605199 | 2024-01-16 17:00:00 +0800 CST | 2024-01-30 16:59:59 +0800 CST |
79 | | 9001 | 定向采购α-桑朵莱希 | 1705978800 | 1708657199 | 2024-01-23 11:00:00 +0800 CST | 2024-02-23 10:59:59 +0800 CST |
80 | | 10001 | 军备提升α-桑朵莱希武器 | 1705978800 | 1708657199 | 2024-01-23 11:00:00 +0800 CST | 2024-02-23 10:59:59 +0800 CST |
81 | | 11001 | 定向采购β-琼玖 | 1707382800 | 1708657199 | 2024-02-08 17:00:00 +0800 CST | 2024-02-23 10:59:59 +0800 CST |
82 | | 12001 | 军备提升β-琼玖武器 | 1707382800 | 1708657199 | 2024-02-08 17:00:00 +0800 CST | 2024-02-23 10:59:59 +0800 CST |
83 | | 13001 | 定向采购α-莱娜 | 1708657200 | 1711421999 | 2024-02-23 11:00:00 +0800 CST | 2024-03-26 10:59:59 +0800 CST |
84 | | 14001 | 军备提升α-莱娜武器 | 1708657200 | 1711421999 | 2024-02-23 11:00:00 +0800 CST | 2024-03-26 10:59:59 +0800 CST |
85 | | 15001 | 定向采购-黛烟 | 1712653200 | 1714445999 | 2024-04-09 17:00:00 +0800 CST | 2024-04-30 10:59:59 +0800 CST |
86 | | 16001 | 军备提升-黛烟武器 | 1712653200 | 1714445999 | 2024-04-09 17:00:00 +0800 CST | 2024-04-30 10:59:59 +0800 CST |
87 | | 17001 | 定向采购-绛雨 | 1711422000 | 1714445999 | 2024-03-26 11:00:00 +0800 CST | 2024-04-30 10:59:59 +0800 CST |
88 | | 18001 | 军备提升-绛雨武器 | 1711422000 | 1714445999 | 2024-03-26 11:00:00 +0800 CST | 2024-04-30 10:59:59 +0800 CST |
89 | | 19001 | 定向采购β-莫辛纳甘 | 1709629200 | 1711421999 | 2024-03-05 17:00:00 +0800 CST | 2024-03-26 10:59:59 +0800 CST |
90 | | 20001 | 军备提升β-莫辛纳甘武器 | 1709629200 | 1711421999 | 2024-03-05 17:00:00 +0800 CST | 2024-03-26 10:59:59 +0800 CST |
91 | | 21001 | 定向采购-佩里缇亚 | 1711422000 | 1712653199 | 2024-03-26 11:00:00 +0800 CST | 2024-04-09 16:59:59 +0800 CST |
92 | | 22001 | 军备提升-佩里缇亚武器 | 1711422000 | 1712653199 | 2024-03-26 11:00:00 +0800 CST | 2024-04-09 16:59:59 +0800 CST |
93 | | 23001 | 定向采购-玛绮朵 | 1714446000 | 1717556399 | 2024-04-30 11:00:00 +0800 CST | 2024-06-05 10:59:59 +0800 CST |
94 | | 24001 | 军备提升-玛绮朵武器 | 1714446000 | 1717556399 | 2024-04-30 11:00:00 +0800 CST | 2024-06-05 10:59:59 +0800 CST |
95 | | 25001 | 定向采购-桑朵莱希 | 1715677200 | 1717556399 | 2024-05-14 17:00:00 +0800 CST | 2024-06-05 10:59:59 +0800 CST |
96 | | 26001 | 军备提升-桑朵莱希武器 | 1715677200 | 1717556399 | 2024-05-14 17:00:00 +0800 CST | 2024-06-05 10:59:59 +0800 CST |
97 | | 27001 | 定向采购-乌尔丽德 | 1717556400 | 1719284399 | 2024-06-05 11:00:00 +0800 CST | 2024-06-25 10:59:59 +0800 CST |
98 | | 28001 | 军备提升-乌尔丽德武器 | 1717556400 | 1719284399 | 2024-06-05 11:00:00 +0800 CST | 2024-06-25 10:59:59 +0800 CST |
99 | | 29001 | 定向采购-莱娜 | 1717556400 | 1719284399 | 2024-06-05 11:00:00 +0800 CST | 2024-06-25 10:59:59 +0800 CST |
100 | | 30001 | 军备提升-莱娜武器 | 1717556400 | 1719284399 | 2024-06-05 11:00:00 +0800 CST | 2024-06-25 10:59:59 +0800 CST |
101 | | 31001 | 定向采购-索米 | 1719284400 | 1721098799 | 2024-06-25 11:00:00 +0800 CST | 2024-07-16 10:59:59 +0800 CST |
102 | | 32001 | 军备提升-索米武器 | 1719284400 | 1721098799 | 2024-06-25 11:00:00 +0800 CST | 2024-07-16 10:59:59 +0800 CST |
103 | | 33001 | 定向采购-绛雨 | 1719284400 | 1721098799 | 2024-06-25 11:00:00 +0800 CST | 2024-07-16 10:59:59 +0800 CST |
104 | | 34001 | 军备提升-绛雨武器 | 1719284400 | 1721098799 | 2024-06-25 11:00:00 +0800 CST | 2024-07-16 10:59:59 +0800 CST |
105 | | 35001 | 定向采购-杜莎妮 | 1721098800 | 1722913199 | 2024-07-16 11:00:00 +0800 CST | 2024-08-06 10:59:59 +0800 CST |
106 | | 36001 | 军备提升-杜莎妮武器 | 1721098800 | 1722913199 | 2024-07-16 11:00:00 +0800 CST | 2024-08-06 10:59:59 +0800 CST |
107 | | 37001 | 定向采购-玛绮朵 | 1721098800 | 1722913199 | 2024-07-16 11:00:00 +0800 CST | 2024-08-06 10:59:59 +0800 CST |
108 | | 38001 | 军备提升-玛绮朵武器 | 1721098800 | 1722913199 | 2024-07-16 11:00:00 +0800 CST | 2024-08-06 10:59:59 +0800 CST |
109 | | 39001 | 定向采购-朝晖 | 1722913200 | 1724705999 | 2024-08-06 11:00:00 +0800 CST | 2024-08-27 04:59:59 +0800 CST |
110 | | 40001 | 军备提升-朝晖武器 | 1722913200 | 1724705999 | 2024-08-06 11:00:00 +0800 CST | 2024-08-27 04:59:59 +0800 CST |
111 | | 41001 | 定向采购-乌尔丽德 | 1722913200 | 1724705999 | 2024-08-06 11:00:00 +0800 CST | 2024-08-27 04:59:59 +0800 CST |
112 | | 42001 | 军备提升-乌尔丽德武器 | 1722913200 | 1724705999 | 2024-08-06 11:00:00 +0800 CST | 2024-08-27 04:59:59 +0800 CST |
113 | | 43001 | 定向采购-可露凯 | 1724706000 | 1726714799 | 2024-08-27 05:00:00 +0800 CST | 2024-09-19 10:59:59 +0800 CST |
114 | | 44001 | 军备提升-可露凯武器 | 1724706000 | 1726714799 | 2024-08-27 05:00:00 +0800 CST | 2024-09-19 10:59:59 +0800 CST |
115 | | 45001 | 定向采购-索米 | 1724706000 | 1726714799 | 2024-08-27 05:00:00 +0800 CST | 2024-09-19 10:59:59 +0800 CST |
116 | | 46001 | 军备提升-索米武器 | 1724706000 | 1726714799 | 2024-08-27 05:00:00 +0800 CST | 2024-09-19 10:59:59 +0800 CST |
117 | | 90001 | 自选采购·人形 | 1724706000 | 1728442799 | 2024-08-27 05:00:00 +0800 CST | 2024-10-09 10:59:59 +0800 CST |
118 | | 91001 | 自选采购·军备 | 1724706000 | 1728442799 | 2024-08-27 05:00:00 +0800 CST | 2024-10-09 10:59:59 +0800 CST |
119 | | 99001 | 神秘箱 | 1689476400 | 4089668399 | 2023-07-16 11:00:00 +0800 CST | 2099-08-06 10:59:59 +0800 CST |
120 |
121 | ### 人形信息
122 |
123 | | ItemId | Rank | 备注 |
124 | |---------------------------------|------------------------------|---------------------------------|
125 | | 1001 | 4 | 克罗丽科 |
126 | | 1008 | 4 | 纳美西丝 |
127 | | 1009 | 4 | 寇尔芙 |
128 | | 1013 | 5 | 莱娜 |
129 | | 1015 | 5 | 维普蕾 |
130 | | 1017 | 4 | 闪电 |
131 | | 1021 | 5 | 佩里缇亚 |
132 | | 1022 | 4 | 夏克里 |
133 | | 1023 | 5 | 杜莎妮 |
134 | | 1024 | 4 | 奇塔 |
135 | | 1025 | 5 | 托洛洛 |
136 | | 1026 | 4 | 纳甘 |
137 | | 1027 | 5 | 琼玖 |
138 | | 1028 | 5 | 桑朵莱希 |
139 | | 1029 | 5 | 塞布丽娜 |
140 | | 1032 | 5 | 黛烟 |
141 | | 1033 | 5 | 莫辛纳甘 |
142 | | 1034 | 5 | 玛绮朵 |
143 | | 1035 | 5 | 绛雨 |
144 | | 1036 | 4 | 科谢尼娅 |
145 | | 1037 | 5 | 乌尔丽德 |
146 | | 1038 | 4 | 莉塔拉 |
147 | | 1039 | 5 | 索米 |
148 | | 1040 | 5 | 波波沙 |
149 | | 1041 | 4 | 洛塔 |
150 | | 1050 | 5 | 朝晖 |
151 | | 1052 | 5 | 可露凯 |
152 |
153 | ### 武器信息
154 |
155 | | ItemId | Rank | 备注 |
156 | |----------------------------------|------------------------------|-----------------------------------------|
157 | | 10001 | 5 | 喧闹恶灵 |
158 | | 10002 | 5 | 绝密手稿 |
159 | | 10003 | 5 | 阿尔克纳 |
160 | | 10004 | 5 | 盖尔诺 |
161 | | 10005 | 5 | 王冠鹿角兔 |
162 | | 10006 | 5 | 妙尔尼尔 |
163 | | 10007 | 5 | 远行游鸽 |
164 | | 10131 | 3 | 旧式通用冲锋枪9 |
165 | | 10132 | 4 | 通用冲锋枪9 |
166 | | 10133 | 5 | 幼狮 |
167 | | 10231 | 3 | 旧式科夫罗夫 |
168 | | 10232 | 4 | 科夫罗夫 |
169 | | 10233 | 5 | 传颂之诗 |
170 | | 10331 | 3 | 旧式莫辛-纳甘 |
171 | | 10332 | 4 | 莫辛-纳甘 |
172 | | 10333 | 5 | 斯摩希克 |
173 | | 10341 | 3 | 旧式瓦尔特2000 |
174 | | 10342 | 4 | 瓦尔特2000 |
175 | | 10343 | 5 | 苦涩焦糖 |
176 | | 10351 | 3 | 旧式九七式 |
177 | | 10352 | 4 | 九七式 |
178 | | 10353 | 5 | 跃虎 |
179 | | 10361 | 3 | 旧式斯捷奇金 |
180 | | 10362 | 4 | 斯捷奇金 |
181 | | 10371 | 3 | 旧式羽锋 |
182 | | 10372 | 4 | 羽锋 |
183 | | 10373 | 5 | 流羽白英 |
184 | | 10381 | 3 | 旧式加利尔轻机枪 |
185 | | 10382 | 4 | 加利尔轻机枪 |
186 | | 10391 | 3 | 旧式索米 |
187 | | 10392 | 4 | 索米 |
188 | | 10393 | 5 | 未言使命 |
189 | | 10401 | 3 | 旧式波波沙冲锋枪 |
190 | | 10402 | 4 | 波波沙冲锋枪 |
191 | | 10403 | 5 | 斯瓦罗格 |
192 | | 10411 | 3 | 旧式超级90 |
193 | | 10412 | 4 | 超级90 |
194 | | 10501 | 3 | 旧式长风零六 |
195 | | 10502 | 4 | 长风零六 |
196 | | 10503 | 5 | 不留行 |
197 | | 10521 | 3 | 旧式黑克勒科赫416 |
198 | | 10522 | 4 | 黑克勒科赫416 |
199 | | 10523 | 5 | 斯库拉 |
200 | | 11007 | 4 | 野兔 |
201 | | 11008 | 3 | 旧式金牛座曲线 |
202 | | 11009 | 3 | 旧式复仇女神 |
203 | | 11010 | 3 | 旧式格洛利娅 |
204 | | 11014 | 4 | 复仇女神 |
205 | | 11015 | 4 | 金牛座曲线 |
206 | | 11016 | 5 | 猎心者 |
207 | | 11017 | 3 | 旧式莫洛12 |
208 | | 11020 | 5 | 光学幻境 |
209 | | 11021 | 4 | 莫洛12 |
210 | | 11022 | 3 | 旧式野兔 |
211 | | 11023 | 4 | 格洛利娅 |
212 | | 11024 | 3 | 旧式佩切涅 |
213 | | 11026 | 4 | 佩切涅 |
214 | | 11030 | 3 | 旧式罗宾逊先进步枪 |
215 | | 11031 | 4 | 罗宾逊先进步枪 |
216 | | 11036 | 3 | 旧式卡拉什-阿尔法 |
217 | | 11037 | 4 | 卡拉什-阿尔法 |
218 | | 11038 | 5 | 游星 |
219 | | 11039 | 3 | 旧式黑科赫7 |
220 | | 11040 | 4 | 黑科赫7 |
221 | | 11042 | 3 | 旧式一九一式 |
222 | | 11043 | 4 | 一九一式 |
223 | | 11044 | 5 | 金石奏 |
224 | | 11045 | 3 | 旧式特殊用途自动型霰弹枪 |
225 | | 11046 | 4 | 特殊用途自动型霰弹枪 |
226 | | 11047 | 5 | 梅扎露娜 |
227 | | 11048 | 3 | 旧式纳甘左轮 |
228 | | 11049 | 4 | 纳甘左轮 |
229 | | 11051 | 3 | 旧式九五式 |
230 | | 11052 | 4 | 九五式 |
231 | | 11053 | 5 | 重弦 |
232 | | 11054 | 3 | 旧式格威尔36 |
233 | | 11055 | 4 | 格威尔36 |
234 | | 11056 | 5 | 女仆准则 |
--------------------------------------------------------------------------------
/app.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "gf2gacha/config"
7 | "gf2gacha/logger"
8 | "gf2gacha/logic"
9 | "gf2gacha/model"
10 | "gf2gacha/util"
11 | "github.com/elazarl/goproxy"
12 | "github.com/pkg/errors"
13 | "github.com/wailsapp/wails/v2/pkg/runtime"
14 | "net"
15 | "net/http"
16 | "os"
17 | "strings"
18 | "sync"
19 | )
20 |
21 | // App struct
22 | type App struct {
23 | ctx context.Context
24 | captureMutex sync.Mutex
25 | tcpPort int
26 | tcpListener *net.Listener
27 | MitmServer *http.Server
28 | }
29 |
30 | // NewApp creates a new App application struct
31 | func NewApp() *App {
32 | return &App{}
33 | }
34 |
35 | // startup is called when the app starts. The context is saved
36 | // so we can call the runtime methods
37 | func (a *App) startup(ctx context.Context) {
38 | a.ctx = ctx
39 | }
40 |
41 | func (a *App) GetLogInfo() model.LogInfo {
42 | info, err := util.GetLogInfo()
43 | if err != nil {
44 | logger.Logger.Error(err)
45 | return model.LogInfo{}
46 | }
47 | return info
48 | }
49 |
50 | func (a *App) GetUserList() []string {
51 | userList, err := logic.GetUserList()
52 | if err != nil {
53 | logger.Logger.Error(err)
54 | return nil
55 | }
56 | return userList
57 | }
58 |
59 | func (a *App) GetPoolInfo(uid string, poolType int64) model.Pool {
60 | pool, err := logic.GetPoolInfo(uid, poolType)
61 | if err != nil {
62 | logger.Logger.Error(err)
63 | return model.Pool{}
64 | }
65 | return pool
66 | }
67 |
68 | func (a *App) UpdatePoolInfo(isFull bool) ([]string, error) {
69 | messageList, err := logic.UpdatePoolInfo(isFull)
70 | if err != nil {
71 | logger.Logger.Error(err)
72 | return nil, err
73 | }
74 | return messageList, nil
75 | }
76 |
77 | func (a *App) MergeEreRecord(uid, typ string) (message string, err error) {
78 | if uid == "" {
79 | return "", errors.New("UID为空,请至少更新一次数据再进行合并")
80 | }
81 |
82 | var fileOption runtime.OpenDialogOptions
83 | switch strings.ToLower(typ) {
84 | case "json":
85 | fileOption.Title = "请选择Exilium Recruit Export的Json文件"
86 | fileOption.Filters = []runtime.FileFilter{{DisplayName: "EreJsonData", Pattern: "*.json"}}
87 | case "excel":
88 | fileOption.Title = "请选择Exilium Recruit Export的Excel文件"
89 | fileOption.Filters = []runtime.FileFilter{{DisplayName: "EreExcelData", Pattern: "*.xlsx"}}
90 | default:
91 | return "", errors.Errorf("unknown type: %s", typ)
92 | }
93 | erePath, err := runtime.OpenFileDialog(a.ctx, fileOption)
94 | if err != nil {
95 | logger.Logger.Error(err)
96 | return
97 | }
98 |
99 | if erePath == "" {
100 | return "", errors.New("用户取消导入")
101 | }
102 |
103 | err = logic.MergeEreRecord(uid, erePath, typ)
104 | if err != nil {
105 | logger.Logger.Error(err)
106 | return
107 | }
108 |
109 | message = "合并成功"
110 | return
111 | }
112 |
113 | func (a *App) ImportRawJson(uid string, isReverse bool) (message string, err error) {
114 | if uid == "" {
115 | return "", errors.New("UID为空,请至少更新一次数据再进行导出")
116 | }
117 |
118 | fileOption := runtime.OpenDialogOptions{
119 | Title: "请选择RawJson文件",
120 | Filters: []runtime.FileFilter{{DisplayName: "RawJsonData", Pattern: "*.json"}},
121 | }
122 | importFilePath, err := runtime.OpenFileDialog(a.ctx, fileOption)
123 | if err != nil {
124 | logger.Logger.Error(err)
125 | return
126 | }
127 |
128 | if importFilePath == "" {
129 | return "", errors.New("用户取消导入")
130 | }
131 |
132 | err = logic.ImportRawJson(uid, importFilePath, isReverse)
133 | if err != nil {
134 | logger.Logger.Error(err)
135 | return
136 | }
137 |
138 | message = "合并成功"
139 | return
140 | }
141 |
142 | func (a *App) ExportRawJson(uid string) (message string, err error) {
143 | if uid == "" {
144 | return "", errors.New("UID为空,请至少更新一次数据再进行导出")
145 | }
146 |
147 | fileOption := runtime.OpenDialogOptions{
148 | Title: "选择RawJson保存目录",
149 | }
150 | saveDir, err := runtime.OpenDirectoryDialog(a.ctx, fileOption)
151 | if err != nil {
152 | logger.Logger.Error(err)
153 | return
154 | }
155 |
156 | if saveDir == "" {
157 | return "", errors.New("用户取消导出")
158 | }
159 |
160 | err = logic.ExportRawJson(uid, saveDir)
161 | if err != nil {
162 | logger.Logger.Error(err)
163 | return
164 | }
165 |
166 | message = "导出成功"
167 | return
168 | }
169 |
170 | func (a *App) ImportMccExcel(uid string) (message string, err error) {
171 | if uid == "" {
172 | return "", errors.New("UID为空,请至少更新一次数据再进行导出")
173 | }
174 |
175 | fileOption := runtime.OpenDialogOptions{
176 | Title: "请选择MccExcel文件",
177 | Filters: []runtime.FileFilter{{DisplayName: "MccExcel", Pattern: "*.xlsx"}},
178 | }
179 | importFilePath, err := runtime.OpenFileDialog(a.ctx, fileOption)
180 | if err != nil {
181 | logger.Logger.Error(err)
182 | return
183 | }
184 |
185 | if importFilePath == "" {
186 | return "", errors.New("用户取消导入")
187 | }
188 |
189 | err = logic.ImportMccExcel(uid, importFilePath)
190 | if err != nil {
191 | logger.Logger.Error(err)
192 | return
193 | }
194 |
195 | message = "MccExcel合并成功"
196 | return
197 | }
198 |
199 | func (a *App) ExportMccExcel(uid string) (message string, err error) {
200 | if uid == "" {
201 | return "", errors.New("UID为空,请至少更新一次数据再进行导出")
202 | }
203 |
204 | fileOption := runtime.OpenDialogOptions{
205 | Title: "选择MccExcel保存目录",
206 | }
207 | saveDir, err := runtime.OpenDirectoryDialog(a.ctx, fileOption)
208 | if err != nil {
209 | logger.Logger.Error(err)
210 | return
211 | }
212 |
213 | if saveDir == "" {
214 | return "", errors.New("用户取消导出")
215 | }
216 |
217 | err = logic.ExportMccExcel(uid, saveDir)
218 | if err != nil {
219 | logger.Logger.Error(err)
220 | return
221 | }
222 |
223 | message = "MccExcel导出成功"
224 | return
225 | }
226 |
227 | func (a *App) HandleCommunityTasks() (messageList []string, err error) {
228 | messageList, err = logic.HandleCommunityTasks()
229 | if err != nil {
230 | logger.Logger.Error(err)
231 | return
232 | }
233 | return
234 | }
235 |
236 | func (a *App) GetCurrentVersion() string {
237 | return util.GetVersion()
238 | }
239 |
240 | func (a *App) GetLatestVersion() (string, error) {
241 | version, err := logic.GetLatestVersion()
242 | if err != nil {
243 | logger.Logger.Error(err)
244 | return "", err
245 | }
246 | return version, nil
247 | }
248 |
249 | func (a *App) UpdateTo(version string) (string, error) {
250 | err := logic.UpdateTo(version)
251 | if err != nil {
252 | logger.Logger.Error(err)
253 | return "", err
254 | }
255 | return "", nil
256 | }
257 |
258 | func (a *App) GetCommunityExchangeList() ([]model.CommunityExchangeList, error) {
259 | list, err := logic.GetCommunityExchangeList()
260 | if err != nil {
261 | logger.Logger.Error(err)
262 | return nil, err
263 | }
264 | return list, nil
265 | }
266 |
267 | func (a *App) GetSettingExchangeList() ([]int64, error) {
268 | if !config.IsSetExchangeList() {
269 | exchangeList, err := logic.GetCommunityExchangeList()
270 | if err != nil {
271 | logger.Logger.Error(err)
272 | return nil, err
273 | }
274 | var idList []int64
275 | for _, item := range exchangeList {
276 | idList = append(idList, item.Id)
277 | }
278 | err = config.SetExchangeList(idList)
279 | if err != nil {
280 | logger.Logger.Error(err)
281 | return nil, err
282 | }
283 | }
284 |
285 | return config.GetExchangeList(), nil
286 | }
287 |
288 | func (a *App) SaveSettingExchangeList(exchangeList []int64) error {
289 | return config.SetExchangeList(exchangeList)
290 | }
291 |
292 | func (a *App) GetSettingFont() (string, error) {
293 | return config.GetFont(), nil
294 | }
295 |
296 | func (a *App) SaveSettingFont(newFont string) error {
297 | return config.SetFont(newFont)
298 | }
299 |
300 | func (a *App) GetSettingLayout() (int64, error) {
301 | return config.GetLayout(), nil
302 | }
303 |
304 | func (a *App) SaveSettingLayout(layoutType int64) error {
305 | return config.SetLayout(layoutType)
306 | }
307 |
308 | func (a *App) CaptureStart() error {
309 | _, err := os.Stat("ca.crt")
310 | if err != nil {
311 | logger.Logger.Errorf("CA证书不存在:%v", err)
312 | err = util.GenCA()
313 | if err != nil {
314 | logger.Logger.Errorf("生成CA证书出错:%v", err)
315 | return err
316 | }
317 | }
318 |
319 | if !util.IsTrustedCA() {
320 | err = util.InstallCA()
321 | if err != nil {
322 | logger.Logger.Errorf("安装CA证书出错:%v", err)
323 | return err
324 | }
325 | }
326 |
327 | if a.tcpListener == nil {
328 | for port := 60000; port < 60010; port++ {
329 | listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
330 | if err != nil {
331 | logger.Logger.Error(err)
332 | continue
333 | }
334 |
335 | a.captureMutex.Lock()
336 | a.tcpPort = port
337 | a.tcpListener = &listener
338 | a.captureMutex.Unlock()
339 | logger.Logger.Infof("端口%d可用\n", port)
340 |
341 | break
342 | }
343 | }
344 |
345 | cert, err := util.ParseCA()
346 | if err != nil {
347 | logger.Logger.Errorf("读取CA证书出错:%v", err)
348 | return err
349 | }
350 |
351 | customCaMitm := &goproxy.ConnectAction{Action: goproxy.ConnectMitm, TLSConfig: goproxy.TLSConfigFromCA(cert)}
352 | var customAlwaysMitm goproxy.FuncHttpsHandler = func(host string, ctx *goproxy.ProxyCtx) (*goproxy.ConnectAction, string) {
353 | return customCaMitm, host
354 | }
355 |
356 | proxy := goproxy.NewProxyHttpServer()
357 | cond := goproxy.DstHostIs("gf2-gacha-record.sunborngame.com")
358 |
359 | proxy.OnRequest(cond).HandleConnect(customAlwaysMitm)
360 | proxy.OnRequest(cond).DoFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
361 | if req.URL.Path == "/list" {
362 | gachaUrl := req.URL.String()
363 | accessToken := req.Header.Get("Authorization")
364 |
365 | userInfo, err := logic.GetUserInfoFromBBS(accessToken)
366 | if err != nil {
367 | logger.Logger.Errorf("未从社区获取到用户信息:%v", err)
368 | return req, nil
369 | }
370 |
371 | err = logic.AppendLog(accessToken, userInfo.User.GameUid, gachaUrl)
372 | if err != nil {
373 | logger.Logger.Errorf("追加游戏日志失败:%v", err)
374 | return req, nil
375 | }
376 |
377 | logger.Logger.Infof("uid: %d\n", userInfo.User.GameUid)
378 | logger.Logger.Infof("gachaUrl: %s\n", gachaUrl)
379 | logger.Logger.Infof("accessToken: %s\n", accessToken)
380 |
381 | runtime.EventsEmit(a.ctx, "captureSuccess")
382 | }
383 |
384 | return req, nil
385 | })
386 |
387 | //启动监听
388 | if a.MitmServer == nil {
389 | a.captureMutex.Lock()
390 | a.MitmServer = &http.Server{Handler: proxy}
391 |
392 | go func(mitmServer *http.Server, tcpListener *net.Listener) {
393 | logger.Logger.Info("捕获协程启动")
394 | err := mitmServer.Serve(*tcpListener)
395 | if err != nil {
396 | if errors.Is(err, http.ErrServerClosed) {
397 | logger.Logger.Info("捕获正常停止")
398 | return
399 | } else {
400 | logger.Logger.Errorf("捕获异常停止: %v", err)
401 | }
402 | a.captureMutex.Lock()
403 | a.tcpListener = nil
404 | a.MitmServer = nil
405 | a.captureMutex.Unlock()
406 | }
407 | }(a.MitmServer, a.tcpListener)
408 |
409 | err = util.EnableSysProxy(a.tcpPort)
410 | if err != nil {
411 | logger.Logger.Errorf("设置代理信息出错:%v", err)
412 | return err
413 | }
414 |
415 | a.captureMutex.Unlock()
416 | }
417 |
418 | return nil
419 | }
420 |
421 | func (a *App) CaptureClose() error {
422 | a.captureMutex.Lock()
423 | defer a.captureMutex.Unlock()
424 |
425 | if a.MitmServer != nil {
426 | err := a.MitmServer.Shutdown(context.Background())
427 | if err != nil && !errors.Is(err, http.ErrServerClosed) {
428 | logger.Logger.Error(err)
429 | return err
430 | }
431 | a.tcpListener = nil
432 | a.MitmServer = nil
433 | }
434 |
435 | err := util.DisableSysProxy()
436 | if err != nil {
437 | logger.Logger.Errorf("还原代理信息出错:%v", err)
438 | return err
439 | }
440 |
441 | return nil
442 | }
443 |
--------------------------------------------------------------------------------
/build/appicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MatchaCabin/gf2gacha/98957835ff3b0b90afa5af33d84bb44ba4e4f34d/build/appicon.png
--------------------------------------------------------------------------------
/config/init.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "fmt"
5 | "gf2gacha/logger"
6 | "github.com/pkg/errors"
7 | "github.com/spf13/viper"
8 | )
9 |
10 | func init() {
11 | viper.SetConfigName("config")
12 | viper.SetConfigType("yaml")
13 | viper.AddConfigPath(".")
14 | if err := viper.ReadInConfig(); err != nil {
15 | if errors.As(err, &viper.ConfigFileNotFoundError{}) {
16 | logger.Logger.Warnf("配置文件不存在,将自动创建默认配置文件")
17 | err = viper.SafeWriteConfig()
18 | if err != nil {
19 | logger.Logger.Panic(err)
20 | }
21 | } else {
22 | logger.Logger.Panic(err)
23 | }
24 | }
25 | }
26 |
27 | func GetGameDataDir() string {
28 | return viper.GetString("GameDataDir")
29 | }
30 |
31 | func SetGameDataDir(gameDataDir string) error {
32 | viper.Set("GameDataDir", gameDataDir)
33 | return viper.WriteConfig()
34 | }
35 |
36 | func IsSetGameDataDir() bool {
37 | return viper.IsSet("GameDataDir")
38 | }
39 |
40 | func GetWebToken(uid string) string {
41 | return viper.GetString(fmt.Sprintf("%s.webToken", uid))
42 | }
43 |
44 | func SetWebToken(uid, token string) error {
45 | viper.Set(fmt.Sprintf("%s.webToken", uid), token)
46 | return viper.WriteConfig()
47 | }
48 |
49 | func IsSetExchangeList() bool {
50 | return viper.IsSet("exchangeList")
51 | }
52 |
53 | func GetExchangeList() []int64 {
54 | intList := viper.GetIntSlice("exchangeList")
55 | var int64List []int64
56 | for i := range intList {
57 | int64List = append(int64List, int64(intList[i]))
58 | }
59 |
60 | return int64List
61 | }
62 |
63 | func SetExchangeList(exchangeList []int64) error {
64 | viper.Set("exchangeList", exchangeList)
65 | return viper.WriteConfig()
66 | }
67 |
68 | func GetFont() string {
69 | return viper.GetString("font")
70 | }
71 |
72 | func SetFont(newFont string) error {
73 | viper.Set("font", newFont)
74 | return viper.WriteConfig()
75 | }
76 |
77 | func GetLayout() int64 {
78 | return viper.GetInt64("layout")
79 | }
80 |
81 | func SetLayout(layoutType int64) error {
82 | viper.Set("layout", layoutType)
83 | return viper.WriteConfig()
84 | }
85 |
--------------------------------------------------------------------------------
/frontend/auto-imports.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | /* prettier-ignore */
3 | // @ts-nocheck
4 | // noinspection JSUnusedGlobalSymbols
5 | // Generated by unplugin-auto-import
6 | // biome-ignore lint: disable
7 | export {}
8 | declare global {
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/frontend/components.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | // @ts-nocheck
3 | // Generated by unplugin-vue-components
4 | // Read more: https://github.com/vuejs/core/pull/3399
5 | // biome-ignore lint: disable
6 | export {}
7 |
8 | /* prettier-ignore */
9 | declare module 'vue' {
10 | export interface GlobalComponents {
11 | CaptureDialog: typeof import('./src/components/CaptureDialog.vue')['default']
12 | ElAlert: typeof import('element-plus/es')['ElAlert']
13 | ElButton: typeof import('element-plus/es')['ElButton']
14 | ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
15 | ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
16 | ElDialog: typeof import('element-plus/es')['ElDialog']
17 | ElDropdown: typeof import('element-plus/es')['ElDropdown']
18 | ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
19 | ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
20 | ElInput: typeof import('element-plus/es')['ElInput']
21 | ElOption: typeof import('element-plus/es')['ElOption']
22 | ElSelect: typeof import('element-plus/es')['ElSelect']
23 | ElTag: typeof import('element-plus/es')['ElTag']
24 | ElTooltip: typeof import('element-plus/es')['ElTooltip']
25 | PoolCard: typeof import('./src/components/PoolCard.vue')['default']
26 | SettingDialog: typeof import('./src/components/SettingDialog.vue')['default']
27 | }
28 | export interface ComponentCustomProperties {
29 | vLoading: typeof import('element-plus/es')['ElLoadingDirective']
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/frontend/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | gf2gacha
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "frontend",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vue-tsc -b && vite build",
9 | "preview": "vite preview"
10 | },
11 | "dependencies": {
12 | "@element-plus/icons-vue": "^2.3.1",
13 | "@tailwindcss/vite": "^4.0.11",
14 | "element-plus": "^2.9.5",
15 | "html2canvas-pro": "^1.5.8",
16 | "pinia": "^3.0.1",
17 | "tailwindcss": "^4.0.11",
18 | "vue": "^3.5.13",
19 | "vue-echarts": "^7.0.3"
20 | },
21 | "devDependencies": {
22 | "@vitejs/plugin-vue": "^5.2.1",
23 | "@vue/tsconfig": "^0.7.0",
24 | "typescript": "~5.7.2",
25 | "unplugin-auto-import": "^19.1.1",
26 | "unplugin-vue-components": "^28.4.1",
27 | "vite": "^6.2.0",
28 | "vue-tsc": "^2.2.4"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/frontend/package.json.md5:
--------------------------------------------------------------------------------
1 | da8c951790b55cdcd5fc4a2987b3eef9
--------------------------------------------------------------------------------
/frontend/src/App.vue:
--------------------------------------------------------------------------------
1 |
198 |
199 |
200 |
201 |
202 |
203 | 增量更新
204 | 全量更新
205 |
206 | 导入导出
207 |
208 |
209 | 导入EreJson
210 | 导入EreExcel
211 |
212 | 导入RawJson
213 |
214 |
215 | 导入RawJson(时间倒序)
216 |
217 | 导入MccExcel
218 | 导出RawJson
219 | 导出MccExcel
220 |
221 |
222 |
223 | 捕获信息
224 |
225 |
226 |
一键社区
227 |
UID:
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
238 |
239 |
240 | 关于
241 |
242 |
243 |
244 |
项目地址
245 |
https://github.com/MatchaCabin/gf2gacha
246 |
247 |
248 |
UID
249 |
250 |
251 |
252 |
253 |
抽卡链接
254 |
255 |
256 |
257 |
258 |
AccessToken
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
--------------------------------------------------------------------------------
/frontend/src/assets/fonts/ChillRoundM.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MatchaCabin/gf2gacha/98957835ff3b0b90afa5af33d84bb44ba4e4f34d/frontend/src/assets/fonts/ChillRoundM.otf
--------------------------------------------------------------------------------
/frontend/src/components/CaptureDialog.vue:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
20 |
21 | 通过抓包捕获用户信息
22 |
23 |
24 |
首次打开这个界面会弹出安装CA证书
25 |
若程序目录下无CA证书则会自动生成私有CA证书,可使用自己的CA证书替换
26 |
不建议使用公用CA证书,存在安全隐患
27 |
1.保持当前界面打开状态
28 |
2.进入游戏打开招募界面
29 |
3.点击任意卡池的访问详情
30 |
若成功获取信息,当前界面会自动关闭
31 |
32 |
33 |
--------------------------------------------------------------------------------
/frontend/src/components/PoolCard.vue:
--------------------------------------------------------------------------------
1 |
122 |
123 |
124 |
126 |
127 |
128 |
129 |
{{ title() }}
130 |
131 |
132 |
133 |
134 |
一共 {{ pool.gachaCount }} 抽,已垫 {{ pool.storedCount }} 抽
135 |
五星: {{ pool.rank5Count }} [{{ pool.gachaCount > 0 ? Math.round(pool.rank5Count * 10000 / pool.gachaCount) / 100 + '%' : '0%' }}]
136 |
四星: {{ pool.rank4Count }} [{{ pool.gachaCount > 0 ? Math.round(pool.rank4Count * 10000 / pool.gachaCount) / 100 + '%' : '0%' }}]
137 |
三星: {{ pool.rank3Count }} [{{ pool.gachaCount > 0 ? Math.round(pool.rank3Count * 10000 / pool.gachaCount) / 100 + '%' : '0%' }}]
138 |
平均出金抽数:{{ pool.rank5Count > 0 ? ((pool.gachaCount - pool.storedCount) / pool.rank5Count).toFixed(1) : '无' }}
139 |
平均出Up抽数:{{ pool.rank5Count - pool.loseCount > 0 ? ((pool.gachaCount - pool.storedCount) / (pool.rank5Count - pool.loseCount)).toFixed(1) : '无' }}
140 |
歪率: {{ pool.rank5Count > 0 ? Math.round(pool.loseCount * 10000 / (pool.rank5Count - pool.guaranteesCount)) / 100 + '%' : '0%' }}
141 |
142 |
143 |
五星抽卡记录:
144 |
{{ isDesc ? '倒序' : '正序' }}
145 |
146 |
147 |
148 | {{ record.name }}
149 | 「{{ record.count }}」
150 | 歪
151 |
152 |
153 |
154 |
--------------------------------------------------------------------------------
/frontend/src/components/SettingDialog.vue:
--------------------------------------------------------------------------------
1 |
68 |
69 |
70 |
71 |
72 | 设置
73 |
74 |
75 |
76 |
社区兑换
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
字体
85 |
86 |
87 |
88 |
89 |
90 |
91 |
布局
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/frontend/src/main.ts:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue'
2 | import { createPinia } from 'pinia'
3 | import './style.css'
4 | import 'element-plus/theme-chalk/el-loading.css'
5 | import 'element-plus/theme-chalk/el-message-box.css'
6 | import App from './App.vue'
7 |
8 | const pinia = createPinia()
9 | createApp(App).use(pinia).mount('#app')
10 |
--------------------------------------------------------------------------------
/frontend/src/stores/layout.ts:
--------------------------------------------------------------------------------
1 | import { defineStore } from 'pinia'
2 | import {ref} from "vue";
3 |
4 | export const useLayoutStore = defineStore('counter', () => {
5 | const layoutType = ref(0)
6 |
7 | return { layoutType }
8 | })
--------------------------------------------------------------------------------
/frontend/src/style.css:
--------------------------------------------------------------------------------
1 | @import "tailwindcss";
2 |
3 | body {
4 | margin: 0;
5 | }
6 |
7 | @font-face {
8 | font-family: "ChillRoundM";
9 | src: local(""),
10 | url("assets/fonts/ChillRoundM.otf");
11 | }
--------------------------------------------------------------------------------
/frontend/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/frontend/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@vue/tsconfig/tsconfig.dom.json",
3 | "compilerOptions": {
4 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
5 |
6 | /* Linting */
7 | "strict": true,
8 | "noUnusedLocals": true,
9 | "noUnusedParameters": true,
10 | "noFallthroughCasesInSwitch": true,
11 | "noUncheckedSideEffectImports": true
12 | },
13 | "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
14 | }
15 |
--------------------------------------------------------------------------------
/frontend/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "files": [],
3 | "references": [
4 | { "path": "./tsconfig.app.json" },
5 | { "path": "./tsconfig.node.json" }
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/frontend/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
4 | "target": "ES2022",
5 | "lib": ["ES2023"],
6 | "module": "ESNext",
7 | "skipLibCheck": true,
8 |
9 | /* Bundler mode */
10 | "moduleResolution": "bundler",
11 | "allowImportingTsExtensions": true,
12 | "isolatedModules": true,
13 | "moduleDetection": "force",
14 | "noEmit": true,
15 |
16 | /* Linting */
17 | "strict": true,
18 | "noUnusedLocals": true,
19 | "noUnusedParameters": true,
20 | "noFallthroughCasesInSwitch": true,
21 | "noUncheckedSideEffectImports": true
22 | },
23 | "include": ["vite.config.ts"]
24 | }
25 |
--------------------------------------------------------------------------------
/frontend/vite.config.ts:
--------------------------------------------------------------------------------
1 | import {defineConfig} from 'vite'
2 | import vue from '@vitejs/plugin-vue'
3 | import AutoImport from 'unplugin-auto-import/vite'
4 | import Components from 'unplugin-vue-components/vite'
5 | import {ElementPlusResolver} from 'unplugin-vue-components/resolvers'
6 | import tailwindcss from '@tailwindcss/vite'
7 |
8 | // https://vite.dev/config/
9 | export default defineConfig({
10 | plugins: [
11 | vue(),
12 | tailwindcss(),
13 | AutoImport({
14 | resolvers: [ElementPlusResolver()],
15 | }),
16 | Components({
17 | resolvers: [ElementPlusResolver()],
18 | }),
19 | ],
20 | })
21 |
--------------------------------------------------------------------------------
/frontend/wailsjs/go/main/App.d.ts:
--------------------------------------------------------------------------------
1 | // Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
2 | // This file is automatically generated. DO NOT EDIT
3 | import {model} from '../models';
4 |
5 | export function CaptureClose():Promise;
6 |
7 | export function CaptureStart():Promise;
8 |
9 | export function ExportMccExcel(arg1:string):Promise;
10 |
11 | export function ExportRawJson(arg1:string):Promise;
12 |
13 | export function GetCommunityExchangeList():Promise>;
14 |
15 | export function GetCurrentVersion():Promise;
16 |
17 | export function GetLatestVersion():Promise;
18 |
19 | export function GetLogInfo():Promise;
20 |
21 | export function GetPoolInfo(arg1:string,arg2:number):Promise;
22 |
23 | export function GetSettingExchangeList():Promise>;
24 |
25 | export function GetSettingFont():Promise;
26 |
27 | export function GetSettingLayout():Promise;
28 |
29 | export function GetUserList():Promise>;
30 |
31 | export function HandleCommunityTasks():Promise>;
32 |
33 | export function ImportMccExcel(arg1:string):Promise;
34 |
35 | export function ImportRawJson(arg1:string,arg2:boolean):Promise;
36 |
37 | export function MergeEreRecord(arg1:string,arg2:string):Promise;
38 |
39 | export function SaveSettingExchangeList(arg1:Array):Promise;
40 |
41 | export function SaveSettingFont(arg1:string):Promise;
42 |
43 | export function SaveSettingLayout(arg1:number):Promise;
44 |
45 | export function UpdatePoolInfo(arg1:boolean):Promise>;
46 |
47 | export function UpdateTo(arg1:string):Promise;
48 |
--------------------------------------------------------------------------------
/frontend/wailsjs/go/main/App.js:
--------------------------------------------------------------------------------
1 | // @ts-check
2 | // Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
3 | // This file is automatically generated. DO NOT EDIT
4 |
5 | export function CaptureClose() {
6 | return window['go']['main']['App']['CaptureClose']();
7 | }
8 |
9 | export function CaptureStart() {
10 | return window['go']['main']['App']['CaptureStart']();
11 | }
12 |
13 | export function ExportMccExcel(arg1) {
14 | return window['go']['main']['App']['ExportMccExcel'](arg1);
15 | }
16 |
17 | export function ExportRawJson(arg1) {
18 | return window['go']['main']['App']['ExportRawJson'](arg1);
19 | }
20 |
21 | export function GetCommunityExchangeList() {
22 | return window['go']['main']['App']['GetCommunityExchangeList']();
23 | }
24 |
25 | export function GetCurrentVersion() {
26 | return window['go']['main']['App']['GetCurrentVersion']();
27 | }
28 |
29 | export function GetLatestVersion() {
30 | return window['go']['main']['App']['GetLatestVersion']();
31 | }
32 |
33 | export function GetLogInfo() {
34 | return window['go']['main']['App']['GetLogInfo']();
35 | }
36 |
37 | export function GetPoolInfo(arg1, arg2) {
38 | return window['go']['main']['App']['GetPoolInfo'](arg1, arg2);
39 | }
40 |
41 | export function GetSettingExchangeList() {
42 | return window['go']['main']['App']['GetSettingExchangeList']();
43 | }
44 |
45 | export function GetSettingFont() {
46 | return window['go']['main']['App']['GetSettingFont']();
47 | }
48 |
49 | export function GetSettingLayout() {
50 | return window['go']['main']['App']['GetSettingLayout']();
51 | }
52 |
53 | export function GetUserList() {
54 | return window['go']['main']['App']['GetUserList']();
55 | }
56 |
57 | export function HandleCommunityTasks() {
58 | return window['go']['main']['App']['HandleCommunityTasks']();
59 | }
60 |
61 | export function ImportMccExcel(arg1) {
62 | return window['go']['main']['App']['ImportMccExcel'](arg1);
63 | }
64 |
65 | export function ImportRawJson(arg1, arg2) {
66 | return window['go']['main']['App']['ImportRawJson'](arg1, arg2);
67 | }
68 |
69 | export function MergeEreRecord(arg1, arg2) {
70 | return window['go']['main']['App']['MergeEreRecord'](arg1, arg2);
71 | }
72 |
73 | export function SaveSettingExchangeList(arg1) {
74 | return window['go']['main']['App']['SaveSettingExchangeList'](arg1);
75 | }
76 |
77 | export function SaveSettingFont(arg1) {
78 | return window['go']['main']['App']['SaveSettingFont'](arg1);
79 | }
80 |
81 | export function SaveSettingLayout(arg1) {
82 | return window['go']['main']['App']['SaveSettingLayout'](arg1);
83 | }
84 |
85 | export function UpdatePoolInfo(arg1) {
86 | return window['go']['main']['App']['UpdatePoolInfo'](arg1);
87 | }
88 |
89 | export function UpdateTo(arg1) {
90 | return window['go']['main']['App']['UpdateTo'](arg1);
91 | }
92 |
--------------------------------------------------------------------------------
/frontend/wailsjs/go/models.ts:
--------------------------------------------------------------------------------
1 | export namespace model {
2 |
3 | export class CommunityExchangeList {
4 | id: number;
5 | name: string;
6 |
7 | static createFrom(source: any = {}) {
8 | return new CommunityExchangeList(source);
9 | }
10 |
11 | constructor(source: any = {}) {
12 | if ('string' === typeof source) source = JSON.parse(source);
13 | this.id = source["id"];
14 | this.name = source["name"];
15 | }
16 | }
17 | export class DisplayRecord {
18 | id: number;
19 | name: string;
20 | lose: boolean;
21 | count: number;
22 |
23 | static createFrom(source: any = {}) {
24 | return new DisplayRecord(source);
25 | }
26 |
27 | constructor(source: any = {}) {
28 | if ('string' === typeof source) source = JSON.parse(source);
29 | this.id = source["id"];
30 | this.name = source["name"];
31 | this.lose = source["lose"];
32 | this.count = source["count"];
33 | }
34 | }
35 | export class LogInfo {
36 | tablePath: string;
37 | accessToken: string;
38 | uid: string;
39 | gachaUrl: string;
40 |
41 | static createFrom(source: any = {}) {
42 | return new LogInfo(source);
43 | }
44 |
45 | constructor(source: any = {}) {
46 | if ('string' === typeof source) source = JSON.parse(source);
47 | this.tablePath = source["tablePath"];
48 | this.accessToken = source["accessToken"];
49 | this.uid = source["uid"];
50 | this.gachaUrl = source["gachaUrl"];
51 | }
52 | }
53 | export class Pool {
54 | poolType: number;
55 | gachaCount: number;
56 | loseCount: number;
57 | guaranteesCount: number;
58 | rank5Count: number;
59 | rank4Count: number;
60 | rank3Count: number;
61 | storedCount: number;
62 | recordList: DisplayRecord[];
63 |
64 | static createFrom(source: any = {}) {
65 | return new Pool(source);
66 | }
67 |
68 | constructor(source: any = {}) {
69 | if ('string' === typeof source) source = JSON.parse(source);
70 | this.poolType = source["poolType"];
71 | this.gachaCount = source["gachaCount"];
72 | this.loseCount = source["loseCount"];
73 | this.guaranteesCount = source["guaranteesCount"];
74 | this.rank5Count = source["rank5Count"];
75 | this.rank4Count = source["rank4Count"];
76 | this.rank3Count = source["rank3Count"];
77 | this.storedCount = source["storedCount"];
78 | this.recordList = this.convertValues(source["recordList"], DisplayRecord);
79 | }
80 |
81 | convertValues(a: any, classs: any, asMap: boolean = false): any {
82 | if (!a) {
83 | return a;
84 | }
85 | if (a.slice && a.map) {
86 | return (a as any[]).map(elem => this.convertValues(elem, classs));
87 | } else if ("object" === typeof a) {
88 | if (asMap) {
89 | for (const key of Object.keys(a)) {
90 | a[key] = new classs(a[key]);
91 | }
92 | return a;
93 | }
94 | return new classs(a);
95 | }
96 | return a;
97 | }
98 | }
99 |
100 | }
101 |
102 |
--------------------------------------------------------------------------------
/frontend/wailsjs/runtime/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@wailsapp/runtime",
3 | "version": "2.0.0",
4 | "description": "Wails Javascript runtime library",
5 | "main": "runtime.js",
6 | "types": "runtime.d.ts",
7 | "scripts": {
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/wailsapp/wails.git"
12 | },
13 | "keywords": [
14 | "Wails",
15 | "Javascript",
16 | "Go"
17 | ],
18 | "author": "Lea Anthony ",
19 | "license": "MIT",
20 | "bugs": {
21 | "url": "https://github.com/wailsapp/wails/issues"
22 | },
23 | "homepage": "https://github.com/wailsapp/wails#readme"
24 | }
25 |
--------------------------------------------------------------------------------
/frontend/wailsjs/runtime/runtime.d.ts:
--------------------------------------------------------------------------------
1 | /*
2 | _ __ _ __
3 | | | / /___ _(_) /____
4 | | | /| / / __ `/ / / ___/
5 | | |/ |/ / /_/ / / (__ )
6 | |__/|__/\__,_/_/_/____/
7 | The electron alternative for Go
8 | (c) Lea Anthony 2019-present
9 | */
10 |
11 | export interface Position {
12 | x: number;
13 | y: number;
14 | }
15 |
16 | export interface Size {
17 | w: number;
18 | h: number;
19 | }
20 |
21 | export interface Screen {
22 | isCurrent: boolean;
23 | isPrimary: boolean;
24 | width : number
25 | height : number
26 | }
27 |
28 | // Environment information such as platform, buildtype, ...
29 | export interface EnvironmentInfo {
30 | buildType: string;
31 | platform: string;
32 | arch: string;
33 | }
34 |
35 | // [EventsEmit](https://wails.io/docs/reference/runtime/events#eventsemit)
36 | // emits the given event. Optional data may be passed with the event.
37 | // This will trigger any event listeners.
38 | export function EventsEmit(eventName: string, ...data: any): void;
39 |
40 | // [EventsOn](https://wails.io/docs/reference/runtime/events#eventson) sets up a listener for the given event name.
41 | export function EventsOn(eventName: string, callback: (...data: any) => void): () => void;
42 |
43 | // [EventsOnMultiple](https://wails.io/docs/reference/runtime/events#eventsonmultiple)
44 | // sets up a listener for the given event name, but will only trigger a given number times.
45 | export function EventsOnMultiple(eventName: string, callback: (...data: any) => void, maxCallbacks: number): () => void;
46 |
47 | // [EventsOnce](https://wails.io/docs/reference/runtime/events#eventsonce)
48 | // sets up a listener for the given event name, but will only trigger once.
49 | export function EventsOnce(eventName: string, callback: (...data: any) => void): () => void;
50 |
51 | // [EventsOff](https://wails.io/docs/reference/runtime/events#eventsoff)
52 | // unregisters the listener for the given event name.
53 | export function EventsOff(eventName: string, ...additionalEventNames: string[]): void;
54 |
55 | // [EventsOffAll](https://wails.io/docs/reference/runtime/events#eventsoffall)
56 | // unregisters all listeners.
57 | export function EventsOffAll(): void;
58 |
59 | // [LogPrint](https://wails.io/docs/reference/runtime/log#logprint)
60 | // logs the given message as a raw message
61 | export function LogPrint(message: string): void;
62 |
63 | // [LogTrace](https://wails.io/docs/reference/runtime/log#logtrace)
64 | // logs the given message at the `trace` log level.
65 | export function LogTrace(message: string): void;
66 |
67 | // [LogDebug](https://wails.io/docs/reference/runtime/log#logdebug)
68 | // logs the given message at the `debug` log level.
69 | export function LogDebug(message: string): void;
70 |
71 | // [LogError](https://wails.io/docs/reference/runtime/log#logerror)
72 | // logs the given message at the `error` log level.
73 | export function LogError(message: string): void;
74 |
75 | // [LogFatal](https://wails.io/docs/reference/runtime/log#logfatal)
76 | // logs the given message at the `fatal` log level.
77 | // The application will quit after calling this method.
78 | export function LogFatal(message: string): void;
79 |
80 | // [LogInfo](https://wails.io/docs/reference/runtime/log#loginfo)
81 | // logs the given message at the `info` log level.
82 | export function LogInfo(message: string): void;
83 |
84 | // [LogWarning](https://wails.io/docs/reference/runtime/log#logwarning)
85 | // logs the given message at the `warning` log level.
86 | export function LogWarning(message: string): void;
87 |
88 | // [WindowReload](https://wails.io/docs/reference/runtime/window#windowreload)
89 | // Forces a reload by the main application as well as connected browsers.
90 | export function WindowReload(): void;
91 |
92 | // [WindowReloadApp](https://wails.io/docs/reference/runtime/window#windowreloadapp)
93 | // Reloads the application frontend.
94 | export function WindowReloadApp(): void;
95 |
96 | // [WindowSetAlwaysOnTop](https://wails.io/docs/reference/runtime/window#windowsetalwaysontop)
97 | // Sets the window AlwaysOnTop or not on top.
98 | export function WindowSetAlwaysOnTop(b: boolean): void;
99 |
100 | // [WindowSetSystemDefaultTheme](https://wails.io/docs/next/reference/runtime/window#windowsetsystemdefaulttheme)
101 | // *Windows only*
102 | // Sets window theme to system default (dark/light).
103 | export function WindowSetSystemDefaultTheme(): void;
104 |
105 | // [WindowSetLightTheme](https://wails.io/docs/next/reference/runtime/window#windowsetlighttheme)
106 | // *Windows only*
107 | // Sets window to light theme.
108 | export function WindowSetLightTheme(): void;
109 |
110 | // [WindowSetDarkTheme](https://wails.io/docs/next/reference/runtime/window#windowsetdarktheme)
111 | // *Windows only*
112 | // Sets window to dark theme.
113 | export function WindowSetDarkTheme(): void;
114 |
115 | // [WindowCenter](https://wails.io/docs/reference/runtime/window#windowcenter)
116 | // Centers the window on the monitor the window is currently on.
117 | export function WindowCenter(): void;
118 |
119 | // [WindowSetTitle](https://wails.io/docs/reference/runtime/window#windowsettitle)
120 | // Sets the text in the window title bar.
121 | export function WindowSetTitle(title: string): void;
122 |
123 | // [WindowFullscreen](https://wails.io/docs/reference/runtime/window#windowfullscreen)
124 | // Makes the window full screen.
125 | export function WindowFullscreen(): void;
126 |
127 | // [WindowUnfullscreen](https://wails.io/docs/reference/runtime/window#windowunfullscreen)
128 | // Restores the previous window dimensions and position prior to full screen.
129 | export function WindowUnfullscreen(): void;
130 |
131 | // [WindowIsFullscreen](https://wails.io/docs/reference/runtime/window#windowisfullscreen)
132 | // Returns the state of the window, i.e. whether the window is in full screen mode or not.
133 | export function WindowIsFullscreen(): Promise;
134 |
135 | // [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize)
136 | // Sets the width and height of the window.
137 | export function WindowSetSize(width: number, height: number): void;
138 |
139 | // [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize)
140 | // Gets the width and height of the window.
141 | export function WindowGetSize(): Promise;
142 |
143 | // [WindowSetMaxSize](https://wails.io/docs/reference/runtime/window#windowsetmaxsize)
144 | // Sets the maximum window size. Will resize the window if the window is currently larger than the given dimensions.
145 | // Setting a size of 0,0 will disable this constraint.
146 | export function WindowSetMaxSize(width: number, height: number): void;
147 |
148 | // [WindowSetMinSize](https://wails.io/docs/reference/runtime/window#windowsetminsize)
149 | // Sets the minimum window size. Will resize the window if the window is currently smaller than the given dimensions.
150 | // Setting a size of 0,0 will disable this constraint.
151 | export function WindowSetMinSize(width: number, height: number): void;
152 |
153 | // [WindowSetPosition](https://wails.io/docs/reference/runtime/window#windowsetposition)
154 | // Sets the window position relative to the monitor the window is currently on.
155 | export function WindowSetPosition(x: number, y: number): void;
156 |
157 | // [WindowGetPosition](https://wails.io/docs/reference/runtime/window#windowgetposition)
158 | // Gets the window position relative to the monitor the window is currently on.
159 | export function WindowGetPosition(): Promise;
160 |
161 | // [WindowHide](https://wails.io/docs/reference/runtime/window#windowhide)
162 | // Hides the window.
163 | export function WindowHide(): void;
164 |
165 | // [WindowShow](https://wails.io/docs/reference/runtime/window#windowshow)
166 | // Shows the window, if it is currently hidden.
167 | export function WindowShow(): void;
168 |
169 | // [WindowMaximise](https://wails.io/docs/reference/runtime/window#windowmaximise)
170 | // Maximises the window to fill the screen.
171 | export function WindowMaximise(): void;
172 |
173 | // [WindowToggleMaximise](https://wails.io/docs/reference/runtime/window#windowtogglemaximise)
174 | // Toggles between Maximised and UnMaximised.
175 | export function WindowToggleMaximise(): void;
176 |
177 | // [WindowUnmaximise](https://wails.io/docs/reference/runtime/window#windowunmaximise)
178 | // Restores the window to the dimensions and position prior to maximising.
179 | export function WindowUnmaximise(): void;
180 |
181 | // [WindowIsMaximised](https://wails.io/docs/reference/runtime/window#windowismaximised)
182 | // Returns the state of the window, i.e. whether the window is maximised or not.
183 | export function WindowIsMaximised(): Promise;
184 |
185 | // [WindowMinimise](https://wails.io/docs/reference/runtime/window#windowminimise)
186 | // Minimises the window.
187 | export function WindowMinimise(): void;
188 |
189 | // [WindowUnminimise](https://wails.io/docs/reference/runtime/window#windowunminimise)
190 | // Restores the window to the dimensions and position prior to minimising.
191 | export function WindowUnminimise(): void;
192 |
193 | // [WindowIsMinimised](https://wails.io/docs/reference/runtime/window#windowisminimised)
194 | // Returns the state of the window, i.e. whether the window is minimised or not.
195 | export function WindowIsMinimised(): Promise;
196 |
197 | // [WindowIsNormal](https://wails.io/docs/reference/runtime/window#windowisnormal)
198 | // Returns the state of the window, i.e. whether the window is normal or not.
199 | export function WindowIsNormal(): Promise;
200 |
201 | // [WindowSetBackgroundColour](https://wails.io/docs/reference/runtime/window#windowsetbackgroundcolour)
202 | // Sets the background colour of the window to the given RGBA colour definition. This colour will show through for all transparent pixels.
203 | export function WindowSetBackgroundColour(R: number, G: number, B: number, A: number): void;
204 |
205 | // [ScreenGetAll](https://wails.io/docs/reference/runtime/window#screengetall)
206 | // Gets the all screens. Call this anew each time you want to refresh data from the underlying windowing system.
207 | export function ScreenGetAll(): Promise;
208 |
209 | // [BrowserOpenURL](https://wails.io/docs/reference/runtime/browser#browseropenurl)
210 | // Opens the given URL in the system browser.
211 | export function BrowserOpenURL(url: string): void;
212 |
213 | // [Environment](https://wails.io/docs/reference/runtime/intro#environment)
214 | // Returns information about the environment
215 | export function Environment(): Promise;
216 |
217 | // [Quit](https://wails.io/docs/reference/runtime/intro#quit)
218 | // Quits the application.
219 | export function Quit(): void;
220 |
221 | // [Hide](https://wails.io/docs/reference/runtime/intro#hide)
222 | // Hides the application.
223 | export function Hide(): void;
224 |
225 | // [Show](https://wails.io/docs/reference/runtime/intro#show)
226 | // Shows the application.
227 | export function Show(): void;
228 |
229 | // [ClipboardGetText](https://wails.io/docs/reference/runtime/clipboard#clipboardgettext)
230 | // Returns the current text stored on clipboard
231 | export function ClipboardGetText(): Promise;
232 |
233 | // [ClipboardSetText](https://wails.io/docs/reference/runtime/clipboard#clipboardsettext)
234 | // Sets a text on the clipboard
235 | export function ClipboardSetText(text: string): Promise;
236 |
237 | // [OnFileDrop](https://wails.io/docs/reference/runtime/draganddrop#onfiledrop)
238 | // OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings.
239 | export function OnFileDrop(callback: (x: number, y: number ,paths: string[]) => void, useDropTarget: boolean) :void
240 |
241 | // [OnFileDropOff](https://wails.io/docs/reference/runtime/draganddrop#dragandddropoff)
242 | // OnFileDropOff removes the drag and drop listeners and handlers.
243 | export function OnFileDropOff() :void
244 |
245 | // Check if the file path resolver is available
246 | export function CanResolveFilePaths(): boolean;
247 |
248 | // Resolves file paths for an array of files
249 | export function ResolveFilePaths(files: File[]): void
--------------------------------------------------------------------------------
/frontend/wailsjs/runtime/runtime.js:
--------------------------------------------------------------------------------
1 | /*
2 | _ __ _ __
3 | | | / /___ _(_) /____
4 | | | /| / / __ `/ / / ___/
5 | | |/ |/ / /_/ / / (__ )
6 | |__/|__/\__,_/_/_/____/
7 | The electron alternative for Go
8 | (c) Lea Anthony 2019-present
9 | */
10 |
11 | export function LogPrint(message) {
12 | window.runtime.LogPrint(message);
13 | }
14 |
15 | export function LogTrace(message) {
16 | window.runtime.LogTrace(message);
17 | }
18 |
19 | export function LogDebug(message) {
20 | window.runtime.LogDebug(message);
21 | }
22 |
23 | export function LogInfo(message) {
24 | window.runtime.LogInfo(message);
25 | }
26 |
27 | export function LogWarning(message) {
28 | window.runtime.LogWarning(message);
29 | }
30 |
31 | export function LogError(message) {
32 | window.runtime.LogError(message);
33 | }
34 |
35 | export function LogFatal(message) {
36 | window.runtime.LogFatal(message);
37 | }
38 |
39 | export function EventsOnMultiple(eventName, callback, maxCallbacks) {
40 | return window.runtime.EventsOnMultiple(eventName, callback, maxCallbacks);
41 | }
42 |
43 | export function EventsOn(eventName, callback) {
44 | return EventsOnMultiple(eventName, callback, -1);
45 | }
46 |
47 | export function EventsOff(eventName, ...additionalEventNames) {
48 | return window.runtime.EventsOff(eventName, ...additionalEventNames);
49 | }
50 |
51 | export function EventsOnce(eventName, callback) {
52 | return EventsOnMultiple(eventName, callback, 1);
53 | }
54 |
55 | export function EventsEmit(eventName) {
56 | let args = [eventName].slice.call(arguments);
57 | return window.runtime.EventsEmit.apply(null, args);
58 | }
59 |
60 | export function WindowReload() {
61 | window.runtime.WindowReload();
62 | }
63 |
64 | export function WindowReloadApp() {
65 | window.runtime.WindowReloadApp();
66 | }
67 |
68 | export function WindowSetAlwaysOnTop(b) {
69 | window.runtime.WindowSetAlwaysOnTop(b);
70 | }
71 |
72 | export function WindowSetSystemDefaultTheme() {
73 | window.runtime.WindowSetSystemDefaultTheme();
74 | }
75 |
76 | export function WindowSetLightTheme() {
77 | window.runtime.WindowSetLightTheme();
78 | }
79 |
80 | export function WindowSetDarkTheme() {
81 | window.runtime.WindowSetDarkTheme();
82 | }
83 |
84 | export function WindowCenter() {
85 | window.runtime.WindowCenter();
86 | }
87 |
88 | export function WindowSetTitle(title) {
89 | window.runtime.WindowSetTitle(title);
90 | }
91 |
92 | export function WindowFullscreen() {
93 | window.runtime.WindowFullscreen();
94 | }
95 |
96 | export function WindowUnfullscreen() {
97 | window.runtime.WindowUnfullscreen();
98 | }
99 |
100 | export function WindowIsFullscreen() {
101 | return window.runtime.WindowIsFullscreen();
102 | }
103 |
104 | export function WindowGetSize() {
105 | return window.runtime.WindowGetSize();
106 | }
107 |
108 | export function WindowSetSize(width, height) {
109 | window.runtime.WindowSetSize(width, height);
110 | }
111 |
112 | export function WindowSetMaxSize(width, height) {
113 | window.runtime.WindowSetMaxSize(width, height);
114 | }
115 |
116 | export function WindowSetMinSize(width, height) {
117 | window.runtime.WindowSetMinSize(width, height);
118 | }
119 |
120 | export function WindowSetPosition(x, y) {
121 | window.runtime.WindowSetPosition(x, y);
122 | }
123 |
124 | export function WindowGetPosition() {
125 | return window.runtime.WindowGetPosition();
126 | }
127 |
128 | export function WindowHide() {
129 | window.runtime.WindowHide();
130 | }
131 |
132 | export function WindowShow() {
133 | window.runtime.WindowShow();
134 | }
135 |
136 | export function WindowMaximise() {
137 | window.runtime.WindowMaximise();
138 | }
139 |
140 | export function WindowToggleMaximise() {
141 | window.runtime.WindowToggleMaximise();
142 | }
143 |
144 | export function WindowUnmaximise() {
145 | window.runtime.WindowUnmaximise();
146 | }
147 |
148 | export function WindowIsMaximised() {
149 | return window.runtime.WindowIsMaximised();
150 | }
151 |
152 | export function WindowMinimise() {
153 | window.runtime.WindowMinimise();
154 | }
155 |
156 | export function WindowUnminimise() {
157 | window.runtime.WindowUnminimise();
158 | }
159 |
160 | export function WindowSetBackgroundColour(R, G, B, A) {
161 | window.runtime.WindowSetBackgroundColour(R, G, B, A);
162 | }
163 |
164 | export function ScreenGetAll() {
165 | return window.runtime.ScreenGetAll();
166 | }
167 |
168 | export function WindowIsMinimised() {
169 | return window.runtime.WindowIsMinimised();
170 | }
171 |
172 | export function WindowIsNormal() {
173 | return window.runtime.WindowIsNormal();
174 | }
175 |
176 | export function BrowserOpenURL(url) {
177 | window.runtime.BrowserOpenURL(url);
178 | }
179 |
180 | export function Environment() {
181 | return window.runtime.Environment();
182 | }
183 |
184 | export function Quit() {
185 | window.runtime.Quit();
186 | }
187 |
188 | export function Hide() {
189 | window.runtime.Hide();
190 | }
191 |
192 | export function Show() {
193 | window.runtime.Show();
194 | }
195 |
196 | export function ClipboardGetText() {
197 | return window.runtime.ClipboardGetText();
198 | }
199 |
200 | export function ClipboardSetText(text) {
201 | return window.runtime.ClipboardSetText(text);
202 | }
203 |
204 | /**
205 | * Callback for OnFileDrop returns a slice of file path strings when a drop is finished.
206 | *
207 | * @export
208 | * @callback OnFileDropCallback
209 | * @param {number} x - x coordinate of the drop
210 | * @param {number} y - y coordinate of the drop
211 | * @param {string[]} paths - A list of file paths.
212 | */
213 |
214 | /**
215 | * OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings.
216 | *
217 | * @export
218 | * @param {OnFileDropCallback} callback - Callback for OnFileDrop returns a slice of file path strings when a drop is finished.
219 | * @param {boolean} [useDropTarget=true] - Only call the callback when the drop finished on an element that has the drop target style. (--wails-drop-target)
220 | */
221 | export function OnFileDrop(callback, useDropTarget) {
222 | return window.runtime.OnFileDrop(callback, useDropTarget);
223 | }
224 |
225 | /**
226 | * OnFileDropOff removes the drag and drop listeners and handlers.
227 | */
228 | export function OnFileDropOff() {
229 | return window.runtime.OnFileDropOff();
230 | }
231 |
232 | export function CanResolveFilePaths() {
233 | return window.runtime.CanResolveFilePaths();
234 | }
235 |
236 | export function ResolveFilePaths(files) {
237 | return window.runtime.ResolveFilePaths(files);
238 | }
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module gf2gacha
2 |
3 | go 1.23.0
4 |
5 | toolchain go1.23.5
6 |
7 | require (
8 | github.com/elazarl/goproxy v1.7.2
9 | github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf
10 | github.com/pkg/errors v0.9.1
11 | github.com/spf13/viper v1.19.0
12 | github.com/wailsapp/wails/v2 v2.10.1
13 | github.com/xuri/excelize/v2 v2.9.0
14 | go.uber.org/zap v1.27.0
15 | golang.org/x/sys v0.33.0
16 | google.golang.org/protobuf v1.36.5
17 | modernc.org/sqlite v1.36.0
18 | xorm.io/xorm v1.3.9
19 | )
20 |
21 | require (
22 | github.com/bep/debounce v1.2.1 // indirect
23 | github.com/dustin/go-humanize v1.0.1 // indirect
24 | github.com/fsnotify/fsnotify v1.8.0 // indirect
25 | github.com/go-ole/go-ole v1.3.0 // indirect
26 | github.com/goccy/go-json v0.10.5 // indirect
27 | github.com/godbus/dbus/v5 v5.1.0 // indirect
28 | github.com/golang/snappy v1.0.0 // indirect
29 | github.com/google/uuid v1.6.0 // indirect
30 | github.com/hashicorp/hcl v1.0.0 // indirect
31 | github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
32 | github.com/json-iterator/go v1.1.12 // indirect
33 | github.com/labstack/echo/v4 v4.13.3 // indirect
34 | github.com/labstack/gommon v0.4.2 // indirect
35 | github.com/leaanthony/go-ansi-parser v1.6.1 // indirect
36 | github.com/leaanthony/gosod v1.0.4 // indirect
37 | github.com/leaanthony/slicer v1.6.0 // indirect
38 | github.com/leaanthony/u v1.1.1 // indirect
39 | github.com/magiconair/properties v1.8.9 // indirect
40 | github.com/mattn/go-colorable v0.1.14 // indirect
41 | github.com/mattn/go-isatty v0.0.20 // indirect
42 | github.com/mitchellh/mapstructure v1.5.0 // indirect
43 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
44 | github.com/modern-go/reflect2 v1.0.2 // indirect
45 | github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
46 | github.com/ncruces/go-strftime v0.1.9 // indirect
47 | github.com/pelletier/go-toml/v2 v2.2.3 // indirect
48 | github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
49 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
50 | github.com/richardlehane/mscfb v1.0.4 // indirect
51 | github.com/richardlehane/msoleps v1.0.4 // indirect
52 | github.com/rivo/uniseg v0.4.7 // indirect
53 | github.com/sagikazarmark/locafero v0.7.0 // indirect
54 | github.com/sagikazarmark/slog-shim v0.1.0 // indirect
55 | github.com/samber/lo v1.49.1 // indirect
56 | github.com/sourcegraph/conc v0.3.0 // indirect
57 | github.com/spf13/afero v1.12.0 // indirect
58 | github.com/spf13/cast v1.7.1 // indirect
59 | github.com/spf13/pflag v1.0.6 // indirect
60 | github.com/subosito/gotenv v1.6.0 // indirect
61 | github.com/syndtr/goleveldb v1.0.0 // indirect
62 | github.com/tkrajina/go-reflector v0.5.8 // indirect
63 | github.com/valyala/bytebufferpool v1.0.0 // indirect
64 | github.com/valyala/fasttemplate v1.2.2 // indirect
65 | github.com/wailsapp/go-webview2 v1.0.19 // indirect
66 | github.com/wailsapp/mimetype v1.4.1 // indirect
67 | github.com/xuri/efp v0.0.0-20250227110027-3491fafc2b79 // indirect
68 | github.com/xuri/nfp v0.0.0-20250226145837-86d5fc24b2ba // indirect
69 | go.uber.org/multierr v1.11.0 // indirect
70 | golang.org/x/crypto v0.38.0 // indirect
71 | golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
72 | golang.org/x/net v0.40.0 // indirect
73 | golang.org/x/text v0.25.0 // indirect
74 | gopkg.in/ini.v1 v1.67.0 // indirect
75 | gopkg.in/yaml.v3 v3.0.1 // indirect
76 | modernc.org/libc v1.61.13 // indirect
77 | modernc.org/mathutil v1.7.1 // indirect
78 | modernc.org/memory v1.8.2 // indirect
79 | xorm.io/builder v0.3.13 // indirect
80 | )
81 |
82 | // replace github.com/wailsapp/wails/v2 v2.9.1 => D:\Project\Go\pkg\mod
83 |
--------------------------------------------------------------------------------
/logger/init.go:
--------------------------------------------------------------------------------
1 | package logger
2 |
3 | import (
4 | "go.uber.org/zap"
5 | "go.uber.org/zap/zapcore"
6 | "io"
7 | "os"
8 | )
9 |
10 | var Logger *zap.SugaredLogger
11 |
12 | func init() {
13 | logFile, err := os.OpenFile("gf2gacha.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0755)
14 | if err != nil {
15 | panic(err)
16 | }
17 | mw := io.MultiWriter(logFile, os.Stdout)
18 | mwSyncer := zapcore.AddSync(mw)
19 |
20 | encoderConfig := zap.NewProductionEncoderConfig()
21 | encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
22 | encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
23 | encoder := zapcore.NewConsoleEncoder(encoderConfig)
24 |
25 | core := zapcore.NewCore(encoder, mwSyncer, zapcore.InfoLevel)
26 | Logger = zap.New(core).Sugar()
27 | }
28 |
--------------------------------------------------------------------------------
/logic/AppendLog.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "fmt"
5 | "github.com/pkg/errors"
6 | "os"
7 | "path/filepath"
8 | )
9 |
10 | func AppendLog(accessToken string, uid int64, gachaUrl string) error {
11 | userHome, err := os.UserHomeDir()
12 | if err != nil {
13 | return errors.WithStack(err)
14 | }
15 |
16 | logPath := filepath.Join(userHome, "/AppData/LocalLow/SunBorn/少女前线2:追放/Player.log")
17 | file, err := os.OpenFile(logPath, os.O_WRONLY|os.O_APPEND, 0644)
18 | if err != nil {
19 | return err
20 | }
21 | defer file.Close()
22 |
23 | _, err = file.WriteString(fmt.Sprintf(`{"access_token":"%s","uid":%d}\n`, accessToken, uid))
24 | if err != nil {
25 | return err
26 | }
27 |
28 | _, err = file.WriteString(fmt.Sprintf(`{"gacha_record_url":"%s"}\n`, gachaUrl))
29 | if err != nil {
30 | return err
31 | }
32 |
33 | return nil
34 | }
35 |
--------------------------------------------------------------------------------
/logic/ExportRecord.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "gf2gacha/logger"
7 | "gf2gacha/model"
8 | "gf2gacha/preload"
9 | "github.com/pkg/errors"
10 | "github.com/xuri/excelize/v2"
11 | "os"
12 | "path/filepath"
13 | "time"
14 | )
15 |
16 | func ExportRawJson(uid, saveDir string) error {
17 | rawMap := make(map[int64][]model.RemoteRecord)
18 | for _, poolTypeUnit := range preload.PoolTypeMap {
19 | localRecordList, err := GetLocalRecord(uid, poolTypeUnit.Id, 0)
20 | if err != nil {
21 | return errors.WithStack(err)
22 | }
23 | for _, record := range localRecordList {
24 | rawMap[poolTypeUnit.Id] = append(rawMap[poolTypeUnit.Id], model.RemoteRecord{
25 | PoolId: record.PoolId,
26 | ItemId: record.ItemId,
27 | GachaTimestamp: record.GachaTimestamp,
28 | })
29 | }
30 | }
31 | rawJsonBytes, err := json.MarshalIndent(&rawMap, "", "\t")
32 | if err != nil {
33 | return errors.WithStack(err)
34 | }
35 |
36 | err = os.WriteFile(filepath.Join(saveDir, fmt.Sprintf("gf2gacha-raw-%s_%s.json", uid, time.Now().Format("20060102150405"))), rawJsonBytes, 0755)
37 | if err != nil {
38 | return errors.WithStack(err)
39 | }
40 | return nil
41 | }
42 |
43 | func ExportMccExcel(uid, saveDir string) error {
44 | excel := excelize.NewFile()
45 | defer excel.Close()
46 |
47 | styleTitle, err := excel.NewStyle(&excelize.Style{
48 | Font: &excelize.Font{
49 | Family: "Microsoft YaHei",
50 | Size: 14,
51 | Bold: true,
52 | },
53 | Alignment: &excelize.Alignment{
54 | Horizontal: "left",
55 | },
56 | })
57 | if err != nil {
58 | return errors.WithStack(err)
59 | }
60 |
61 | styleRank3, err := excel.NewStyle(&excelize.Style{
62 | Font: &excelize.Font{
63 | Family: "Microsoft YaHei",
64 | Size: 14,
65 | Color: "#5B9BD5",
66 | },
67 | Alignment: &excelize.Alignment{
68 | Horizontal: "left",
69 | },
70 | })
71 | if err != nil {
72 | return errors.WithStack(err)
73 | }
74 |
75 | styleRank4, err := excel.NewStyle(&excelize.Style{
76 | Font: &excelize.Font{
77 | Family: "Microsoft YaHei",
78 | Size: 14,
79 | Color: "#CC00CC",
80 | },
81 | Alignment: &excelize.Alignment{
82 | Horizontal: "left",
83 | },
84 | })
85 | if err != nil {
86 | return errors.WithStack(err)
87 | }
88 |
89 | styleRank5, err := excel.NewStyle(&excelize.Style{
90 | Font: &excelize.Font{
91 | Family: "Microsoft YaHei",
92 | Size: 14,
93 | Color: "#FFC000",
94 | },
95 | Alignment: &excelize.Alignment{
96 | Horizontal: "left",
97 | },
98 | })
99 | if err != nil {
100 | return errors.WithStack(err)
101 | }
102 |
103 | for _, poolTypeUnit := range preload.PoolTypeMap {
104 | localRecordList, err := GetLocalRecord(uid, poolTypeUnit.Id, 0)
105 | if err != nil {
106 | return errors.WithStack(err)
107 | }
108 | if len(localRecordList) > 0 {
109 | var sheetName string
110 | switch poolTypeUnit.Id {
111 | case 1:
112 | sheetName = "常规采购"
113 | case 3:
114 | sheetName = "定向采购"
115 | case 4:
116 | sheetName = "军备提升"
117 | case 5:
118 | sheetName = "初始采购"
119 | case 6:
120 | sheetName = "自选人形"
121 | case 7:
122 | sheetName = "自选武器"
123 | case 8:
124 | sheetName = "神秘箱"
125 | default:
126 | logger.Logger.Warnf("未定义的卡池 poolType:%d poolName:%s", poolTypeUnit.Id, poolTypeUnit.Name)
127 | continue
128 | }
129 | _, err = excel.NewSheet(sheetName)
130 | if err != nil {
131 | return errors.WithStack(err)
132 | }
133 |
134 | err = excel.SetSheetRow(sheetName, "A1", &[]string{"卡池Id", "抽卡时间", "道具Id", "道具名称"})
135 | if err != nil {
136 | return errors.WithStack(err)
137 | }
138 |
139 | err = excel.SetRowStyle(sheetName, 1, 1, styleTitle)
140 | if err != nil {
141 | return errors.WithStack(err)
142 | }
143 | err = excel.SetColWidth(sheetName, "A", "A", 10)
144 | if err != nil {
145 | return errors.WithStack(err)
146 | }
147 | err = excel.SetColWidth(sheetName, "B", "B", 36)
148 | if err != nil {
149 | return errors.WithStack(err)
150 | }
151 | err = excel.SetColWidth(sheetName, "C", "C", 12)
152 | if err != nil {
153 | return errors.WithStack(err)
154 | }
155 | err = excel.SetColWidth(sheetName, "D", "D", 42)
156 | if err != nil {
157 | return errors.WithStack(err)
158 | }
159 |
160 | for i, record := range localRecordList {
161 | rowIndex := i + 2
162 | cellName := fmt.Sprintf("A%d", rowIndex)
163 | poolId := record.PoolId
164 | gachaTime := time.Unix(record.GachaTimestamp, 0).Format("2006年01月02日 15:04:05")
165 | itemId := record.ItemId
166 | itemName := preload.LangMap[preload.ItemMap[itemId].Name.Id]
167 | err = excel.SetSheetRow(sheetName, cellName, &[]interface{}{poolId, gachaTime, itemId, itemName})
168 | if err != nil {
169 | return errors.WithStack(err)
170 | }
171 |
172 | itemRank := preload.ItemRankMap[poolId][itemId]
173 | switch itemRank {
174 | case 3:
175 | err = excel.SetRowStyle(sheetName, rowIndex, rowIndex, styleRank3)
176 | if err != nil {
177 | return errors.WithStack(err)
178 | }
179 | case 4:
180 | err = excel.SetRowStyle(sheetName, rowIndex, rowIndex, styleRank4)
181 | if err != nil {
182 | return errors.WithStack(err)
183 | }
184 | case 5:
185 | err = excel.SetRowStyle(sheetName, rowIndex, rowIndex, styleRank5)
186 | if err != nil {
187 | return errors.WithStack(err)
188 | }
189 | default:
190 | logger.Logger.Warnf("未知道具等级%d %+v", itemRank, record)
191 | }
192 | }
193 | }
194 | }
195 |
196 | err = excel.DeleteSheet("Sheet1")
197 | if err != nil {
198 | return errors.WithStack(err)
199 | }
200 |
201 | err = excel.SaveAs(filepath.Join(saveDir, fmt.Sprintf("gf2gacha-excel-%s_%s.xlsx", uid, time.Now().Format("20060102150405"))))
202 | if err != nil {
203 | return errors.WithStack(err)
204 | }
205 |
206 | return nil
207 | }
208 |
--------------------------------------------------------------------------------
/logic/FetchRemoteData.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "encoding/json"
5 | "gf2gacha/model"
6 | "github.com/pkg/errors"
7 | "io"
8 | "net/http"
9 | "net/url"
10 | "strconv"
11 | "strings"
12 | )
13 |
14 | func FetchRemoteData(gachaUrl, accessToken, next string, poolType int64) (data model.ResponseData, err error) {
15 | reqBody := url.Values{}
16 | if next != "" {
17 | reqBody.Set("next", next)
18 | }
19 | reqBody.Set("type_id", strconv.FormatInt(poolType, 10))
20 |
21 | request, err := http.NewRequest("POST", gachaUrl, strings.NewReader(reqBody.Encode()))
22 | if err != nil {
23 | return model.ResponseData{}, errors.WithStack(err)
24 | }
25 | request.Header.Add("Content-Type", "application/x-www-form-urlencoded")
26 | request.Header.Set("Authorization", accessToken)
27 |
28 | resp, err := http.DefaultClient.Do(request)
29 | if err != nil {
30 | return model.ResponseData{}, errors.WithStack(err)
31 | }
32 | defer resp.Body.Close()
33 |
34 | respBodyBytes, err := io.ReadAll(resp.Body)
35 | if err != nil {
36 | return model.ResponseData{}, errors.WithStack(err)
37 | }
38 | var respBody model.ResponseBody
39 | err = json.Unmarshal(respBodyBytes, &respBody)
40 | if err != nil {
41 | return model.ResponseData{}, errors.WithStack(err)
42 | }
43 | if respBody.Code != 0 {
44 | return model.ResponseData{}, errors.Errorf("%s(Code %d)", respBody.Message, respBody.Code)
45 | }
46 |
47 | err = json.Unmarshal(respBody.Data, &data)
48 | if err != nil {
49 | return model.ResponseData{}, errors.WithStack(err)
50 | }
51 | return data, nil
52 | }
53 |
--------------------------------------------------------------------------------
/logic/GetCommunityExchangeList.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "fmt"
5 | "gf2gacha/model"
6 | "gf2gacha/request"
7 | "github.com/pkg/errors"
8 | )
9 |
10 | func GetCommunityExchangeList() ([]model.CommunityExchangeList, error) {
11 | exchangeListData, err := request.CommunityExchangeList("")
12 | if err != nil {
13 | return nil, errors.WithStack(err)
14 | }
15 |
16 | var exchangeList []model.CommunityExchangeList
17 | for _, exchangeItem := range exchangeListData.List {
18 | exchangeList = append(exchangeList, model.CommunityExchangeList{
19 | Id: exchangeItem.ExchangeId,
20 | Name: fmt.Sprintf("『%s*%d』x%d", exchangeItem.ItemName, exchangeItem.ItemCount, exchangeItem.MaxExchangeCount),
21 | })
22 | }
23 | return exchangeList, nil
24 | }
25 |
--------------------------------------------------------------------------------
/logic/GetLatestVersion.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "github.com/pkg/errors"
5 | "io"
6 | "net/http"
7 | )
8 |
9 | func GetLatestVersion() (string, error) {
10 | resp, err := http.Get("https://gfl2worker.mcc.wiki/gf2gacha/version")
11 | if err != nil {
12 | return "", err
13 | }
14 | defer resp.Body.Close()
15 |
16 | if resp.StatusCode != 200 {
17 | return "", errors.New(resp.Status)
18 | }
19 |
20 | bodyBytes, err := io.ReadAll(resp.Body)
21 | if err != nil {
22 | return "", err
23 | }
24 |
25 | return string(bodyBytes), nil
26 | }
27 |
--------------------------------------------------------------------------------
/logic/GetLocalRecord.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "gf2gacha/model"
5 | "github.com/pkg/errors"
6 | )
7 |
8 | func GetLocalRecord(uid string, poolType, endTimestamp int64) (recordList []model.LocalRecord, err error) {
9 | if uid == "" {
10 | return nil, errors.New("uid为空")
11 | }
12 |
13 | session := model.Engine.NewSession()
14 | defer session.Close()
15 |
16 | err = session.Table(uid).Sync(new(model.LocalRecord))
17 | if err != nil {
18 | return nil, errors.WithStack(err)
19 | }
20 |
21 | if endTimestamp != 0 {
22 | err = session.Table(uid).Where("gacha_timestamp", endTimestamp).Find(&recordList, &model.LocalRecord{PoolType: poolType})
23 | if err != nil {
24 | return nil, errors.WithStack(err)
25 | }
26 | } else {
27 | err = session.Table(uid).Find(&recordList, &model.LocalRecord{PoolType: poolType})
28 | if err != nil {
29 | return nil, errors.WithStack(err)
30 | }
31 | }
32 |
33 | return recordList, nil
34 | }
35 |
--------------------------------------------------------------------------------
/logic/GetPoolInfo.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "gf2gacha/logger"
5 | "gf2gacha/model"
6 | "gf2gacha/preload"
7 | "github.com/pkg/errors"
8 | )
9 |
10 | func GetPoolInfo(uid string, poolType int64) (model.Pool, error) {
11 | localRecordList, err := GetLocalRecord(uid, poolType, 0)
12 | if err != nil {
13 | return model.Pool{}, errors.WithStack(err)
14 | }
15 |
16 | pool := model.Pool{PoolType: poolType}
17 | var isPreviousLose bool
18 | for _, storedRecord := range localRecordList {
19 | pool.GachaCount++
20 | pool.StoredCount++
21 | item := preload.ItemMap[storedRecord.ItemId]
22 | itemRank := item.Rank
23 | if poolType == 8 {
24 | itemRank = preload.ItemRankMap[storedRecord.PoolId][storedRecord.ItemId]
25 | }
26 | if itemRank == 5 {
27 | if isPreviousLose {
28 | pool.GuaranteesCount++
29 | }
30 | //检测是否歪
31 | var lose bool
32 | if upItemId, hasUp := preload.UpItemMap[storedRecord.PoolId]; hasUp && upItemId != storedRecord.ItemId {
33 | pool.LoseCount++
34 | lose = true
35 | isPreviousLose = true
36 | } else {
37 | isPreviousLose = false
38 | }
39 |
40 | pool.RecordList = append(pool.RecordList, model.DisplayRecord{
41 | Id: item.Id,
42 | Name: preload.LangMap[item.Name.Id],
43 | Lose: lose,
44 | Count: pool.StoredCount,
45 | })
46 |
47 | pool.StoredCount = 0
48 | pool.Rank5Count++
49 | } else if itemRank == 4 {
50 | pool.Rank4Count++
51 | } else if itemRank == 3 {
52 | pool.Rank3Count++
53 | } else {
54 | logger.Logger.Warnf("未知的物品Rank poolType:%d poolId:%d itemId:%d", poolType, storedRecord.PoolId, storedRecord.ItemId)
55 | }
56 | }
57 |
58 | return pool, nil
59 | }
60 |
--------------------------------------------------------------------------------
/logic/GetUserInfoFromBBS.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import "gf2gacha/request"
4 |
5 | func GetUserInfoFromBBS(accessToken string) (*request.CommunityUserInfoData, error) {
6 | webToken, err := request.CommunityLogin(accessToken)
7 | if err != nil {
8 | return nil, err
9 | }
10 | userInfo, err := request.CommunityUserInfo(webToken)
11 | if err != nil {
12 | return nil, err
13 | }
14 | return &userInfo, nil
15 | }
16 |
--------------------------------------------------------------------------------
/logic/GetUserList.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "gf2gacha/model"
5 | "github.com/pkg/errors"
6 | )
7 |
8 | func GetUserList() ([]string, error) {
9 | metas, err := model.Engine.DBMetas()
10 | if err != nil {
11 | return nil, errors.WithStack(err)
12 | }
13 |
14 | var uidList []string
15 | for _, meta := range metas {
16 | uidList = append(uidList, meta.Name)
17 | }
18 |
19 | return uidList, nil
20 | }
21 |
--------------------------------------------------------------------------------
/logic/HandleCommunityTasks.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "fmt"
5 | "gf2gacha/config"
6 | "gf2gacha/logger"
7 | "gf2gacha/request"
8 | "gf2gacha/util"
9 | "github.com/pkg/errors"
10 | "sort"
11 | "time"
12 | )
13 |
14 | func HandleCommunityTasks() (messageList []string, err error) {
15 | logInfo, err := util.GetLogInfo()
16 | if err != nil {
17 | return nil, errors.WithStack(err)
18 | }
19 |
20 | webToken, err := request.CommunityLogin(logInfo.AccessToken)
21 | if err != nil {
22 | var respData request.CommonResponse
23 | if errors.As(err, &respData) {
24 | if respData.Code == -1 {
25 | logger.Logger.Info("AccessToken失效,尝试使用保存的WebToken")
26 | webToken = config.GetWebToken(logInfo.Uid)
27 | if webToken == "" {
28 | return nil, errors.New("AccessToken失效且无保存的WebToken,您可能在其他设备登录过,请在本设备重新登录")
29 | }
30 | } else {
31 | return nil, errors.WithStack(err)
32 | }
33 | } else {
34 | return nil, errors.WithStack(err)
35 | }
36 | }
37 | err = config.SetWebToken(logInfo.Uid, webToken)
38 | if err != nil {
39 | return nil, errors.WithStack(err)
40 | }
41 |
42 | taskListData, err := request.CommunityTaskList(webToken)
43 | if err != nil {
44 | var respData request.CommonResponse
45 | if errors.As(err, &respData) && respData.Code == -1 {
46 | return nil, errors.New("AccessToken失效且WebToken过期,您可能在其他设备登录过,请在本设备重新登录")
47 | } else {
48 | return nil, errors.WithStack(err)
49 | }
50 | }
51 |
52 | for _, dailyTask := range taskListData.DailyTask {
53 | if dailyTask.CompleteCount < dailyTask.MaxCompleteCount {
54 | switch dailyTask.TaskName {
55 | case "浏览帖子":
56 | viewMessageList, err := handleCommunityTaskView(webToken, dailyTask.MaxCompleteCount-dailyTask.CompleteCount)
57 | if err != nil {
58 | return nil, errors.WithStack(err)
59 | }
60 | messageList = append(messageList, viewMessageList...)
61 | case "点赞帖子":
62 | likeMessageList, err := handleCommunityTaskLike(webToken, dailyTask.MaxCompleteCount-dailyTask.CompleteCount)
63 | if err != nil {
64 | return nil, errors.WithStack(err)
65 | }
66 | messageList = append(messageList, likeMessageList...)
67 | case "分享帖子":
68 | shareMessageList, err := handleCommunityTaskShare(webToken, dailyTask.MaxCompleteCount-dailyTask.CompleteCount)
69 | if err != nil {
70 | return nil, errors.WithStack(err)
71 | }
72 | messageList = append(messageList, shareMessageList...)
73 | default:
74 | logger.Logger.Errorf("未知的社区任务%s", dailyTask.TaskName)
75 | }
76 | }
77 | }
78 |
79 | exchangeMessageList, err := handleCommunityExchange(webToken)
80 | if err != nil {
81 | return nil, errors.WithStack(err)
82 | }
83 | messageList = append(messageList, exchangeMessageList...)
84 |
85 | userInfo, err := request.CommunityUserInfo(webToken)
86 | if err != nil {
87 | return nil, errors.WithStack(err)
88 | }
89 |
90 | signData, err := request.CommunitySign(webToken)
91 | if err != nil {
92 | return nil, errors.WithStack(err)
93 | }
94 | messageList = append(messageList, fmt.Sprintf("%s(UID:%d)签到成功,获得%s*%d", userInfo.User.GameNickName, userInfo.User.GameUid, signData.GetItemName, signData.GetItemCount))
95 |
96 | return messageList, nil
97 | }
98 |
99 | func handleCommunityTaskView(webToken string, times int64) (messageList []string, err error) {
100 | var count int64
101 | topicListData, err := request.CommunityTopicList(webToken, 0)
102 | if err != nil {
103 | return nil, errors.WithStack(err)
104 | }
105 | for _, topic := range topicListData.List {
106 | _, err = request.CommunityTopicView(webToken, topic.TopicId)
107 | if err != nil {
108 | return nil, errors.WithStack(err)
109 | }
110 |
111 | messageList = append(messageList, fmt.Sprintf("浏览官方板块主题『%s』", topic.Title))
112 |
113 | count++
114 | if count == times {
115 | break
116 | }
117 | }
118 |
119 | return messageList, nil
120 | }
121 |
122 | func handleCommunityTaskLike(webToken string, times int64) (messageList []string, err error) {
123 | var count int64
124 | topicListData, err := request.CommunityTopicList(webToken, 0)
125 | if err != nil {
126 | return nil, errors.WithStack(err)
127 | }
128 | for _, topic := range topicListData.List {
129 | if !topic.IsLike {
130 | //未点赞的直接点赞
131 | err = request.CommunityTopicLike(webToken, topic.TopicId)
132 | if err != nil {
133 | return nil, errors.WithStack(err)
134 | }
135 | messageList = append(messageList, fmt.Sprintf("点赞官方板块主题『%s』", topic.Title))
136 | } else {
137 | //已点赞的取消点赞再点赞
138 | err = request.CommunityTopicLike(webToken, topic.TopicId)
139 | if err != nil {
140 | return nil, errors.WithStack(err)
141 | }
142 | time.Sleep(50 * time.Millisecond)
143 | err = request.CommunityTopicLike(webToken, topic.TopicId)
144 | if err != nil {
145 | return nil, errors.WithStack(err)
146 | }
147 | messageList = append(messageList, fmt.Sprintf("取消并再次点赞官方板块主题『%s』", topic.Title))
148 | }
149 |
150 | count++
151 | if count == times {
152 | break
153 | }
154 | }
155 |
156 | return messageList, nil
157 | }
158 |
159 | func handleCommunityTaskShare(webToken string, times int64) (messageList []string, err error) {
160 | var count int64
161 | topicListData, err := request.CommunityTopicList(webToken, 0)
162 | if err != nil {
163 | return nil, errors.WithStack(err)
164 | }
165 | for _, topic := range topicListData.List {
166 | err = request.CommunityTopicShare(webToken, topic.TopicId)
167 | if err != nil {
168 | return nil, errors.WithStack(err)
169 | }
170 |
171 | messageList = append(messageList, fmt.Sprintf("转发官方板块主题『%s』", topic.Title))
172 |
173 | count++
174 | if count == times {
175 | break
176 | }
177 | }
178 |
179 | return messageList, nil
180 | }
181 |
182 | func handleCommunityExchange(webToken string) (messageList []string, err error) {
183 | exchangeList := config.GetExchangeList()
184 | exchangeMap := make(map[int64]struct{})
185 | for _, itemId := range exchangeList {
186 | exchangeMap[itemId] = struct{}{}
187 | }
188 |
189 | exchangeListData, err := request.CommunityExchangeList(webToken)
190 | if err != nil {
191 | return nil, errors.WithStack(err)
192 | }
193 | //按价值排序,优先兑换高价值道具
194 | sort.Slice(exchangeListData.List, func(i, j int) bool {
195 | return exchangeListData.List[i].UseScore > exchangeListData.List[j].UseScore
196 | })
197 | for _, exchangeItem := range exchangeListData.List {
198 | //如果不在用户设置里,那就跳过
199 | if _, has := exchangeMap[exchangeItem.ExchangeId]; !has {
200 | messageList = append(messageList, fmt.Sprintf("用户设置不兑换『%s*%d』", exchangeItem.ItemName, exchangeItem.ItemCount))
201 | continue
202 | }
203 | if exchangeItem.ExchangeCount < exchangeItem.MaxExchangeCount {
204 | for i := int64(0); i < exchangeItem.MaxExchangeCount-exchangeItem.ExchangeCount; i++ {
205 | info, err := request.CommunityUserInfo(webToken)
206 | if err != nil {
207 | return nil, errors.WithStack(err)
208 | }
209 |
210 | if info.User.Score >= exchangeItem.UseScore {
211 | err = request.CommunityExchange(webToken, exchangeItem.ExchangeId)
212 | if err != nil {
213 | return nil, errors.WithStack(err)
214 | }
215 | messageList = append(messageList, fmt.Sprintf("消耗积分%d,成功兑换『%s*%d』", exchangeItem.UseScore, exchangeItem.ItemName, exchangeItem.ItemCount))
216 | } else {
217 | messageList = append(messageList, fmt.Sprintf("积分不足%d,无法兑换『%s*%d』", exchangeItem.UseScore, exchangeItem.ItemName, exchangeItem.ItemCount))
218 | }
219 | }
220 | }
221 | }
222 |
223 | return messageList, nil
224 | }
225 |
--------------------------------------------------------------------------------
/logic/ImportRecord.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "encoding/json"
5 | "gf2gacha/logger"
6 | "gf2gacha/model"
7 | "gf2gacha/util"
8 | "github.com/pkg/errors"
9 | "github.com/xuri/excelize/v2"
10 | "os"
11 | "slices"
12 | "strconv"
13 | "time"
14 | )
15 |
16 | func ImportRawJson(uid, rawJsonPath string, isReverse bool) error {
17 | rawJsonBytes, err := os.ReadFile(rawJsonPath)
18 | if err != nil {
19 | return errors.WithStack(err)
20 | }
21 |
22 | rawMap := make(map[int64][]model.RemoteRecord)
23 | err = json.Unmarshal(rawJsonBytes, &rawMap)
24 | if err != nil {
25 | return errors.WithStack(err)
26 | }
27 |
28 | //解析成功后先做备份
29 | err = util.BackupDB()
30 | if err != nil {
31 | return errors.WithStack(err)
32 | }
33 |
34 | for poolType, rawRecordList := range rawMap {
35 | var recordList []model.LocalRecord
36 | if isReverse {
37 | slices.Reverse(rawRecordList)
38 | }
39 | for _, record := range rawRecordList {
40 | recordList = append(recordList, model.LocalRecord{
41 | PoolType: poolType,
42 | PoolId: record.PoolId,
43 | ItemId: record.ItemId,
44 | GachaTimestamp: record.GachaTimestamp,
45 | })
46 | }
47 |
48 | localRecordList, err := GetLocalRecord(uid, poolType, 0)
49 | if err != nil {
50 | return errors.WithStack(err)
51 | }
52 |
53 | newRecordList := MergeRecord(localRecordList, recordList)
54 | if len(newRecordList) == 0 {
55 | return nil
56 | }
57 |
58 | err = RemoveLocalRecord(uid, poolType)
59 | if err != nil {
60 | return errors.WithStack(err)
61 | }
62 |
63 | err = SaveLocalRecord(uid, newRecordList)
64 | if err != nil {
65 | return errors.WithStack(err)
66 | }
67 | }
68 |
69 | return nil
70 | }
71 |
72 | func ImportMccExcel(uid, excelPath string) error {
73 | excel, err := excelize.OpenFile(excelPath)
74 | if err != nil {
75 | return errors.WithStack(err)
76 | }
77 | defer excel.Close()
78 |
79 | poolMap := make(map[int64][]model.LocalRecord)
80 | for _, sheetName := range excel.GetSheetList() {
81 | var poolType int64
82 | switch sheetName {
83 | case "常规采购":
84 | poolType = 1
85 | case "定向采购":
86 | poolType = 3
87 | case "军备提升":
88 | poolType = 4
89 | case "初始采购":
90 | poolType = 5
91 | case "自选人形":
92 | poolType = 6
93 | case "自选武器":
94 | poolType = 7
95 | case "神秘箱":
96 | poolType = 8
97 | default:
98 | logger.Logger.Warnf("未知Sheet名称:%s", sheetName)
99 | continue
100 | }
101 | rows, err := excel.GetRows(sheetName)
102 | if err != nil {
103 | errors.WithStack(err)
104 | }
105 |
106 | fieldMapping := make(map[string]int)
107 | for rowIndex, row := range rows {
108 | //先获取标题栏索引值,以防止有乱序的情况
109 | if rowIndex == 0 {
110 | for colIndex, cell := range row {
111 | switch cell {
112 | case "卡池Id":
113 | fieldMapping["PoolId"] = colIndex
114 | case "抽卡时间":
115 | fieldMapping["GachaTime"] = colIndex
116 | case "道具Id":
117 | fieldMapping["ItemId"] = colIndex
118 | }
119 | }
120 | continue
121 | }
122 |
123 | var poolId, itemId, gachaTimestamp int64
124 | poolIdIndex := fieldMapping["PoolId"]
125 | if poolIdIndex >= len(row) {
126 | return errors.Errorf("%s第%d行:卡池Id(Index:%d)越界或不存在", sheetName, rowIndex, poolIdIndex)
127 | }
128 | poolId, err = strconv.ParseInt(row[poolIdIndex], 10, 64)
129 | if err != nil {
130 | return errors.WithStack(err)
131 | }
132 |
133 | gachaTimeIndex := fieldMapping["GachaTime"]
134 | if gachaTimeIndex >= len(row) {
135 | return errors.Errorf("%s第%d行:抽卡时间(Index:%d)越界或不存在", sheetName, rowIndex, gachaTimeIndex)
136 | }
137 |
138 | t, err := time.ParseInLocation("2006年01月02日 15:04:05", row[gachaTimeIndex], time.Local)
139 | if err != nil {
140 | return errors.WithStack(err)
141 | }
142 | gachaTimestamp = t.Unix()
143 |
144 | itemIdIndex := fieldMapping["ItemId"]
145 | if itemIdIndex >= len(row) {
146 | return errors.Errorf("%s第%d行:道具Id(Index:%d)越界或不存在", sheetName, rowIndex, itemIdIndex)
147 | }
148 | itemId, err = strconv.ParseInt(row[itemIdIndex], 10, 64)
149 | if err != nil {
150 | return errors.WithStack(err)
151 | }
152 |
153 | poolMap[poolType] = append(poolMap[poolType], model.LocalRecord{
154 | PoolType: poolType,
155 | PoolId: poolId,
156 | ItemId: itemId,
157 | GachaTimestamp: gachaTimestamp,
158 | })
159 | }
160 | }
161 |
162 | if len(poolMap) == 0 {
163 | return errors.New("Excel内无可解析数据")
164 | }
165 |
166 | //解析成功后先做备份
167 | err = util.BackupDB()
168 | if err != nil {
169 | return errors.WithStack(err)
170 | }
171 |
172 | for poolType, recordList := range poolMap {
173 | localRecordList, err := GetLocalRecord(uid, poolType, 0)
174 | if err != nil {
175 | return errors.WithStack(err)
176 | }
177 |
178 | newRecordList := MergeRecord(localRecordList, recordList)
179 | if len(newRecordList) == 0 {
180 | return nil
181 | }
182 |
183 | err = RemoveLocalRecord(uid, poolType)
184 | if err != nil {
185 | return errors.WithStack(err)
186 | }
187 |
188 | err = SaveLocalRecord(uid, newRecordList)
189 | if err != nil {
190 | return errors.WithStack(err)
191 | }
192 | }
193 |
194 | return nil
195 | }
196 |
--------------------------------------------------------------------------------
/logic/MergeEreRecord.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "gf2gacha/model"
5 | "gf2gacha/util"
6 | "github.com/pkg/errors"
7 | "os"
8 | "strings"
9 | )
10 |
11 | func MergeEreRecord(uid, erePath, typ string) error {
12 | ereFileData, err := os.ReadFile(erePath)
13 | if err != nil {
14 | return errors.WithStack(err)
15 | }
16 |
17 | var ereRecordList []model.LocalRecord
18 |
19 | switch strings.ToLower(typ) {
20 | case "json":
21 | ereRecordList, err = ParseEreJsonData(ereFileData)
22 | if err != nil {
23 | return errors.WithStack(err)
24 | }
25 | case "excel":
26 | ereRecordList, err = ParseEreExcelData(ereFileData)
27 | if err != nil {
28 | return errors.WithStack(err)
29 | }
30 | default:
31 | return errors.Errorf("unknown ere type: %s", typ)
32 | }
33 |
34 | //解析成功后先做备份
35 | err = util.BackupDB()
36 | if err != nil {
37 | return errors.WithStack(err)
38 | }
39 |
40 | //需要每个池子分开处理
41 | poolMap := make(map[int64][]model.LocalRecord)
42 | for i, record := range ereRecordList {
43 | poolMap[record.PoolType] = append(poolMap[record.PoolType], ereRecordList[i])
44 | }
45 |
46 | for poolType, recordList := range poolMap {
47 | err = mergeEreRecord(uid, poolType, recordList)
48 | if err != nil {
49 | return errors.WithStack(err)
50 | }
51 | }
52 |
53 | return nil
54 | }
55 |
56 | func mergeEreRecord(uid string, poolType int64, ereRecordList []model.LocalRecord) error {
57 | localRecordList, err := GetLocalRecord(uid, poolType, 0)
58 | if err != nil {
59 | return errors.WithStack(err)
60 | }
61 |
62 | newRecordList := MergeRecord(localRecordList, ereRecordList)
63 | if len(newRecordList) == 0 {
64 | return nil
65 | }
66 |
67 | err = RemoveLocalRecord(uid, poolType)
68 | if err != nil {
69 | return errors.WithStack(err)
70 | }
71 |
72 | err = SaveLocalRecord(uid, newRecordList)
73 | if err != nil {
74 | return errors.WithStack(err)
75 | }
76 |
77 | return nil
78 | }
79 |
--------------------------------------------------------------------------------
/logic/MergeRecord.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "cmp"
5 | "fmt"
6 | "gf2gacha/logger"
7 | "gf2gacha/model"
8 | "slices"
9 | "strconv"
10 | )
11 |
12 | func MergeRecord(recordList1, recordList2 []model.LocalRecord) (newRecordList []model.LocalRecord) {
13 | recordList1 = addFakeId(recordList1)
14 | recordList2 = addFakeId(recordList2)
15 | tmpRecordList := append(recordList1, recordList2...)
16 | slices.SortStableFunc(tmpRecordList, func(a, b model.LocalRecord) int {
17 | return cmp.Compare(a.Id, b.Id)
18 | })
19 |
20 | fakeIdMap := make(map[int64]struct{})
21 | for _, record := range tmpRecordList {
22 | if _, exist := fakeIdMap[record.Id]; !exist {
23 | fakeIdMap[record.Id] = struct{}{}
24 | newRecordList = append(newRecordList, model.LocalRecord{
25 | PoolType: record.PoolType,
26 | PoolId: record.PoolId,
27 | ItemId: record.ItemId,
28 | GachaTimestamp: record.GachaTimestamp,
29 | })
30 | }
31 | }
32 |
33 | return newRecordList
34 | }
35 |
36 | func addFakeId(recordList []model.LocalRecord) []model.LocalRecord {
37 | if len(recordList) == 0 {
38 | return nil
39 | }
40 | var preTimeStamp, order int64
41 | for i, record := range recordList {
42 | if record.GachaTimestamp != preTimeStamp {
43 | //前一个只能是0或9
44 | if order != 0 && order != 9 {
45 | logger.Logger.Warnf("存在抽卡时间相同但不足十连的数据:%+v", recordList[i-1])
46 | }
47 | order = 0
48 | } else {
49 | order++
50 | if order > 9 {
51 | logger.Logger.Warnf("存在抽卡时间相同但超过十连的数据:%+v", recordList[i-1])
52 | }
53 | }
54 |
55 | fakeId, _ := strconv.ParseInt(fmt.Sprintf("%d%02d", record.GachaTimestamp, order), 10, 64)
56 | recordList[i].Id = fakeId
57 |
58 | preTimeStamp = record.GachaTimestamp
59 | }
60 | return recordList
61 | }
62 |
--------------------------------------------------------------------------------
/logic/ParseEreExcelData.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "bytes"
5 | "gf2gacha/logger"
6 | "gf2gacha/model"
7 | "gf2gacha/preload"
8 | "github.com/pkg/errors"
9 | "github.com/xuri/excelize/v2"
10 | "strconv"
11 | )
12 |
13 | func ParseEreExcelData(ereExcelData []byte) ([]model.LocalRecord, error) {
14 | reader, err := excelize.OpenReader(bytes.NewReader(ereExcelData))
15 | if err != nil {
16 | return nil, errors.WithStack(err)
17 | }
18 | defer reader.Close()
19 |
20 | var recordList []model.LocalRecord
21 | for _, sheetName := range reader.GetSheetList() {
22 | var poolType int64
23 | switch sheetName {
24 | case "常规采购":
25 | poolType = 1
26 | case "定向采购":
27 | poolType = 3
28 | case "军备提升":
29 | poolType = 4
30 | case "初始采购":
31 | poolType = 5
32 | default:
33 | logger.Logger.Warnf("未知Sheet名称:%s", sheetName)
34 | continue
35 | }
36 | rows, err := reader.GetRows(sheetName)
37 | if err != nil {
38 | return nil, errors.WithStack(err)
39 | }
40 |
41 | fieldMapping := make(map[string]int)
42 | for rowIndex, row := range rows {
43 | //先获取标题栏索引值,以防止有乱序的情况
44 | if rowIndex == 0 {
45 | for colIndex, cell := range row {
46 | switch cell {
47 | case "时间":
48 | fieldMapping["GachaTimestamp"] = colIndex
49 | case "备注":
50 | fieldMapping["PoolId"] = colIndex
51 | case "类别":
52 | fieldMapping["ItemType"] = colIndex
53 | case "名称":
54 | fieldMapping["ItemName"] = colIndex
55 | }
56 | }
57 | continue
58 | }
59 |
60 | var poolId, itemId, gachaTimestamp int64
61 | var itemType, itemName string
62 | poolIdIndex := fieldMapping["PoolId"]
63 | if poolIdIndex >= len(row) {
64 | return nil, errors.Errorf("%s第%d行:备注(Index:%d)越界", sheetName, rowIndex, poolIdIndex)
65 | }
66 | poolId, err = strconv.ParseInt(row[poolIdIndex], 10, 64)
67 | if err != nil {
68 | return nil, errors.WithStack(err)
69 | }
70 |
71 | gachaTimestampIndex := fieldMapping["GachaTimestamp"]
72 | if gachaTimestampIndex >= len(row) {
73 | return nil, errors.Errorf("%s第%d行:时间(Index:%d)越界", sheetName, rowIndex, gachaTimestampIndex)
74 | }
75 | gachaTimestamp, err = strconv.ParseInt(row[gachaTimestampIndex], 10, 64)
76 | if err != nil {
77 | return nil, errors.WithStack(err)
78 | }
79 |
80 | itemTypeIndex := fieldMapping["ItemType"]
81 | if itemTypeIndex >= len(row) {
82 | return nil, errors.Errorf("%s第%d行:类别(Index:%d)越界", sheetName, rowIndex, itemTypeIndex)
83 | }
84 | itemType = row[itemTypeIndex]
85 |
86 | itemNameIndex := fieldMapping["ItemName"]
87 | if itemNameIndex >= len(row) {
88 | return nil, errors.Errorf("%s第%d行:名称(Index:%d)越界", sheetName, rowIndex, itemNameIndex)
89 | }
90 | itemName = row[itemNameIndex]
91 |
92 | switch itemType {
93 | case "角色":
94 | itemId = preload.DollNameMapping[itemName]
95 | case "武器":
96 | itemId = preload.WeaponNameMapping[itemName]
97 | default:
98 | return nil, errors.Errorf("%s第%d行:未知类别(Index:%d):%s", sheetName, rowIndex, itemTypeIndex, itemType)
99 | }
100 | recordList = append(recordList, model.LocalRecord{
101 | PoolType: poolType,
102 | PoolId: poolId,
103 | ItemId: itemId,
104 | GachaTimestamp: gachaTimestamp,
105 | })
106 | }
107 | }
108 |
109 | return recordList, nil
110 | }
111 |
--------------------------------------------------------------------------------
/logic/ParseEreJsonData.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "encoding/json"
5 | "gf2gacha/model"
6 | "github.com/pkg/errors"
7 | "strconv"
8 | )
9 |
10 | type ereJsonStruct struct {
11 | Result [][]interface{} `json:"result"`
12 | Time int64 `json:"time"`
13 | TypeMap [][]string `json:"typeMap"`
14 | Account string `json:"account"`
15 | }
16 |
17 | func ParseEreJsonData(ereJsonData []byte) ([]model.LocalRecord, error) {
18 | var ereData ereJsonStruct
19 | err := json.Unmarshal(ereJsonData, &ereData)
20 | if err != nil {
21 | return nil, errors.WithStack(err)
22 | }
23 |
24 | var recordList []model.LocalRecord
25 | for _, result := range ereData.Result {
26 | poolType, err := strconv.ParseInt(result[0].(string), 10, 64)
27 | if err != nil {
28 | return nil, errors.WithStack(err)
29 | }
30 |
31 | for _, ereUnit := range result[1].([]interface{}) {
32 | ereUnitArray := ereUnit.([]interface{})
33 | recordList = append(recordList, model.LocalRecord{
34 | PoolType: poolType,
35 | PoolId: int64(ereUnitArray[4].(float64)),
36 | ItemId: int64(ereUnitArray[0].(float64)),
37 | GachaTimestamp: int64(ereUnitArray[5].(float64)),
38 | })
39 | }
40 | }
41 |
42 | return recordList, nil
43 | }
44 |
--------------------------------------------------------------------------------
/logic/RemoveLocalRecord.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "gf2gacha/model"
5 | "github.com/pkg/errors"
6 | )
7 |
8 | func RemoveLocalRecord(uid string, poolType int64) (err error) {
9 | session := model.Engine.NewSession()
10 | defer session.Close()
11 |
12 | _, err = session.Table(uid).Delete(&model.LocalRecord{PoolType: poolType})
13 | if err != nil {
14 | return errors.WithStack(err)
15 | }
16 |
17 | return nil
18 | }
19 |
--------------------------------------------------------------------------------
/logic/SaveLocalRecord.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "gf2gacha/model"
5 | "github.com/pkg/errors"
6 | )
7 |
8 | func SaveLocalRecord(uid string, recordList []model.LocalRecord) error {
9 | if uid == "" {
10 | return errors.New("uid为空")
11 | }
12 |
13 | session := model.Engine.NewSession()
14 | defer session.Close()
15 |
16 | err := session.Table(uid).Sync(new(model.LocalRecord))
17 | if err != nil {
18 | return errors.WithStack(err)
19 | }
20 |
21 | for len(recordList) > 0 {
22 | maxCap := min(100, len(recordList))
23 | _, err = session.Table(uid).Insert(recordList[:maxCap])
24 | if err != nil {
25 | return errors.WithStack(err)
26 | }
27 | recordList = recordList[maxCap:]
28 | }
29 |
30 | return nil
31 | }
32 |
--------------------------------------------------------------------------------
/logic/UpdatePoolInfo.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "fmt"
5 | "gf2gacha/logger"
6 | "gf2gacha/model"
7 | "gf2gacha/preload"
8 | "gf2gacha/util"
9 | "github.com/pkg/errors"
10 | "slices"
11 | )
12 |
13 | func UpdatePoolInfo(isFull bool) (messageList []string, err error) {
14 | logInfo, err := util.GetLogInfo()
15 | if err != nil {
16 | return nil, errors.WithStack(err)
17 | }
18 |
19 | messageList = append(messageList, logInfo.Uid)
20 | for _, poolTypeUnit := range preload.PoolTypeMap {
21 | if isFull {
22 | n, err := fullUpdatePoolInfo(logInfo, poolTypeUnit.Id)
23 | if err != nil {
24 | return nil, errors.WithStack(err)
25 | }
26 | messageList = append(messageList, fmt.Sprintf("%s 全量更新%d条数据", poolTypeUnit.Name, n))
27 | } else {
28 | n, err := incrementalUpdatePoolInfo(logInfo, poolTypeUnit.Id)
29 | if err != nil {
30 | return nil, errors.WithStack(err)
31 | }
32 | messageList = append(messageList, fmt.Sprintf("%s 增量更新%d条数据", poolTypeUnit.Name, n))
33 | }
34 | }
35 |
36 | return messageList, nil
37 | }
38 |
39 | // incrementalUpdatePoolInfo 增量更新
40 | func incrementalUpdatePoolInfo(logInfo model.LogInfo, poolType int64) (int, error) {
41 | localRecordList, err := GetLocalRecord(logInfo.Uid, poolType, 0)
42 | if err != nil {
43 | return 0, errors.WithStack(err)
44 | }
45 |
46 | var lastLocalRecord model.LocalRecord
47 |
48 | if len(localRecordList) > 0 {
49 | lastLocalRecord = localRecordList[len(localRecordList)-1]
50 | }
51 |
52 | var diffRemoteRecordList []model.RemoteRecord
53 | respData, err := FetchRemoteData(logInfo.GachaUrl, logInfo.AccessToken, "", poolType)
54 | if err != nil {
55 | return 0, errors.WithStack(err)
56 | }
57 |
58 | //todo 只对比一条是有可能有问题的
59 | var flag bool
60 | for i, remoteRecord := range respData.RecordList {
61 | if remoteRecord.ItemId == lastLocalRecord.ItemId && remoteRecord.GachaTimestamp == lastLocalRecord.GachaTimestamp {
62 | flag = true
63 | break
64 | } else {
65 | diffRemoteRecordList = append(diffRemoteRecordList, respData.RecordList[i])
66 | }
67 | }
68 | for respData.Next != "" && !flag {
69 | //time.Sleep(50 * time.Millisecond) //这个接口似乎没有限制频率
70 | respData, err = FetchRemoteData(logInfo.GachaUrl, logInfo.AccessToken, respData.Next, poolType)
71 | if err != nil {
72 | return 0, errors.WithStack(err)
73 | }
74 | for i, remoteRecord := range respData.RecordList {
75 | if remoteRecord.ItemId == lastLocalRecord.ItemId && remoteRecord.GachaTimestamp == lastLocalRecord.GachaTimestamp {
76 | flag = true
77 | break
78 | } else {
79 | diffRemoteRecordList = append(diffRemoteRecordList, respData.RecordList[i])
80 | }
81 | }
82 | }
83 |
84 | if len(diffRemoteRecordList) > 0 {
85 | var diffLocalRecordList []model.LocalRecord
86 | for i := len(diffRemoteRecordList) - 1; i >= 0; i-- {
87 | diffLocalRecordList = append(diffLocalRecordList, model.LocalRecord{
88 | PoolType: poolType,
89 | PoolId: diffRemoteRecordList[i].PoolId,
90 | ItemId: diffRemoteRecordList[i].ItemId,
91 | GachaTimestamp: diffRemoteRecordList[i].GachaTimestamp,
92 | })
93 | }
94 | err = SaveLocalRecord(logInfo.Uid, diffLocalRecordList)
95 | if err != nil {
96 | return 0, errors.WithStack(err)
97 | }
98 | }
99 |
100 | updateNum := len(diffRemoteRecordList)
101 | logger.Logger.Infof("UID:%s poolType:%d 增量更新%d条数据", logInfo.Uid, poolType, updateNum)
102 |
103 | return updateNum, nil
104 | }
105 |
106 | // fullUpdatePoolInfo 全量更新
107 | func fullUpdatePoolInfo(logInfo model.LogInfo, poolType int64) (int, error) {
108 | var remoteRecordList []model.LocalRecord
109 |
110 | respData, err := FetchRemoteData(logInfo.GachaUrl, logInfo.AccessToken, "", poolType)
111 | if err != nil {
112 | return 0, errors.WithStack(err)
113 | }
114 | for _, record := range respData.RecordList {
115 | remoteRecordList = append(remoteRecordList, model.LocalRecord{
116 | PoolType: poolType,
117 | PoolId: record.PoolId,
118 | ItemId: record.ItemId,
119 | GachaTimestamp: record.GachaTimestamp,
120 | })
121 | }
122 |
123 | for respData.Next != "" {
124 | //time.Sleep(50 * time.Millisecond) //这个接口似乎没有限制频率
125 | respData, err = FetchRemoteData(logInfo.GachaUrl, logInfo.AccessToken, respData.Next, poolType)
126 | if err != nil {
127 | return 0, errors.WithStack(err)
128 | }
129 | for _, record := range respData.RecordList {
130 | remoteRecordList = append(remoteRecordList, model.LocalRecord{
131 | PoolType: poolType,
132 | PoolId: record.PoolId,
133 | ItemId: record.ItemId,
134 | GachaTimestamp: record.GachaTimestamp,
135 | })
136 | }
137 | }
138 |
139 | if len(remoteRecordList) == 0 {
140 | return 0, nil
141 | }
142 | //合并前先备份
143 | err = util.BackupDB()
144 | if err != nil {
145 | return 0, errors.WithStack(err)
146 | }
147 |
148 | slices.Reverse(remoteRecordList)
149 |
150 | localRecordList, err := GetLocalRecord(logInfo.Uid, poolType, remoteRecordList[0].GachaTimestamp)
151 | if err != nil {
152 | return 0, errors.WithStack(err)
153 | }
154 |
155 | newRecordList := MergeRecord(remoteRecordList, localRecordList)
156 |
157 | err = RemoveLocalRecord(logInfo.Uid, poolType)
158 | if err != nil {
159 | return 0, errors.WithStack(err)
160 | }
161 |
162 | err = SaveLocalRecord(logInfo.Uid, newRecordList)
163 | if err != nil {
164 | return 0, errors.WithStack(err)
165 | }
166 |
167 | updateNum := len(newRecordList)
168 | logger.Logger.Infof("UID:%s poolType:%d 全量更新%d条数据", logInfo.Uid, poolType, updateNum)
169 |
170 | return updateNum, nil
171 | }
172 |
--------------------------------------------------------------------------------
/logic/UpdateTo.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "fmt"
5 | "github.com/inconshreveable/go-update"
6 | "github.com/pkg/errors"
7 | "net/http"
8 | "os"
9 | "os/exec"
10 | )
11 |
12 | func UpdateTo(version string) error {
13 | resp, err := http.Get(fmt.Sprintf("https://gfl2bucket.mcc.wiki/gf2gacha/%s/gf2gacha.exe", version))
14 | if err != nil {
15 | return err
16 | }
17 | defer resp.Body.Close()
18 | if resp.StatusCode != http.StatusOK {
19 | return errors.New(resp.Status)
20 | }
21 |
22 | err = update.Apply(resp.Body, update.Options{})
23 | if err != nil {
24 | return err
25 | }
26 |
27 | err = restart()
28 | if err != nil {
29 | return err
30 | }
31 | os.Exit(0)
32 |
33 | return nil
34 | }
35 |
36 | func restart() error {
37 | execPath, err := os.Executable()
38 | if err != nil {
39 | return err
40 | }
41 | cmd := exec.Command(execPath)
42 | return cmd.Start()
43 | }
44 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "embed"
5 | "fmt"
6 | "gf2gacha/logger"
7 | _ "gf2gacha/logger"
8 | "gf2gacha/util"
9 | "github.com/wailsapp/wails/v2"
10 | "github.com/wailsapp/wails/v2/pkg/options"
11 | "github.com/wailsapp/wails/v2/pkg/options/assetserver"
12 | )
13 |
14 | //go:embed all:frontend/dist
15 | var assets embed.FS
16 |
17 | func main() {
18 | defer logger.Logger.Sync()
19 | defer util.DisableSysProxy()
20 | // Create an instance of the app structure
21 | app := NewApp()
22 |
23 | // Create application with options
24 | err := wails.Run(&options.App{
25 | Title: fmt.Sprintf("少女前线2:追放 抽卡导出分析工具%v Powered by MccWiki抹茶", util.GetVersion()),
26 | Width: 1280,
27 | Height: 720,
28 | MinWidth: 1280,
29 | MinHeight: 720,
30 | AssetServer: &assetserver.Options{
31 | Assets: assets,
32 | },
33 | OnStartup: app.startup,
34 | Bind: []interface{}{
35 | app,
36 | },
37 | })
38 |
39 | if err != nil {
40 | logger.Logger.Error(err)
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/model/community.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | type CommunityExchangeList struct {
4 | Id int64 `json:"id"`
5 | Name string `json:"name"`
6 | }
7 |
--------------------------------------------------------------------------------
/model/init.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "gf2gacha/logger"
5 | _ "modernc.org/sqlite"
6 | "xorm.io/xorm"
7 | )
8 |
9 | var Engine *xorm.Engine
10 |
11 | func init() {
12 | var err error
13 | Engine, err = xorm.NewEngine("sqlite", "./gf2gacha.db?_pragma=busy_timeout(2000)")
14 | if err != nil {
15 | logger.Logger.Panic(err)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/model/logInfo.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | type LogInfo struct {
4 | TablePath string `json:"tablePath"`
5 | AccessToken string `json:"accessToken"`
6 | Uid string `json:"uid"`
7 | GachaUrl string `json:"gachaUrl"`
8 | }
9 |
--------------------------------------------------------------------------------
/model/pool.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | type Pool struct {
4 | PoolType int64 `json:"poolType"`
5 | GachaCount int64 `json:"gachaCount"`
6 | LoseCount int64 `json:"loseCount"`
7 | GuaranteesCount int64 `json:"guaranteesCount"`
8 | Rank5Count int64 `json:"rank5Count"`
9 | Rank4Count int64 `json:"rank4Count"`
10 | Rank3Count int64 `json:"rank3Count"`
11 | StoredCount int64 `json:"storedCount"`
12 | RecordList []DisplayRecord `json:"recordList"`
13 | }
14 |
--------------------------------------------------------------------------------
/model/record.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import "time"
4 |
5 | type RemoteRecord struct {
6 | PoolId int64 `json:"pool_id"`
7 | ItemId int64 `json:"item"`
8 | GachaTimestamp int64 `json:"time"`
9 | }
10 |
11 | type LocalRecord struct {
12 | Id int64
13 | InsertTime time.Time `xorm:"created"`
14 | PoolType int64
15 | PoolId int64
16 | ItemId int64
17 | GachaTimestamp int64
18 | }
19 |
20 | type DisplayRecord struct {
21 | Id int64 `json:"id"`
22 | Name string `json:"name"`
23 | Lose bool `json:"lose"`
24 | Count int64 `json:"count"`
25 | }
26 |
--------------------------------------------------------------------------------
/model/request.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import "encoding/json"
4 |
5 | type ResponseBody struct {
6 | Code int `json:"code"`
7 | Message string `json:"message"`
8 | Data json.RawMessage `json:"data"`
9 | }
10 |
11 | type ResponseData struct {
12 | RecordList []RemoteRecord `json:"list"`
13 | Next string `json:"next"`
14 | }
15 |
--------------------------------------------------------------------------------
/pb/GachaData.pb.go:
--------------------------------------------------------------------------------
1 | // Code generated by protoc-gen-go. DO NOT EDIT.
2 | // versions:
3 | // protoc-gen-go v1.31.0
4 | // protoc v4.25.1
5 | // source: proto/GachaData.proto
6 |
7 | package pb
8 |
9 | import (
10 | protoreflect "google.golang.org/protobuf/reflect/protoreflect"
11 | protoimpl "google.golang.org/protobuf/runtime/protoimpl"
12 | reflect "reflect"
13 | sync "sync"
14 | )
15 |
16 | const (
17 | // Verify that this generated code is sufficiently up-to-date.
18 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
19 | // Verify that runtime/protoimpl is sufficiently up-to-date.
20 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
21 | )
22 |
23 | type GachaData struct {
24 | state protoimpl.MessageState
25 | sizeCache protoimpl.SizeCache
26 | unknownFields protoimpl.UnknownFields
27 |
28 | Units []*GachaDataUnit `protobuf:"bytes,1,rep,name=Units,proto3" json:"Units,omitempty"`
29 | }
30 |
31 | func (x *GachaData) Reset() {
32 | *x = GachaData{}
33 | if protoimpl.UnsafeEnabled {
34 | mi := &file_proto_GachaData_proto_msgTypes[0]
35 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
36 | ms.StoreMessageInfo(mi)
37 | }
38 | }
39 |
40 | func (x *GachaData) String() string {
41 | return protoimpl.X.MessageStringOf(x)
42 | }
43 |
44 | func (*GachaData) ProtoMessage() {}
45 |
46 | func (x *GachaData) ProtoReflect() protoreflect.Message {
47 | mi := &file_proto_GachaData_proto_msgTypes[0]
48 | if protoimpl.UnsafeEnabled && x != nil {
49 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
50 | if ms.LoadMessageInfo() == nil {
51 | ms.StoreMessageInfo(mi)
52 | }
53 | return ms
54 | }
55 | return mi.MessageOf(x)
56 | }
57 |
58 | // Deprecated: Use GachaData.ProtoReflect.Descriptor instead.
59 | func (*GachaData) Descriptor() ([]byte, []int) {
60 | return file_proto_GachaData_proto_rawDescGZIP(), []int{0}
61 | }
62 |
63 | func (x *GachaData) GetUnits() []*GachaDataUnit {
64 | if x != nil {
65 | return x.Units
66 | }
67 | return nil
68 | }
69 |
70 | type GachaDataUnit struct {
71 | state protoimpl.MessageState
72 | sizeCache protoimpl.SizeCache
73 | unknownFields protoimpl.UnknownFields
74 |
75 | Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
76 | Type int64 `protobuf:"varint,4,opt,name=type,proto3" json:"type,omitempty"`
77 | StartTime int64 `protobuf:"varint,7,opt,name=startTime,proto3" json:"startTime,omitempty"`
78 | EndTime int64 `protobuf:"varint,8,opt,name=endTime,proto3" json:"endTime,omitempty"`
79 | Name *LanguageStringData `protobuf:"bytes,9,opt,name=name,proto3" json:"name,omitempty"`
80 | RateDesGun string `protobuf:"bytes,21,opt,name=rateDesGun,proto3" json:"rateDesGun,omitempty"`
81 | RateDesWeapon string `protobuf:"bytes,22,opt,name=rateDesWeapon,proto3" json:"rateDesWeapon,omitempty"`
82 | GunUpItem string `protobuf:"bytes,26,opt,name=gunUpItem,proto3" json:"gunUpItem,omitempty"`
83 | WeaponUpItem string `protobuf:"bytes,28,opt,name=weaponUpItem,proto3" json:"weaponUpItem,omitempty"`
84 | }
85 |
86 | func (x *GachaDataUnit) Reset() {
87 | *x = GachaDataUnit{}
88 | if protoimpl.UnsafeEnabled {
89 | mi := &file_proto_GachaData_proto_msgTypes[1]
90 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
91 | ms.StoreMessageInfo(mi)
92 | }
93 | }
94 |
95 | func (x *GachaDataUnit) String() string {
96 | return protoimpl.X.MessageStringOf(x)
97 | }
98 |
99 | func (*GachaDataUnit) ProtoMessage() {}
100 |
101 | func (x *GachaDataUnit) ProtoReflect() protoreflect.Message {
102 | mi := &file_proto_GachaData_proto_msgTypes[1]
103 | if protoimpl.UnsafeEnabled && x != nil {
104 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
105 | if ms.LoadMessageInfo() == nil {
106 | ms.StoreMessageInfo(mi)
107 | }
108 | return ms
109 | }
110 | return mi.MessageOf(x)
111 | }
112 |
113 | // Deprecated: Use GachaDataUnit.ProtoReflect.Descriptor instead.
114 | func (*GachaDataUnit) Descriptor() ([]byte, []int) {
115 | return file_proto_GachaData_proto_rawDescGZIP(), []int{1}
116 | }
117 |
118 | func (x *GachaDataUnit) GetId() int64 {
119 | if x != nil {
120 | return x.Id
121 | }
122 | return 0
123 | }
124 |
125 | func (x *GachaDataUnit) GetType() int64 {
126 | if x != nil {
127 | return x.Type
128 | }
129 | return 0
130 | }
131 |
132 | func (x *GachaDataUnit) GetStartTime() int64 {
133 | if x != nil {
134 | return x.StartTime
135 | }
136 | return 0
137 | }
138 |
139 | func (x *GachaDataUnit) GetEndTime() int64 {
140 | if x != nil {
141 | return x.EndTime
142 | }
143 | return 0
144 | }
145 |
146 | func (x *GachaDataUnit) GetName() *LanguageStringData {
147 | if x != nil {
148 | return x.Name
149 | }
150 | return nil
151 | }
152 |
153 | func (x *GachaDataUnit) GetRateDesGun() string {
154 | if x != nil {
155 | return x.RateDesGun
156 | }
157 | return ""
158 | }
159 |
160 | func (x *GachaDataUnit) GetRateDesWeapon() string {
161 | if x != nil {
162 | return x.RateDesWeapon
163 | }
164 | return ""
165 | }
166 |
167 | func (x *GachaDataUnit) GetGunUpItem() string {
168 | if x != nil {
169 | return x.GunUpItem
170 | }
171 | return ""
172 | }
173 |
174 | func (x *GachaDataUnit) GetWeaponUpItem() string {
175 | if x != nil {
176 | return x.WeaponUpItem
177 | }
178 | return ""
179 | }
180 |
181 | var File_proto_GachaData_proto protoreflect.FileDescriptor
182 |
183 | var file_proto_GachaData_proto_rawDesc = []byte{
184 | 0x0a, 0x15, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x47, 0x61, 0x63, 0x68, 0x61, 0x44, 0x61, 0x74,
185 | 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x4c,
186 | 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x44, 0x61, 0x74,
187 | 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x31, 0x0a, 0x09, 0x47, 0x61, 0x63, 0x68, 0x61,
188 | 0x44, 0x61, 0x74, 0x61, 0x12, 0x24, 0x0a, 0x05, 0x55, 0x6e, 0x69, 0x74, 0x73, 0x18, 0x01, 0x20,
189 | 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x47, 0x61, 0x63, 0x68, 0x61, 0x44, 0x61, 0x74, 0x61, 0x55,
190 | 0x6e, 0x69, 0x74, 0x52, 0x05, 0x55, 0x6e, 0x69, 0x74, 0x73, 0x22, 0x9c, 0x02, 0x0a, 0x0d, 0x47,
191 | 0x61, 0x63, 0x68, 0x61, 0x44, 0x61, 0x74, 0x61, 0x55, 0x6e, 0x69, 0x74, 0x12, 0x0e, 0x0a, 0x02,
192 | 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04,
193 | 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65,
194 | 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x18, 0x07, 0x20,
195 | 0x01, 0x28, 0x03, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x18,
196 | 0x0a, 0x07, 0x65, 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52,
197 | 0x07, 0x65, 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x27, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65,
198 | 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x4c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67,
199 | 0x65, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x44, 0x61, 0x74, 0x61, 0x52, 0x04, 0x6e, 0x61, 0x6d,
200 | 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x72, 0x61, 0x74, 0x65, 0x44, 0x65, 0x73, 0x47, 0x75, 0x6e, 0x18,
201 | 0x15, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x61, 0x74, 0x65, 0x44, 0x65, 0x73, 0x47, 0x75,
202 | 0x6e, 0x12, 0x24, 0x0a, 0x0d, 0x72, 0x61, 0x74, 0x65, 0x44, 0x65, 0x73, 0x57, 0x65, 0x61, 0x70,
203 | 0x6f, 0x6e, 0x18, 0x16, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x72, 0x61, 0x74, 0x65, 0x44, 0x65,
204 | 0x73, 0x57, 0x65, 0x61, 0x70, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x67, 0x75, 0x6e, 0x55, 0x70,
205 | 0x49, 0x74, 0x65, 0x6d, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x67, 0x75, 0x6e, 0x55,
206 | 0x70, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x22, 0x0a, 0x0c, 0x77, 0x65, 0x61, 0x70, 0x6f, 0x6e, 0x55,
207 | 0x70, 0x49, 0x74, 0x65, 0x6d, 0x18, 0x1c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x77, 0x65, 0x61,
208 | 0x70, 0x6f, 0x6e, 0x55, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x42, 0x09, 0x5a, 0x07, 0x2e, 0x2f, 0x70,
209 | 0x62, 0x3b, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
210 | }
211 |
212 | var (
213 | file_proto_GachaData_proto_rawDescOnce sync.Once
214 | file_proto_GachaData_proto_rawDescData = file_proto_GachaData_proto_rawDesc
215 | )
216 |
217 | func file_proto_GachaData_proto_rawDescGZIP() []byte {
218 | file_proto_GachaData_proto_rawDescOnce.Do(func() {
219 | file_proto_GachaData_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_GachaData_proto_rawDescData)
220 | })
221 | return file_proto_GachaData_proto_rawDescData
222 | }
223 |
224 | var file_proto_GachaData_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
225 | var file_proto_GachaData_proto_goTypes = []interface{}{
226 | (*GachaData)(nil), // 0: GachaData
227 | (*GachaDataUnit)(nil), // 1: GachaDataUnit
228 | (*LanguageStringData)(nil), // 2: LanguageStringData
229 | }
230 | var file_proto_GachaData_proto_depIdxs = []int32{
231 | 1, // 0: GachaData.Units:type_name -> GachaDataUnit
232 | 2, // 1: GachaDataUnit.name:type_name -> LanguageStringData
233 | 2, // [2:2] is the sub-list for method output_type
234 | 2, // [2:2] is the sub-list for method input_type
235 | 2, // [2:2] is the sub-list for extension type_name
236 | 2, // [2:2] is the sub-list for extension extendee
237 | 0, // [0:2] is the sub-list for field type_name
238 | }
239 |
240 | func init() { file_proto_GachaData_proto_init() }
241 | func file_proto_GachaData_proto_init() {
242 | if File_proto_GachaData_proto != nil {
243 | return
244 | }
245 | file_proto_LanguageStringData_proto_init()
246 | if !protoimpl.UnsafeEnabled {
247 | file_proto_GachaData_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
248 | switch v := v.(*GachaData); i {
249 | case 0:
250 | return &v.state
251 | case 1:
252 | return &v.sizeCache
253 | case 2:
254 | return &v.unknownFields
255 | default:
256 | return nil
257 | }
258 | }
259 | file_proto_GachaData_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
260 | switch v := v.(*GachaDataUnit); i {
261 | case 0:
262 | return &v.state
263 | case 1:
264 | return &v.sizeCache
265 | case 2:
266 | return &v.unknownFields
267 | default:
268 | return nil
269 | }
270 | }
271 | }
272 | type x struct{}
273 | out := protoimpl.TypeBuilder{
274 | File: protoimpl.DescBuilder{
275 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
276 | RawDescriptor: file_proto_GachaData_proto_rawDesc,
277 | NumEnums: 0,
278 | NumMessages: 2,
279 | NumExtensions: 0,
280 | NumServices: 0,
281 | },
282 | GoTypes: file_proto_GachaData_proto_goTypes,
283 | DependencyIndexes: file_proto_GachaData_proto_depIdxs,
284 | MessageInfos: file_proto_GachaData_proto_msgTypes,
285 | }.Build()
286 | File_proto_GachaData_proto = out.File
287 | file_proto_GachaData_proto_rawDesc = nil
288 | file_proto_GachaData_proto_goTypes = nil
289 | file_proto_GachaData_proto_depIdxs = nil
290 | }
291 |
--------------------------------------------------------------------------------
/pb/GachaTypeListData.pb.go:
--------------------------------------------------------------------------------
1 | // Code generated by protoc-gen-go. DO NOT EDIT.
2 | // versions:
3 | // protoc-gen-go v1.31.0
4 | // protoc v4.25.1
5 | // source: proto/GachaTypeListData.proto
6 |
7 | package pb
8 |
9 | import (
10 | protoreflect "google.golang.org/protobuf/reflect/protoreflect"
11 | protoimpl "google.golang.org/protobuf/runtime/protoimpl"
12 | reflect "reflect"
13 | sync "sync"
14 | )
15 |
16 | const (
17 | // Verify that this generated code is sufficiently up-to-date.
18 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
19 | // Verify that runtime/protoimpl is sufficiently up-to-date.
20 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
21 | )
22 |
23 | type GachaTypeListData struct {
24 | state protoimpl.MessageState
25 | sizeCache protoimpl.SizeCache
26 | unknownFields protoimpl.UnknownFields
27 |
28 | Units []*GachaTypeListDataUnit `protobuf:"bytes,1,rep,name=Units,proto3" json:"Units,omitempty"`
29 | }
30 |
31 | func (x *GachaTypeListData) Reset() {
32 | *x = GachaTypeListData{}
33 | if protoimpl.UnsafeEnabled {
34 | mi := &file_proto_GachaTypeListData_proto_msgTypes[0]
35 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
36 | ms.StoreMessageInfo(mi)
37 | }
38 | }
39 |
40 | func (x *GachaTypeListData) String() string {
41 | return protoimpl.X.MessageStringOf(x)
42 | }
43 |
44 | func (*GachaTypeListData) ProtoMessage() {}
45 |
46 | func (x *GachaTypeListData) ProtoReflect() protoreflect.Message {
47 | mi := &file_proto_GachaTypeListData_proto_msgTypes[0]
48 | if protoimpl.UnsafeEnabled && x != nil {
49 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
50 | if ms.LoadMessageInfo() == nil {
51 | ms.StoreMessageInfo(mi)
52 | }
53 | return ms
54 | }
55 | return mi.MessageOf(x)
56 | }
57 |
58 | // Deprecated: Use GachaTypeListData.ProtoReflect.Descriptor instead.
59 | func (*GachaTypeListData) Descriptor() ([]byte, []int) {
60 | return file_proto_GachaTypeListData_proto_rawDescGZIP(), []int{0}
61 | }
62 |
63 | func (x *GachaTypeListData) GetUnits() []*GachaTypeListDataUnit {
64 | if x != nil {
65 | return x.Units
66 | }
67 | return nil
68 | }
69 |
70 | type GachaTypeListDataUnit struct {
71 | state protoimpl.MessageState
72 | sizeCache protoimpl.SizeCache
73 | unknownFields protoimpl.UnknownFields
74 |
75 | Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
76 | Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
77 | }
78 |
79 | func (x *GachaTypeListDataUnit) Reset() {
80 | *x = GachaTypeListDataUnit{}
81 | if protoimpl.UnsafeEnabled {
82 | mi := &file_proto_GachaTypeListData_proto_msgTypes[1]
83 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
84 | ms.StoreMessageInfo(mi)
85 | }
86 | }
87 |
88 | func (x *GachaTypeListDataUnit) String() string {
89 | return protoimpl.X.MessageStringOf(x)
90 | }
91 |
92 | func (*GachaTypeListDataUnit) ProtoMessage() {}
93 |
94 | func (x *GachaTypeListDataUnit) ProtoReflect() protoreflect.Message {
95 | mi := &file_proto_GachaTypeListData_proto_msgTypes[1]
96 | if protoimpl.UnsafeEnabled && x != nil {
97 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
98 | if ms.LoadMessageInfo() == nil {
99 | ms.StoreMessageInfo(mi)
100 | }
101 | return ms
102 | }
103 | return mi.MessageOf(x)
104 | }
105 |
106 | // Deprecated: Use GachaTypeListDataUnit.ProtoReflect.Descriptor instead.
107 | func (*GachaTypeListDataUnit) Descriptor() ([]byte, []int) {
108 | return file_proto_GachaTypeListData_proto_rawDescGZIP(), []int{1}
109 | }
110 |
111 | func (x *GachaTypeListDataUnit) GetId() int64 {
112 | if x != nil {
113 | return x.Id
114 | }
115 | return 0
116 | }
117 |
118 | func (x *GachaTypeListDataUnit) GetName() string {
119 | if x != nil {
120 | return x.Name
121 | }
122 | return ""
123 | }
124 |
125 | var File_proto_GachaTypeListData_proto protoreflect.FileDescriptor
126 |
127 | var file_proto_GachaTypeListData_proto_rawDesc = []byte{
128 | 0x0a, 0x1d, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x47, 0x61, 0x63, 0x68, 0x61, 0x54, 0x79, 0x70,
129 | 0x65, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x61, 0x74, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22,
130 | 0x41, 0x0a, 0x11, 0x47, 0x61, 0x63, 0x68, 0x61, 0x54, 0x79, 0x70, 0x65, 0x4c, 0x69, 0x73, 0x74,
131 | 0x44, 0x61, 0x74, 0x61, 0x12, 0x2c, 0x0a, 0x05, 0x55, 0x6e, 0x69, 0x74, 0x73, 0x18, 0x01, 0x20,
132 | 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x47, 0x61, 0x63, 0x68, 0x61, 0x54, 0x79, 0x70, 0x65, 0x4c,
133 | 0x69, 0x73, 0x74, 0x44, 0x61, 0x74, 0x61, 0x55, 0x6e, 0x69, 0x74, 0x52, 0x05, 0x55, 0x6e, 0x69,
134 | 0x74, 0x73, 0x22, 0x3b, 0x0a, 0x15, 0x47, 0x61, 0x63, 0x68, 0x61, 0x54, 0x79, 0x70, 0x65, 0x4c,
135 | 0x69, 0x73, 0x74, 0x44, 0x61, 0x74, 0x61, 0x55, 0x6e, 0x69, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69,
136 | 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e,
137 | 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x42,
138 | 0x09, 0x5a, 0x07, 0x2e, 0x2f, 0x70, 0x62, 0x3b, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
139 | 0x6f, 0x33,
140 | }
141 |
142 | var (
143 | file_proto_GachaTypeListData_proto_rawDescOnce sync.Once
144 | file_proto_GachaTypeListData_proto_rawDescData = file_proto_GachaTypeListData_proto_rawDesc
145 | )
146 |
147 | func file_proto_GachaTypeListData_proto_rawDescGZIP() []byte {
148 | file_proto_GachaTypeListData_proto_rawDescOnce.Do(func() {
149 | file_proto_GachaTypeListData_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_GachaTypeListData_proto_rawDescData)
150 | })
151 | return file_proto_GachaTypeListData_proto_rawDescData
152 | }
153 |
154 | var file_proto_GachaTypeListData_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
155 | var file_proto_GachaTypeListData_proto_goTypes = []interface{}{
156 | (*GachaTypeListData)(nil), // 0: GachaTypeListData
157 | (*GachaTypeListDataUnit)(nil), // 1: GachaTypeListDataUnit
158 | }
159 | var file_proto_GachaTypeListData_proto_depIdxs = []int32{
160 | 1, // 0: GachaTypeListData.Units:type_name -> GachaTypeListDataUnit
161 | 1, // [1:1] is the sub-list for method output_type
162 | 1, // [1:1] is the sub-list for method input_type
163 | 1, // [1:1] is the sub-list for extension type_name
164 | 1, // [1:1] is the sub-list for extension extendee
165 | 0, // [0:1] is the sub-list for field type_name
166 | }
167 |
168 | func init() { file_proto_GachaTypeListData_proto_init() }
169 | func file_proto_GachaTypeListData_proto_init() {
170 | if File_proto_GachaTypeListData_proto != nil {
171 | return
172 | }
173 | if !protoimpl.UnsafeEnabled {
174 | file_proto_GachaTypeListData_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
175 | switch v := v.(*GachaTypeListData); i {
176 | case 0:
177 | return &v.state
178 | case 1:
179 | return &v.sizeCache
180 | case 2:
181 | return &v.unknownFields
182 | default:
183 | return nil
184 | }
185 | }
186 | file_proto_GachaTypeListData_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
187 | switch v := v.(*GachaTypeListDataUnit); i {
188 | case 0:
189 | return &v.state
190 | case 1:
191 | return &v.sizeCache
192 | case 2:
193 | return &v.unknownFields
194 | default:
195 | return nil
196 | }
197 | }
198 | }
199 | type x struct{}
200 | out := protoimpl.TypeBuilder{
201 | File: protoimpl.DescBuilder{
202 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
203 | RawDescriptor: file_proto_GachaTypeListData_proto_rawDesc,
204 | NumEnums: 0,
205 | NumMessages: 2,
206 | NumExtensions: 0,
207 | NumServices: 0,
208 | },
209 | GoTypes: file_proto_GachaTypeListData_proto_goTypes,
210 | DependencyIndexes: file_proto_GachaTypeListData_proto_depIdxs,
211 | MessageInfos: file_proto_GachaTypeListData_proto_msgTypes,
212 | }.Build()
213 | File_proto_GachaTypeListData_proto = out.File
214 | file_proto_GachaTypeListData_proto_rawDesc = nil
215 | file_proto_GachaTypeListData_proto_goTypes = nil
216 | file_proto_GachaTypeListData_proto_depIdxs = nil
217 | }
218 |
--------------------------------------------------------------------------------
/pb/ItemData.pb.go:
--------------------------------------------------------------------------------
1 | // Code generated by protoc-gen-go. DO NOT EDIT.
2 | // versions:
3 | // protoc-gen-go v1.31.0
4 | // protoc v4.25.1
5 | // source: proto/ItemData.proto
6 |
7 | package pb
8 |
9 | import (
10 | protoreflect "google.golang.org/protobuf/reflect/protoreflect"
11 | protoimpl "google.golang.org/protobuf/runtime/protoimpl"
12 | reflect "reflect"
13 | sync "sync"
14 | )
15 |
16 | const (
17 | // Verify that this generated code is sufficiently up-to-date.
18 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
19 | // Verify that runtime/protoimpl is sufficiently up-to-date.
20 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
21 | )
22 |
23 | type ItemData struct {
24 | state protoimpl.MessageState
25 | sizeCache protoimpl.SizeCache
26 | unknownFields protoimpl.UnknownFields
27 |
28 | Units []*ItemDataUnit `protobuf:"bytes,1,rep,name=Units,proto3" json:"Units,omitempty"`
29 | }
30 |
31 | func (x *ItemData) Reset() {
32 | *x = ItemData{}
33 | if protoimpl.UnsafeEnabled {
34 | mi := &file_proto_ItemData_proto_msgTypes[0]
35 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
36 | ms.StoreMessageInfo(mi)
37 | }
38 | }
39 |
40 | func (x *ItemData) String() string {
41 | return protoimpl.X.MessageStringOf(x)
42 | }
43 |
44 | func (*ItemData) ProtoMessage() {}
45 |
46 | func (x *ItemData) ProtoReflect() protoreflect.Message {
47 | mi := &file_proto_ItemData_proto_msgTypes[0]
48 | if protoimpl.UnsafeEnabled && x != nil {
49 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
50 | if ms.LoadMessageInfo() == nil {
51 | ms.StoreMessageInfo(mi)
52 | }
53 | return ms
54 | }
55 | return mi.MessageOf(x)
56 | }
57 |
58 | // Deprecated: Use ItemData.ProtoReflect.Descriptor instead.
59 | func (*ItemData) Descriptor() ([]byte, []int) {
60 | return file_proto_ItemData_proto_rawDescGZIP(), []int{0}
61 | }
62 |
63 | func (x *ItemData) GetUnits() []*ItemDataUnit {
64 | if x != nil {
65 | return x.Units
66 | }
67 | return nil
68 | }
69 |
70 | type ItemDataUnit struct {
71 | state protoimpl.MessageState
72 | sizeCache protoimpl.SizeCache
73 | unknownFields protoimpl.UnknownFields
74 |
75 | Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
76 | Name *LanguageStringData `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
77 | Type int64 `protobuf:"varint,5,opt,name=type,proto3" json:"type,omitempty"`
78 | Icon string `protobuf:"bytes,6,opt,name=icon,proto3" json:"icon,omitempty"`
79 | Rank int64 `protobuf:"varint,7,opt,name=rank,proto3" json:"rank,omitempty"`
80 | }
81 |
82 | func (x *ItemDataUnit) Reset() {
83 | *x = ItemDataUnit{}
84 | if protoimpl.UnsafeEnabled {
85 | mi := &file_proto_ItemData_proto_msgTypes[1]
86 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
87 | ms.StoreMessageInfo(mi)
88 | }
89 | }
90 |
91 | func (x *ItemDataUnit) String() string {
92 | return protoimpl.X.MessageStringOf(x)
93 | }
94 |
95 | func (*ItemDataUnit) ProtoMessage() {}
96 |
97 | func (x *ItemDataUnit) ProtoReflect() protoreflect.Message {
98 | mi := &file_proto_ItemData_proto_msgTypes[1]
99 | if protoimpl.UnsafeEnabled && x != nil {
100 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
101 | if ms.LoadMessageInfo() == nil {
102 | ms.StoreMessageInfo(mi)
103 | }
104 | return ms
105 | }
106 | return mi.MessageOf(x)
107 | }
108 |
109 | // Deprecated: Use ItemDataUnit.ProtoReflect.Descriptor instead.
110 | func (*ItemDataUnit) Descriptor() ([]byte, []int) {
111 | return file_proto_ItemData_proto_rawDescGZIP(), []int{1}
112 | }
113 |
114 | func (x *ItemDataUnit) GetId() int64 {
115 | if x != nil {
116 | return x.Id
117 | }
118 | return 0
119 | }
120 |
121 | func (x *ItemDataUnit) GetName() *LanguageStringData {
122 | if x != nil {
123 | return x.Name
124 | }
125 | return nil
126 | }
127 |
128 | func (x *ItemDataUnit) GetType() int64 {
129 | if x != nil {
130 | return x.Type
131 | }
132 | return 0
133 | }
134 |
135 | func (x *ItemDataUnit) GetIcon() string {
136 | if x != nil {
137 | return x.Icon
138 | }
139 | return ""
140 | }
141 |
142 | func (x *ItemDataUnit) GetRank() int64 {
143 | if x != nil {
144 | return x.Rank
145 | }
146 | return 0
147 | }
148 |
149 | var File_proto_ItemData_proto protoreflect.FileDescriptor
150 |
151 | var file_proto_ItemData_proto_rawDesc = []byte{
152 | 0x0a, 0x14, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x49, 0x74, 0x65, 0x6d, 0x44, 0x61, 0x74, 0x61,
153 | 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x4c, 0x61,
154 | 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x44, 0x61, 0x74, 0x61,
155 | 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x2f, 0x0a, 0x08, 0x49, 0x74, 0x65, 0x6d, 0x44, 0x61,
156 | 0x74, 0x61, 0x12, 0x23, 0x0a, 0x05, 0x55, 0x6e, 0x69, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28,
157 | 0x0b, 0x32, 0x0d, 0x2e, 0x49, 0x74, 0x65, 0x6d, 0x44, 0x61, 0x74, 0x61, 0x55, 0x6e, 0x69, 0x74,
158 | 0x52, 0x05, 0x55, 0x6e, 0x69, 0x74, 0x73, 0x22, 0x83, 0x01, 0x0a, 0x0c, 0x49, 0x74, 0x65, 0x6d,
159 | 0x44, 0x61, 0x74, 0x61, 0x55, 0x6e, 0x69, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01,
160 | 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x12, 0x27, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65,
161 | 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x4c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67,
162 | 0x65, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x44, 0x61, 0x74, 0x61, 0x52, 0x04, 0x6e, 0x61, 0x6d,
163 | 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52,
164 | 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x18, 0x06, 0x20,
165 | 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x61, 0x6e,
166 | 0x6b, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x72, 0x61, 0x6e, 0x6b, 0x42, 0x09, 0x5a,
167 | 0x07, 0x2e, 0x2f, 0x70, 0x62, 0x3b, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
168 | }
169 |
170 | var (
171 | file_proto_ItemData_proto_rawDescOnce sync.Once
172 | file_proto_ItemData_proto_rawDescData = file_proto_ItemData_proto_rawDesc
173 | )
174 |
175 | func file_proto_ItemData_proto_rawDescGZIP() []byte {
176 | file_proto_ItemData_proto_rawDescOnce.Do(func() {
177 | file_proto_ItemData_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_ItemData_proto_rawDescData)
178 | })
179 | return file_proto_ItemData_proto_rawDescData
180 | }
181 |
182 | var file_proto_ItemData_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
183 | var file_proto_ItemData_proto_goTypes = []interface{}{
184 | (*ItemData)(nil), // 0: ItemData
185 | (*ItemDataUnit)(nil), // 1: ItemDataUnit
186 | (*LanguageStringData)(nil), // 2: LanguageStringData
187 | }
188 | var file_proto_ItemData_proto_depIdxs = []int32{
189 | 1, // 0: ItemData.Units:type_name -> ItemDataUnit
190 | 2, // 1: ItemDataUnit.name:type_name -> LanguageStringData
191 | 2, // [2:2] is the sub-list for method output_type
192 | 2, // [2:2] is the sub-list for method input_type
193 | 2, // [2:2] is the sub-list for extension type_name
194 | 2, // [2:2] is the sub-list for extension extendee
195 | 0, // [0:2] is the sub-list for field type_name
196 | }
197 |
198 | func init() { file_proto_ItemData_proto_init() }
199 | func file_proto_ItemData_proto_init() {
200 | if File_proto_ItemData_proto != nil {
201 | return
202 | }
203 | file_proto_LanguageStringData_proto_init()
204 | if !protoimpl.UnsafeEnabled {
205 | file_proto_ItemData_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
206 | switch v := v.(*ItemData); i {
207 | case 0:
208 | return &v.state
209 | case 1:
210 | return &v.sizeCache
211 | case 2:
212 | return &v.unknownFields
213 | default:
214 | return nil
215 | }
216 | }
217 | file_proto_ItemData_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
218 | switch v := v.(*ItemDataUnit); i {
219 | case 0:
220 | return &v.state
221 | case 1:
222 | return &v.sizeCache
223 | case 2:
224 | return &v.unknownFields
225 | default:
226 | return nil
227 | }
228 | }
229 | }
230 | type x struct{}
231 | out := protoimpl.TypeBuilder{
232 | File: protoimpl.DescBuilder{
233 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
234 | RawDescriptor: file_proto_ItemData_proto_rawDesc,
235 | NumEnums: 0,
236 | NumMessages: 2,
237 | NumExtensions: 0,
238 | NumServices: 0,
239 | },
240 | GoTypes: file_proto_ItemData_proto_goTypes,
241 | DependencyIndexes: file_proto_ItemData_proto_depIdxs,
242 | MessageInfos: file_proto_ItemData_proto_msgTypes,
243 | }.Build()
244 | File_proto_ItemData_proto = out.File
245 | file_proto_ItemData_proto_rawDesc = nil
246 | file_proto_ItemData_proto_goTypes = nil
247 | file_proto_ItemData_proto_depIdxs = nil
248 | }
249 |
--------------------------------------------------------------------------------
/pb/LangPackageTableCnData.pb.go:
--------------------------------------------------------------------------------
1 | // Code generated by protoc-gen-go. DO NOT EDIT.
2 | // versions:
3 | // protoc-gen-go v1.31.0
4 | // protoc v4.25.1
5 | // source: proto/LangPackageTableCnData.proto
6 |
7 | package pb
8 |
9 | import (
10 | protoreflect "google.golang.org/protobuf/reflect/protoreflect"
11 | protoimpl "google.golang.org/protobuf/runtime/protoimpl"
12 | reflect "reflect"
13 | sync "sync"
14 | )
15 |
16 | const (
17 | // Verify that this generated code is sufficiently up-to-date.
18 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
19 | // Verify that runtime/protoimpl is sufficiently up-to-date.
20 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
21 | )
22 |
23 | type LangPackageTableCnData struct {
24 | state protoimpl.MessageState
25 | sizeCache protoimpl.SizeCache
26 | unknownFields protoimpl.UnknownFields
27 |
28 | Units []*LangPackageTableCnDataUnit `protobuf:"bytes,1,rep,name=Units,proto3" json:"Units,omitempty"`
29 | }
30 |
31 | func (x *LangPackageTableCnData) Reset() {
32 | *x = LangPackageTableCnData{}
33 | if protoimpl.UnsafeEnabled {
34 | mi := &file_proto_LangPackageTableCnData_proto_msgTypes[0]
35 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
36 | ms.StoreMessageInfo(mi)
37 | }
38 | }
39 |
40 | func (x *LangPackageTableCnData) String() string {
41 | return protoimpl.X.MessageStringOf(x)
42 | }
43 |
44 | func (*LangPackageTableCnData) ProtoMessage() {}
45 |
46 | func (x *LangPackageTableCnData) ProtoReflect() protoreflect.Message {
47 | mi := &file_proto_LangPackageTableCnData_proto_msgTypes[0]
48 | if protoimpl.UnsafeEnabled && x != nil {
49 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
50 | if ms.LoadMessageInfo() == nil {
51 | ms.StoreMessageInfo(mi)
52 | }
53 | return ms
54 | }
55 | return mi.MessageOf(x)
56 | }
57 |
58 | // Deprecated: Use LangPackageTableCnData.ProtoReflect.Descriptor instead.
59 | func (*LangPackageTableCnData) Descriptor() ([]byte, []int) {
60 | return file_proto_LangPackageTableCnData_proto_rawDescGZIP(), []int{0}
61 | }
62 |
63 | func (x *LangPackageTableCnData) GetUnits() []*LangPackageTableCnDataUnit {
64 | if x != nil {
65 | return x.Units
66 | }
67 | return nil
68 | }
69 |
70 | type LangPackageTableCnDataUnit struct {
71 | state protoimpl.MessageState
72 | sizeCache protoimpl.SizeCache
73 | unknownFields protoimpl.UnknownFields
74 |
75 | Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
76 | Content string `protobuf:"bytes,2,opt,name=content,proto3" json:"content,omitempty"`
77 | }
78 |
79 | func (x *LangPackageTableCnDataUnit) Reset() {
80 | *x = LangPackageTableCnDataUnit{}
81 | if protoimpl.UnsafeEnabled {
82 | mi := &file_proto_LangPackageTableCnData_proto_msgTypes[1]
83 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
84 | ms.StoreMessageInfo(mi)
85 | }
86 | }
87 |
88 | func (x *LangPackageTableCnDataUnit) String() string {
89 | return protoimpl.X.MessageStringOf(x)
90 | }
91 |
92 | func (*LangPackageTableCnDataUnit) ProtoMessage() {}
93 |
94 | func (x *LangPackageTableCnDataUnit) ProtoReflect() protoreflect.Message {
95 | mi := &file_proto_LangPackageTableCnData_proto_msgTypes[1]
96 | if protoimpl.UnsafeEnabled && x != nil {
97 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
98 | if ms.LoadMessageInfo() == nil {
99 | ms.StoreMessageInfo(mi)
100 | }
101 | return ms
102 | }
103 | return mi.MessageOf(x)
104 | }
105 |
106 | // Deprecated: Use LangPackageTableCnDataUnit.ProtoReflect.Descriptor instead.
107 | func (*LangPackageTableCnDataUnit) Descriptor() ([]byte, []int) {
108 | return file_proto_LangPackageTableCnData_proto_rawDescGZIP(), []int{1}
109 | }
110 |
111 | func (x *LangPackageTableCnDataUnit) GetId() int64 {
112 | if x != nil {
113 | return x.Id
114 | }
115 | return 0
116 | }
117 |
118 | func (x *LangPackageTableCnDataUnit) GetContent() string {
119 | if x != nil {
120 | return x.Content
121 | }
122 | return ""
123 | }
124 |
125 | var File_proto_LangPackageTableCnData_proto protoreflect.FileDescriptor
126 |
127 | var file_proto_LangPackageTableCnData_proto_rawDesc = []byte{
128 | 0x0a, 0x22, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x4c, 0x61, 0x6e, 0x67, 0x50, 0x61, 0x63, 0x6b,
129 | 0x61, 0x67, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x2e, 0x70,
130 | 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x4b, 0x0a, 0x16, 0x4c, 0x61, 0x6e, 0x67, 0x50, 0x61, 0x63, 0x6b,
131 | 0x61, 0x67, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x12, 0x31,
132 | 0x0a, 0x05, 0x55, 0x6e, 0x69, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e,
133 | 0x4c, 0x61, 0x6e, 0x67, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65,
134 | 0x43, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x55, 0x6e, 0x69, 0x74, 0x52, 0x05, 0x55, 0x6e, 0x69, 0x74,
135 | 0x73, 0x22, 0x46, 0x0a, 0x1a, 0x4c, 0x61, 0x6e, 0x67, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65,
136 | 0x54, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x55, 0x6e, 0x69, 0x74, 0x12,
137 | 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x12,
138 | 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
139 | 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x42, 0x09, 0x5a, 0x07, 0x2e, 0x2f, 0x70,
140 | 0x62, 0x3b, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
141 | }
142 |
143 | var (
144 | file_proto_LangPackageTableCnData_proto_rawDescOnce sync.Once
145 | file_proto_LangPackageTableCnData_proto_rawDescData = file_proto_LangPackageTableCnData_proto_rawDesc
146 | )
147 |
148 | func file_proto_LangPackageTableCnData_proto_rawDescGZIP() []byte {
149 | file_proto_LangPackageTableCnData_proto_rawDescOnce.Do(func() {
150 | file_proto_LangPackageTableCnData_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_LangPackageTableCnData_proto_rawDescData)
151 | })
152 | return file_proto_LangPackageTableCnData_proto_rawDescData
153 | }
154 |
155 | var file_proto_LangPackageTableCnData_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
156 | var file_proto_LangPackageTableCnData_proto_goTypes = []interface{}{
157 | (*LangPackageTableCnData)(nil), // 0: LangPackageTableCnData
158 | (*LangPackageTableCnDataUnit)(nil), // 1: LangPackageTableCnDataUnit
159 | }
160 | var file_proto_LangPackageTableCnData_proto_depIdxs = []int32{
161 | 1, // 0: LangPackageTableCnData.Units:type_name -> LangPackageTableCnDataUnit
162 | 1, // [1:1] is the sub-list for method output_type
163 | 1, // [1:1] is the sub-list for method input_type
164 | 1, // [1:1] is the sub-list for extension type_name
165 | 1, // [1:1] is the sub-list for extension extendee
166 | 0, // [0:1] is the sub-list for field type_name
167 | }
168 |
169 | func init() { file_proto_LangPackageTableCnData_proto_init() }
170 | func file_proto_LangPackageTableCnData_proto_init() {
171 | if File_proto_LangPackageTableCnData_proto != nil {
172 | return
173 | }
174 | if !protoimpl.UnsafeEnabled {
175 | file_proto_LangPackageTableCnData_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
176 | switch v := v.(*LangPackageTableCnData); i {
177 | case 0:
178 | return &v.state
179 | case 1:
180 | return &v.sizeCache
181 | case 2:
182 | return &v.unknownFields
183 | default:
184 | return nil
185 | }
186 | }
187 | file_proto_LangPackageTableCnData_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
188 | switch v := v.(*LangPackageTableCnDataUnit); i {
189 | case 0:
190 | return &v.state
191 | case 1:
192 | return &v.sizeCache
193 | case 2:
194 | return &v.unknownFields
195 | default:
196 | return nil
197 | }
198 | }
199 | }
200 | type x struct{}
201 | out := protoimpl.TypeBuilder{
202 | File: protoimpl.DescBuilder{
203 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
204 | RawDescriptor: file_proto_LangPackageTableCnData_proto_rawDesc,
205 | NumEnums: 0,
206 | NumMessages: 2,
207 | NumExtensions: 0,
208 | NumServices: 0,
209 | },
210 | GoTypes: file_proto_LangPackageTableCnData_proto_goTypes,
211 | DependencyIndexes: file_proto_LangPackageTableCnData_proto_depIdxs,
212 | MessageInfos: file_proto_LangPackageTableCnData_proto_msgTypes,
213 | }.Build()
214 | File_proto_LangPackageTableCnData_proto = out.File
215 | file_proto_LangPackageTableCnData_proto_rawDesc = nil
216 | file_proto_LangPackageTableCnData_proto_goTypes = nil
217 | file_proto_LangPackageTableCnData_proto_depIdxs = nil
218 | }
219 |
--------------------------------------------------------------------------------
/pb/LanguageStringData.pb.go:
--------------------------------------------------------------------------------
1 | // Code generated by protoc-gen-go. DO NOT EDIT.
2 | // versions:
3 | // protoc-gen-go v1.31.0
4 | // protoc v4.25.1
5 | // source: proto/LanguageStringData.proto
6 |
7 | package pb
8 |
9 | import (
10 | protoreflect "google.golang.org/protobuf/reflect/protoreflect"
11 | protoimpl "google.golang.org/protobuf/runtime/protoimpl"
12 | reflect "reflect"
13 | sync "sync"
14 | )
15 |
16 | const (
17 | // Verify that this generated code is sufficiently up-to-date.
18 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
19 | // Verify that runtime/protoimpl is sufficiently up-to-date.
20 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
21 | )
22 |
23 | type LanguageStringData struct {
24 | state protoimpl.MessageState
25 | sizeCache protoimpl.SizeCache
26 | unknownFields protoimpl.UnknownFields
27 |
28 | Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
29 | }
30 |
31 | func (x *LanguageStringData) Reset() {
32 | *x = LanguageStringData{}
33 | if protoimpl.UnsafeEnabled {
34 | mi := &file_proto_LanguageStringData_proto_msgTypes[0]
35 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
36 | ms.StoreMessageInfo(mi)
37 | }
38 | }
39 |
40 | func (x *LanguageStringData) String() string {
41 | return protoimpl.X.MessageStringOf(x)
42 | }
43 |
44 | func (*LanguageStringData) ProtoMessage() {}
45 |
46 | func (x *LanguageStringData) ProtoReflect() protoreflect.Message {
47 | mi := &file_proto_LanguageStringData_proto_msgTypes[0]
48 | if protoimpl.UnsafeEnabled && x != nil {
49 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
50 | if ms.LoadMessageInfo() == nil {
51 | ms.StoreMessageInfo(mi)
52 | }
53 | return ms
54 | }
55 | return mi.MessageOf(x)
56 | }
57 |
58 | // Deprecated: Use LanguageStringData.ProtoReflect.Descriptor instead.
59 | func (*LanguageStringData) Descriptor() ([]byte, []int) {
60 | return file_proto_LanguageStringData_proto_rawDescGZIP(), []int{0}
61 | }
62 |
63 | func (x *LanguageStringData) GetId() int64 {
64 | if x != nil {
65 | return x.Id
66 | }
67 | return 0
68 | }
69 |
70 | var File_proto_LanguageStringData_proto protoreflect.FileDescriptor
71 |
72 | var file_proto_LanguageStringData_proto_rawDesc = []byte{
73 | 0x0a, 0x1e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x4c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65,
74 | 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x44, 0x61, 0x74, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
75 | 0x22, 0x24, 0x0a, 0x12, 0x4c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x53, 0x74, 0x72, 0x69,
76 | 0x6e, 0x67, 0x44, 0x61, 0x74, 0x61, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
77 | 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x42, 0x09, 0x5a, 0x07, 0x2e, 0x2f, 0x70, 0x62, 0x3b, 0x70,
78 | 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
79 | }
80 |
81 | var (
82 | file_proto_LanguageStringData_proto_rawDescOnce sync.Once
83 | file_proto_LanguageStringData_proto_rawDescData = file_proto_LanguageStringData_proto_rawDesc
84 | )
85 |
86 | func file_proto_LanguageStringData_proto_rawDescGZIP() []byte {
87 | file_proto_LanguageStringData_proto_rawDescOnce.Do(func() {
88 | file_proto_LanguageStringData_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_LanguageStringData_proto_rawDescData)
89 | })
90 | return file_proto_LanguageStringData_proto_rawDescData
91 | }
92 |
93 | var file_proto_LanguageStringData_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
94 | var file_proto_LanguageStringData_proto_goTypes = []interface{}{
95 | (*LanguageStringData)(nil), // 0: LanguageStringData
96 | }
97 | var file_proto_LanguageStringData_proto_depIdxs = []int32{
98 | 0, // [0:0] is the sub-list for method output_type
99 | 0, // [0:0] is the sub-list for method input_type
100 | 0, // [0:0] is the sub-list for extension type_name
101 | 0, // [0:0] is the sub-list for extension extendee
102 | 0, // [0:0] is the sub-list for field type_name
103 | }
104 |
105 | func init() { file_proto_LanguageStringData_proto_init() }
106 | func file_proto_LanguageStringData_proto_init() {
107 | if File_proto_LanguageStringData_proto != nil {
108 | return
109 | }
110 | if !protoimpl.UnsafeEnabled {
111 | file_proto_LanguageStringData_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
112 | switch v := v.(*LanguageStringData); i {
113 | case 0:
114 | return &v.state
115 | case 1:
116 | return &v.sizeCache
117 | case 2:
118 | return &v.unknownFields
119 | default:
120 | return nil
121 | }
122 | }
123 | }
124 | type x struct{}
125 | out := protoimpl.TypeBuilder{
126 | File: protoimpl.DescBuilder{
127 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
128 | RawDescriptor: file_proto_LanguageStringData_proto_rawDesc,
129 | NumEnums: 0,
130 | NumMessages: 1,
131 | NumExtensions: 0,
132 | NumServices: 0,
133 | },
134 | GoTypes: file_proto_LanguageStringData_proto_goTypes,
135 | DependencyIndexes: file_proto_LanguageStringData_proto_depIdxs,
136 | MessageInfos: file_proto_LanguageStringData_proto_msgTypes,
137 | }.Build()
138 | File_proto_LanguageStringData_proto = out.File
139 | file_proto_LanguageStringData_proto_rawDesc = nil
140 | file_proto_LanguageStringData_proto_goTypes = nil
141 | file_proto_LanguageStringData_proto_depIdxs = nil
142 | }
143 |
--------------------------------------------------------------------------------
/preload/init.go:
--------------------------------------------------------------------------------
1 | package preload
2 |
3 | import (
4 | "gf2gacha/logger"
5 | "gf2gacha/pb"
6 | "gf2gacha/util"
7 | "path/filepath"
8 | "strconv"
9 | "strings"
10 | )
11 |
12 | var (
13 | LangMap = make(map[int64]string)
14 | ItemMap = make(map[int64]*pb.ItemDataUnit)
15 | UpItemMap = make(map[int64]int64)
16 | ItemRankMap = make(map[int64]map[int64]int64)
17 |
18 | PoolTypeMap = make(map[int64]*pb.GachaTypeListDataUnit)
19 |
20 | DollNameMapping = make(map[string]int64)
21 | WeaponNameMapping = make(map[string]int64)
22 | )
23 |
24 | func init() {
25 | //info, err := util.GetLogInfo()
26 | //if err != nil {
27 | // logger.Logger.Panic(err)
28 | //}
29 |
30 | gameDataDir, err := util.GetGameDataDir()
31 | if err != nil {
32 | logger.Logger.Panic(err)
33 | }
34 | tableDir := filepath.Join(gameDataDir, "Table")
35 |
36 | var langCNData pb.LangPackageTableCnData
37 | err = util.GetTableData(tableDir, &langCNData)
38 | if err != nil {
39 | logger.Logger.Panic(err)
40 | }
41 | for i, unit := range langCNData.Units {
42 | LangMap[unit.Id] = langCNData.Units[i].Content
43 | }
44 |
45 | var itemData pb.ItemData
46 | err = util.GetTableData(tableDir, &itemData)
47 | if err != nil {
48 | logger.Logger.Panic(err)
49 | }
50 | for i, item := range itemData.Units {
51 | ItemMap[item.Id] = itemData.Units[i]
52 | switch item.Type {
53 | case 10:
54 | DollNameMapping[LangMap[item.Name.Id]] = item.Id
55 | case 20:
56 | WeaponNameMapping[LangMap[item.Name.Id]] = item.Id
57 | }
58 | }
59 |
60 | var gachaData pb.GachaData
61 | err = util.GetTableData(tableDir, &gachaData)
62 | if err != nil {
63 | logger.Logger.Panic(err)
64 | }
65 | for _, unit := range gachaData.Units {
66 | //等级为5在卡池中不一定算5星,比如神秘箱中的导体5/6,因此需要从卡池道具列表中提取等级
67 | ItemRankMap[unit.Id] = make(map[int64]int64)
68 | //提取人形,散爆这里居然有中文冒号你敢信
69 | rateDesGun := strings.ReplaceAll(unit.RateDesGun, ":", ":")
70 | rateDollStringGroupList := strings.Split(rateDesGun, ",")
71 | for _, rateDollStringGroup := range rateDollStringGroupList {
72 | var rank int64
73 | var dollStringGroup string
74 | switch {
75 | case strings.HasPrefix(rateDollStringGroup, "5:"):
76 | rank = 5
77 | dollStringGroup = strings.TrimPrefix(rateDollStringGroup, "5:")
78 | case strings.HasPrefix(rateDollStringGroup, "4:"):
79 | rank = 4
80 | dollStringGroup = strings.TrimPrefix(rateDollStringGroup, "4:")
81 | case strings.HasPrefix(rateDollStringGroup, "3:"):
82 | rank = 3
83 | dollStringGroup = strings.TrimPrefix(rateDollStringGroup, "3:")
84 | default:
85 | if rateDollStringGroup != "" {
86 | logger.Logger.Errorf("未知的人形掉落列表:%s", rateDollStringGroup)
87 | }
88 | continue
89 | }
90 | dollStringList := strings.Split(dollStringGroup, ":")
91 | for _, dollString := range dollStringList {
92 | dollId, err := strconv.ParseInt(dollString, 10, 64)
93 | if err != nil {
94 | logger.Logger.Panic(err)
95 | }
96 | ItemRankMap[unit.Id][dollId] = rank
97 | }
98 | }
99 | //提取武器,散爆这里居然有中文冒号你敢信
100 | rateDesWeapon := strings.ReplaceAll(unit.RateDesWeapon, ":", ":")
101 | rateWeaponStringGroupList := strings.Split(rateDesWeapon, ",")
102 | for _, rateWeaponStringGroup := range rateWeaponStringGroupList {
103 | var rank int64
104 | var weaponStringGroup string
105 | switch {
106 | case strings.HasPrefix(rateWeaponStringGroup, "5:"):
107 | rank = 5
108 | weaponStringGroup = strings.TrimPrefix(rateWeaponStringGroup, "5:")
109 | case strings.HasPrefix(rateWeaponStringGroup, "4:"):
110 | rank = 4
111 | weaponStringGroup = strings.TrimPrefix(rateWeaponStringGroup, "4:")
112 | case strings.HasPrefix(rateWeaponStringGroup, "3:"):
113 | rank = 3
114 | weaponStringGroup = strings.TrimPrefix(rateWeaponStringGroup, "3:")
115 | default:
116 | if rateWeaponStringGroup != "" {
117 | logger.Logger.Errorf("未知的道具等级列表:%s", rateWeaponStringGroup)
118 | }
119 | continue
120 | }
121 | weaponStringList := strings.Split(weaponStringGroup, ":")
122 | for _, weaponString := range weaponStringList {
123 | weaponId, err := strconv.ParseInt(weaponString, 10, 64)
124 | if err != nil {
125 | logger.Logger.Panic(err)
126 | }
127 | ItemRankMap[unit.Id][weaponId] = rank
128 | }
129 | }
130 |
131 | //限定池需要提取upItem来分辨是否歪
132 | if unit.Type == 3 || unit.Type == 4 {
133 | var upItemGroupString string
134 | if unit.GunUpItem != "" {
135 | upItemGroupString = unit.GunUpItem
136 | } else {
137 | upItemGroupString = unit.WeaponUpItem
138 | }
139 | upItemGroupList := strings.Split(upItemGroupString, ",")
140 | for _, upItemGroup := range upItemGroupList {
141 | if strings.HasPrefix(upItemGroup, "5:") {
142 | upItemString := strings.TrimPrefix(upItemGroup, "5:")
143 | upItemId, err := strconv.ParseInt(upItemString, 10, 64)
144 | if err != nil {
145 | logger.Logger.Panic(err)
146 | }
147 | UpItemMap[unit.Id] = upItemId
148 | break
149 | }
150 | }
151 | }
152 | }
153 |
154 | var gachaTypeListData pb.GachaTypeListData
155 | err = util.GetTableData(tableDir, &gachaTypeListData)
156 | for i, unit := range gachaTypeListData.Units {
157 | PoolTypeMap[unit.Id] = gachaTypeListData.Units[i]
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/request/CommunityCommon.go:
--------------------------------------------------------------------------------
1 | package request
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "fmt"
7 | "github.com/pkg/errors"
8 | "io"
9 | "net/http"
10 | "net/url"
11 | )
12 |
13 | type CommonResponse struct {
14 | Code int `json:"Code"`
15 | Message string `json:"Message"`
16 | Data json.RawMessage `json:"data"`
17 | }
18 |
19 | func (c CommonResponse) Error() string {
20 | return fmt.Sprintf("%s(Code:%d)", c.Message, c.Code)
21 | }
22 |
23 | func CommunityGet(apiUrl string, values url.Values, webToken string) (dataBytes json.RawMessage, err error) {
24 | apiUrl += "?" + values.Encode()
25 | request, err := http.NewRequest("GET", apiUrl, nil)
26 | if err != nil {
27 | return nil, errors.WithStack(err)
28 | }
29 |
30 | request.Header.Set("Content-Type", "application/json")
31 | request.Header.Set("Origin", "https://gf2-bbs.sunborngame.com")
32 | request.Header.Set("Referer", "https://gf2-bbs.sunborngame.com")
33 | request.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 Edg/125.0.0.0")
34 | if webToken != "" {
35 | request.Header.Set("Authorization", webToken)
36 | }
37 |
38 | resp, err := http.DefaultClient.Do(request)
39 | if err != nil {
40 | return nil, errors.WithStack(err)
41 | }
42 | defer resp.Body.Close()
43 |
44 | body, err := io.ReadAll(resp.Body)
45 | if err != nil {
46 | return nil, errors.WithStack(err)
47 | }
48 | var respBody CommonResponse
49 | err = json.Unmarshal(body, &respBody)
50 | if err != nil {
51 | return nil, errors.WithStack(err)
52 | }
53 |
54 | if respBody.Code != 0 {
55 | return nil, errors.WithStack(respBody)
56 | }
57 |
58 | return respBody.Data, nil
59 | }
60 |
61 | func CommunityPost(apiUrl string, params map[string]interface{}, webToken string) (dataBytes json.RawMessage, err error) {
62 | requestBodyBytes, err := json.Marshal(¶ms)
63 | if err != nil {
64 | return nil, errors.WithStack(err)
65 | }
66 | request, err := http.NewRequest("POST", apiUrl, bytes.NewReader(requestBodyBytes))
67 | if err != nil {
68 | return nil, errors.WithStack(err)
69 | }
70 |
71 | request.Header.Set("Content-Type", "application/json")
72 | request.Header.Set("Origin", "https://gf2-bbs.sunborngame.com")
73 | request.Header.Set("Referer", "https://gf2-bbs.sunborngame.com")
74 | request.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 Edg/125.0.0.0")
75 | if webToken != "" {
76 | request.Header.Set("Authorization", webToken)
77 | }
78 |
79 | resp, err := http.DefaultClient.Do(request)
80 | if err != nil {
81 | return nil, errors.WithStack(err)
82 | }
83 | defer resp.Body.Close()
84 |
85 | body, err := io.ReadAll(resp.Body)
86 | if err != nil {
87 | return nil, errors.WithStack(err)
88 | }
89 | var respBody CommonResponse
90 | err = json.Unmarshal(body, &respBody)
91 | if err != nil {
92 | return nil, errors.WithStack(err)
93 | }
94 |
95 | if respBody.Code != 0 {
96 | return nil, errors.WithStack(respBody)
97 | }
98 |
99 | return respBody.Data, nil
100 | }
101 |
--------------------------------------------------------------------------------
/request/CommunityExchange.go:
--------------------------------------------------------------------------------
1 | package request
2 |
3 | import (
4 | "github.com/pkg/errors"
5 | )
6 |
7 | func CommunityExchange(webToken string, exchangeId int64) error {
8 | apiUrl := `https://gf2-bbs-api.sunborngame.com/community/item/exchange`
9 |
10 | params := map[string]interface{}{
11 | "exchange_id": exchangeId,
12 | }
13 |
14 | _, err := CommunityPost(apiUrl, params, webToken)
15 | if err != nil {
16 | return errors.WithStack(err)
17 | }
18 |
19 | return nil
20 | }
21 |
--------------------------------------------------------------------------------
/request/CommunityExchangeList.go:
--------------------------------------------------------------------------------
1 | package request
2 |
3 | import (
4 | "encoding/json"
5 | "github.com/pkg/errors"
6 | )
7 |
8 | type CommunityExchangeListData struct {
9 | List []struct {
10 | ExchangeId int64 `json:"exchange_id"`
11 | ItemName string `json:"item_name"`
12 | ItemCount int64 `json:"item_count"`
13 | ItemPic string `json:"item_pic"`
14 | ItemContext string `json:"item_context"`
15 | UseScore int64 `json:"use_score"`
16 | ExchangeCount int64 `json:"exchange_count"`
17 | MaxExchangeCount int64 `json:"max_exchange_count"`
18 | Cycle string `json:"cycle"`
19 | } `json:"list"`
20 | Total int64 `json:"total"`
21 | }
22 |
23 | func CommunityExchangeList(webToken string) (CommunityExchangeListData, error) {
24 | apiUrl := `https://gf2-bbs-api.sunborngame.com/community/item/exchange_list`
25 |
26 | dataBytes, err := CommunityGet(apiUrl, nil, webToken)
27 | if err != nil {
28 | return CommunityExchangeListData{}, errors.WithStack(err)
29 | }
30 |
31 | var data CommunityExchangeListData
32 | err = json.Unmarshal(dataBytes, &data)
33 | if err != nil {
34 | return CommunityExchangeListData{}, errors.WithStack(err)
35 | }
36 |
37 | return data, nil
38 | }
39 |
--------------------------------------------------------------------------------
/request/CommunityLogin.go:
--------------------------------------------------------------------------------
1 | package request
2 |
3 | import (
4 | "encoding/json"
5 | "github.com/pkg/errors"
6 | )
7 |
8 | type CommunityLoginData struct {
9 | Account struct {
10 | Token string `json:"token"`
11 | Uid int64 `json:"uid"`
12 | PlatformId int64 `json:"platform_id"`
13 | ChannelId int64 `json:"channel_id"`
14 | } `json:"account"`
15 | }
16 |
17 | func CommunityLogin(gameToken string) (webToken string, err error) {
18 | apiUrl := `https://gf2-bbs-api.sunborngame.com/login/game_skip`
19 | params := map[string]interface{}{
20 | "game_token": gameToken,
21 | }
22 |
23 | dataBytes, err := CommunityPost(apiUrl, params, "")
24 | if err != nil {
25 | return "", errors.WithStack(err)
26 | }
27 |
28 | var data CommunityLoginData
29 | err = json.Unmarshal(dataBytes, &data)
30 | if err != nil {
31 | return "", errors.WithStack(err)
32 | }
33 |
34 | return data.Account.Token, nil
35 | }
36 |
--------------------------------------------------------------------------------
/request/CommunitySign.go:
--------------------------------------------------------------------------------
1 | package request
2 |
3 | import (
4 | "encoding/json"
5 | "github.com/pkg/errors"
6 | )
7 |
8 | type CommunitySignData struct {
9 | GetExp int `json:"get_exp"`
10 | GetItemCount int `json:"get_item_count"`
11 | GetItemName string `json:"get_item_name"`
12 | GetItemUrl string `json:"get_item_url"`
13 | GetScore int `json:"get_score"`
14 | }
15 |
16 | func CommunitySign(webToken string) (CommunitySignData, error) {
17 | apiUrl := `https://gf2-bbs-api.sunborngame.com/community/task/sign_in`
18 |
19 | dataBytes, err := CommunityPost(apiUrl, nil, webToken)
20 | if err != nil {
21 | return CommunitySignData{}, errors.WithStack(err)
22 | }
23 |
24 | var data CommunitySignData
25 | err = json.Unmarshal(dataBytes, &data)
26 | if err != nil {
27 | return CommunitySignData{}, errors.WithStack(err)
28 | }
29 |
30 | return data, nil
31 | }
32 |
--------------------------------------------------------------------------------
/request/CommunityTaskList.go:
--------------------------------------------------------------------------------
1 | package request
2 |
3 | import (
4 | "encoding/json"
5 | "github.com/pkg/errors"
6 | )
7 |
8 | type CommunityTaskListData struct {
9 | DailyTask []struct {
10 | TaskName string `json:"task_name"`
11 | TaskContext string `json:"task_context"`
12 | CompleteCount int64 `json:"complete_count"`
13 | MaxCompleteCount int64 `json:"max_complete_count"`
14 | } `json:"daily_task"`
15 | MoreTask []struct {
16 | TaskName string `json:"task_name"`
17 | TaskContext string `json:"task_context"`
18 | CompleteCount int64 `json:"complete_count"`
19 | MaxCompleteCount int64 `json:"max_complete_count"`
20 | } `json:"more_task"`
21 | }
22 |
23 | func CommunityTaskList(webToken string) (CommunityTaskListData, error) {
24 | apiUrl := `https://gf2-bbs-api.sunborngame.com/community/task/get_current_task_list`
25 |
26 | dataBytes, err := CommunityGet(apiUrl, nil, webToken)
27 | if err != nil {
28 | return CommunityTaskListData{}, errors.WithStack(err)
29 | }
30 |
31 | var data CommunityTaskListData
32 | err = json.Unmarshal(dataBytes, &data)
33 | if err != nil {
34 | return CommunityTaskListData{}, errors.WithStack(err)
35 | }
36 |
37 | return data, nil
38 | }
39 |
--------------------------------------------------------------------------------
/request/CommunityTopicLike.go:
--------------------------------------------------------------------------------
1 | package request
2 |
3 | import (
4 | "fmt"
5 | "github.com/pkg/errors"
6 | "net/url"
7 | "strconv"
8 | )
9 |
10 | func CommunityTopicLike(webToken string, topicId int64) error {
11 | apiUrl := fmt.Sprintf(`https://gf2-bbs-api.sunborngame.com/community/topic/like/%d`, topicId)
12 |
13 | values := url.Values{
14 | "id": []string{strconv.FormatInt(topicId, 10)},
15 | }
16 |
17 | _, err := CommunityGet(apiUrl, values, webToken)
18 | if err != nil {
19 | return errors.WithStack(err)
20 | }
21 |
22 | return nil
23 | }
24 |
--------------------------------------------------------------------------------
/request/CommunityTopicList.go:
--------------------------------------------------------------------------------
1 | package request
2 |
3 | import (
4 | "encoding/json"
5 | "github.com/pkg/errors"
6 | "net/url"
7 | "strconv"
8 | )
9 |
10 | type CommunityTopicListData struct {
11 | LastTid int64 `json:"last_tid"`
12 | List []struct {
13 | CategoryId int64 `json:"category_id"`
14 | CategoryName string `json:"category_name"`
15 | CommentNum interface{} `json:"comment_num"`
16 | Content string `json:"content"`
17 | CreateTime string `json:"create_time"`
18 | IsAdmin bool `json:"is_admin"`
19 | IsAuthor bool `json:"is_author"`
20 | IsFavor bool `json:"is_favor"`
21 | IsLike bool `json:"is_like"`
22 | LikeNum interface{} `json:"like_num"`
23 | PicList []string `json:"pic_list"`
24 | Title string `json:"title"`
25 | TopicId int64 `json:"topic_id"`
26 | UserAvatar string `json:"user_avatar"`
27 | UserId int64 `json:"user_id"`
28 | UserLevel int64 `json:"user_level"`
29 | UserNickName string `json:"user_nick_name"`
30 | } `json:"list"`
31 | NextPage bool `json:"next_page"`
32 | PubTime int64 `json:"pub_time"`
33 | Total int64 `json:"total"`
34 | }
35 |
36 | func CommunityTopicList(webToken string, userId int64) (CommunityTopicListData, error) {
37 | apiUrl := `https://gf2-bbs-api.sunborngame.com/community/topic/list`
38 |
39 | values := url.Values{
40 | "last_tid": []string{"0"},
41 | "pub_time": []string{"0"},
42 | "reply_time": []string{"0"},
43 | "hot_value": []string{"0"},
44 | }
45 | if userId > 0 {
46 | values.Set("sort_type", "1")
47 | values.Set("category_id", "100")
48 | values.Set("query_type", "4")
49 | values.Set("user_id", strconv.FormatInt(userId, 10))
50 | } else {
51 | values.Set("sort_type", "2")
52 | values.Set("category_id", "5")
53 | values.Set("query_type", "1")
54 | }
55 |
56 | dataBytes, err := CommunityGet(apiUrl, values, webToken)
57 | if err != nil {
58 | return CommunityTopicListData{}, errors.WithStack(err)
59 | }
60 |
61 | var data CommunityTopicListData
62 | err = json.Unmarshal(dataBytes, &data)
63 | if err != nil {
64 | return CommunityTopicListData{}, errors.WithStack(err)
65 | }
66 |
67 | return data, nil
68 | }
69 |
--------------------------------------------------------------------------------
/request/CommunityTopicShare.go:
--------------------------------------------------------------------------------
1 | package request
2 |
3 | import (
4 | "fmt"
5 | "github.com/pkg/errors"
6 | "net/url"
7 | "strconv"
8 | )
9 |
10 | func CommunityTopicShare(webToken string, topicId int64) error {
11 | apiUrl := fmt.Sprintf(`https://gf2-bbs-api.sunborngame.com/community/topic/share/%d`, topicId)
12 |
13 | values := url.Values{
14 | "id": []string{strconv.FormatInt(topicId, 10)},
15 | }
16 |
17 | _, err := CommunityGet(apiUrl, values, webToken)
18 | if err != nil {
19 | return errors.WithStack(err)
20 | }
21 |
22 | return nil
23 | }
24 |
--------------------------------------------------------------------------------
/request/CommunityTopicView.go:
--------------------------------------------------------------------------------
1 | package request
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "github.com/pkg/errors"
7 | "net/url"
8 | "strconv"
9 | )
10 |
11 | type CommunityTopicViewData struct {
12 | BadNum interface{} `json:"bad_num"`
13 | CategoryId int64 `json:"category_id"`
14 | CategoryName string `json:"category_name"`
15 | CommentNum interface{} `json:"comment_num"`
16 | Content string `json:"content"`
17 | CreateTime string `json:"create_time"`
18 | IpLocation string `json:"ip_location"`
19 | IsAdmin bool `json:"is_admin"`
20 | IsAuthor bool `json:"is_author"`
21 | IsBad bool `json:"is_bad"`
22 | IsFavor bool `json:"is_favor"`
23 | IsFollow bool `json:"is_follow"`
24 | IsLike bool `json:"is_like"`
25 | LikeNum interface{} `json:"like_num"`
26 | LikeUserAvatars []string `json:"like_user_avatars"`
27 | PicList []string `json:"pic_list"`
28 | Title string `json:"title"`
29 | TopicId int64 `json:"topic_id"`
30 | UpdateTime string `json:"update_time"`
31 | UserAvatar string `json:"user_avatar"`
32 | UserId int64 `json:"user_id"`
33 | UserLevel int64 `json:"user_level"`
34 | UserNickName string `json:"user_nick_name"`
35 | ViewNum interface{} `json:"view_num"`
36 | }
37 |
38 | func CommunityTopicView(webToken string, topicId int64) (CommunityTopicViewData, error) {
39 | apiUrl := fmt.Sprintf(`https://gf2-bbs-api.sunborngame.com/community/topic/%d`, topicId)
40 |
41 | values := url.Values{
42 | "id": []string{strconv.FormatInt(topicId, 10)},
43 | }
44 |
45 | dataBytes, err := CommunityGet(apiUrl, values, webToken)
46 | if err != nil {
47 | return CommunityTopicViewData{}, errors.WithStack(err)
48 | }
49 |
50 | var data CommunityTopicViewData
51 | err = json.Unmarshal(dataBytes, &data)
52 | if err != nil {
53 | return CommunityTopicViewData{}, errors.WithStack(err)
54 | }
55 |
56 | return data, nil
57 | }
58 |
--------------------------------------------------------------------------------
/request/CommunityUserInfo.go:
--------------------------------------------------------------------------------
1 | package request
2 |
3 | import (
4 | "encoding/json"
5 | "github.com/pkg/errors"
6 | )
7 |
8 | type CommunityUserInfoData struct {
9 | User struct {
10 | AuthLock int64 `json:"auth_lock"`
11 | AuthType int64 `json:"auth_type"`
12 | Avatar string `json:"avatar"`
13 | Exp int64 `json:"exp"`
14 | Fans interface{} `json:"fans"`
15 | Favors interface{} `json:"favors"`
16 | Follows interface{} `json:"follows"`
17 | GameCommanderLevel int64 `json:"game_commander_level"`
18 | GameNickName string `json:"game_nick_name"`
19 | GameUid int64 `json:"game_uid"`
20 | IpLocation string `json:"ip_location"`
21 | IsAdmin bool `json:"is_admin"`
22 | IsAuthor bool `json:"is_author"`
23 | IsFollow bool `json:"is_follow"`
24 | Level int64 `json:"level"`
25 | Likes interface{} `json:"likes"`
26 | NextLvExp int64 `json:"next_lv_exp"`
27 | NickName string `json:"nick_name"`
28 | Score int64 `json:"score"`
29 | Signature string `json:"signature"`
30 | Uid int64 `json:"uid"`
31 | } `json:"user"`
32 | }
33 |
34 | func CommunityUserInfo(webToken string) (CommunityUserInfoData, error) {
35 | apiUrl := `https://gf2-bbs-api.sunborngame.com/community/member/info`
36 | params := map[string]interface{}{
37 | "uid": 0,
38 | }
39 |
40 | dataBytes, err := CommunityPost(apiUrl, params, webToken)
41 | if err != nil {
42 | return CommunityUserInfoData{}, errors.WithStack(err)
43 | }
44 |
45 | var data CommunityUserInfoData
46 | err = json.Unmarshal(dataBytes, &data)
47 | if err != nil {
48 | return CommunityUserInfoData{}, errors.WithStack(err)
49 | }
50 |
51 | return data, nil
52 | }
53 |
--------------------------------------------------------------------------------
/sample.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MatchaCabin/gf2gacha/98957835ff3b0b90afa5af33d84bb44ba4e4f34d/sample.png
--------------------------------------------------------------------------------
/util/BackupDB.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import (
4 | "fmt"
5 | "github.com/pkg/errors"
6 | "os"
7 | "time"
8 | )
9 |
10 | func BackupDB() error {
11 | dbData, err := os.ReadFile("gf2gacha.db")
12 | if err != nil {
13 | return errors.WithStack(err)
14 | }
15 |
16 | backupName := fmt.Sprintf("gf2gacha_backup%s.db", time.Now().Format("20060102150405"))
17 | err = os.WriteFile(backupName, dbData, 0755)
18 | if err != nil {
19 | return errors.WithStack(err)
20 | }
21 | return nil
22 | }
23 |
--------------------------------------------------------------------------------
/util/Cert.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import (
4 | "crypto/ecdsa"
5 | "crypto/elliptic"
6 | "crypto/rand"
7 | "crypto/tls"
8 | "crypto/x509"
9 | "crypto/x509/pkix"
10 | "encoding/pem"
11 | "fmt"
12 | "github.com/pkg/errors"
13 | "golang.org/x/sys/windows"
14 | "math/big"
15 | "os"
16 | "syscall"
17 | "time"
18 | "unsafe"
19 | )
20 |
21 | func GenCA() error {
22 | privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
23 | if err != nil {
24 | return err
25 | }
26 | serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
27 | if err != nil {
28 | return err
29 | }
30 |
31 | template := x509.Certificate{
32 | SerialNumber: serialNumber,
33 | Subject: pkix.Name{
34 | CommonName: "GF2Gacha Custom CA",
35 | Organization: []string{"GF2Gacha Custom Org"},
36 | Country: []string{"CN"},
37 | },
38 | NotBefore: time.Now(),
39 | NotAfter: time.Now().AddDate(20, 0, 0),
40 | KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
41 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
42 | BasicConstraintsValid: true,
43 | IsCA: true,
44 | }
45 |
46 | derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey)
47 | if err != nil {
48 | return err
49 | }
50 | certOut, err := os.Create("ca.crt")
51 | if err != nil {
52 | return err
53 | }
54 | defer certOut.Close()
55 | err = pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
56 | if err != nil {
57 | return err
58 | }
59 |
60 | keyOut, err := os.Create("ca.key")
61 | if err != nil {
62 | return err
63 | }
64 | defer keyOut.Close()
65 | privBytes, err := x509.MarshalECPrivateKey(privateKey)
66 | if err != nil {
67 | return err
68 | }
69 | err = pem.Encode(keyOut, &pem.Block{Type: "EC PRIVATE KEY", Bytes: privBytes})
70 | if err != nil {
71 | return err
72 | }
73 |
74 | return nil
75 | }
76 |
77 | var (
78 | crypt32 = syscall.NewLazyDLL("crypt32.dll")
79 | procCertOpenStore = crypt32.NewProc("CertOpenStore")
80 | procCertCloseStore = crypt32.NewProc("CertCloseStore")
81 | procCertAddEncodedCertificateToStore = crypt32.NewProc("CertAddEncodedCertificateToStore")
82 | )
83 |
84 | func IsTrustedCA() bool {
85 | certPem, err := os.ReadFile("ca.crt")
86 | if err != nil {
87 | fmt.Println(err)
88 | return false
89 | }
90 | certDer, _ := pem.Decode(certPem)
91 | if certDer == nil {
92 | fmt.Println("pem解码失败")
93 | return false
94 | }
95 | cert, err := x509.ParseCertificate(certDer.Bytes)
96 | if err != nil {
97 | fmt.Println(err)
98 | return false
99 | }
100 |
101 | _, err = cert.Verify(x509.VerifyOptions{})
102 | if err != nil {
103 | fmt.Println(err)
104 | return false
105 | }
106 |
107 | return true
108 | }
109 |
110 | func openCertStore() (windows.Handle, error) {
111 | storeName, _ := syscall.UTF16PtrFromString("Root")
112 |
113 | r, _, err := procCertOpenStore.Call(uintptr(10), 0, 0, uintptr(0x00010000), uintptr(unsafe.Pointer(storeName)))
114 | if r == 0 {
115 | return 0, fmt.Errorf("调用CertOpenStore出错: %v", err)
116 | }
117 | return windows.Handle(r), nil
118 | }
119 |
120 | func closeCertStore(store windows.Handle) {
121 | if store != 0 {
122 | procCertCloseStore.Call(uintptr(store), 0)
123 | }
124 | }
125 |
126 | func InstallCA() error {
127 | certPem, err := os.ReadFile("ca.crt")
128 | if err != nil {
129 | return err
130 | }
131 | certDer, _ := pem.Decode(certPem)
132 | if certDer == nil {
133 | return errors.New("pem解码失败")
134 | }
135 |
136 | store, err := openCertStore()
137 | if err != nil {
138 | return err
139 | }
140 | defer closeCertStore(store)
141 |
142 | r, _, err := procCertAddEncodedCertificateToStore.Call(
143 | uintptr(store),
144 | uintptr(windows.X509_ASN_ENCODING),
145 | uintptr(unsafe.Pointer(&certDer.Bytes[0])),
146 | uintptr(len(certDer.Bytes)),
147 | uintptr(4),
148 | 0,
149 | )
150 |
151 | if r == 0 {
152 | return fmt.Errorf("调用CertAddEncodedCertificateToStore出错: %v", err)
153 | }
154 | return nil
155 | }
156 |
157 | func ParseCA() (*tls.Certificate, error) {
158 | caCert, err := os.ReadFile("ca.crt")
159 | if err != nil {
160 | return nil, err
161 | }
162 |
163 | caKey, err := os.ReadFile("ca.key")
164 | if err != nil {
165 | return nil, err
166 | }
167 |
168 | parsedCert, err := tls.X509KeyPair(caCert, caKey)
169 | if err != nil {
170 | return nil, err
171 | }
172 | if parsedCert.Leaf, err = x509.ParseCertificate(parsedCert.Certificate[0]); err != nil {
173 | return nil, err
174 | }
175 | return &parsedCert, nil
176 | }
177 |
--------------------------------------------------------------------------------
/util/GetGameDataDir.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import (
4 | "gf2gacha/config"
5 | "gf2gacha/logger"
6 | "github.com/pkg/errors"
7 | "os"
8 | "path/filepath"
9 | "regexp"
10 | )
11 |
12 | func GetGameDataDir() (string, error) {
13 | userHome, err := os.UserHomeDir()
14 | if err != nil {
15 | return "", errors.WithStack(err)
16 | }
17 |
18 | logPath := filepath.Join(userHome, "/AppData/LocalLow/SunBorn/少女前线2:追放/Player.log")
19 | logData, err := os.ReadFile(logPath)
20 | if err != nil {
21 | return "", errors.WithStack(err)
22 | }
23 |
24 | regexpGamePath, err := regexp.Compile(`\[Subsystems] Discovering subsystems at path (.+)/UnitySubsystems`)
25 | if err != nil {
26 | return "", errors.WithStack(err)
27 | }
28 | resultGamePath := regexpGamePath.FindSubmatch(logData)
29 | if len(resultGamePath) == 2 {
30 | gameDataDir := filepath.Join(string(resultGamePath[1]), "LocalCache/Data")
31 |
32 | //保存游戏路径,供日志里找不到时降级使用
33 | err = config.SetGameDataDir(gameDataDir)
34 | if err != nil {
35 | return "", err
36 | }
37 |
38 | return gameDataDir, nil
39 | }
40 |
41 | logger.Logger.Warn("未在日志中找到游戏路径,尝试从配置文件读取")
42 | //如果日志里没找到游戏路径,尝试降级读取配置文件
43 | if !config.IsSetGameDataDir() {
44 | return "", errors.New("日志和配置文件中均未找到游戏路径")
45 | }
46 |
47 | return config.GetGameDataDir(), nil
48 | }
49 |
--------------------------------------------------------------------------------
/util/GetLogInfo.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import (
4 | "gf2gacha/model"
5 | "github.com/pkg/errors"
6 | "os"
7 | "path/filepath"
8 | "regexp"
9 | )
10 |
11 | func GetLogInfo() (logInfo model.LogInfo, err error) {
12 | userHome, err := os.UserHomeDir()
13 | if err != nil {
14 | return model.LogInfo{}, errors.WithStack(err)
15 | }
16 |
17 | logPath := filepath.Join(userHome, "/AppData/LocalLow/SunBorn/少女前线2:追放/Player.log")
18 | logData, err := os.ReadFile(logPath)
19 | if err != nil {
20 | return model.LogInfo{}, errors.WithStack(err)
21 | }
22 |
23 | regexpGamePath, err := regexp.Compile(`\[Subsystems] Discovering subsystems at path (.+)/UnitySubsystems`)
24 | if err != nil {
25 | return model.LogInfo{}, errors.WithStack(err)
26 | }
27 | resultGamePath := regexpGamePath.FindSubmatch(logData)
28 | if len(resultGamePath) == 2 {
29 | logInfo.TablePath = filepath.Join(string(resultGamePath[1]), "LocalCache/Data/Table")
30 | } else {
31 | return model.LogInfo{}, errors.New("未在日志中找到游戏路径")
32 | }
33 |
34 | regexpUserInfo, err := regexp.Compile(`"access_token":"(.+?)".+"uid":(\d+)`)
35 | if err != nil {
36 | return model.LogInfo{}, errors.WithStack(err)
37 | }
38 | resultUserInfoList := regexpUserInfo.FindAllSubmatch(logData, -1)
39 | if len(resultUserInfoList) == 0 {
40 | return model.LogInfo{}, errors.New("未在日志中找到AccessToken或Uid,可能是最近一次游戏启动时未登录,官服请使用「捕获信息」按钮抓包")
41 | }
42 | resultUserInfo := resultUserInfoList[len(resultUserInfoList)-1]
43 | if len(resultUserInfo) == 3 {
44 | logInfo.AccessToken = string(resultUserInfo[1])
45 | logInfo.Uid = string(resultUserInfo[2])
46 | } else {
47 | return model.LogInfo{}, errors.New("未在日志中找到AccessToken或Uid,可能是最近一次游戏启动时未登录,官服请使用「捕获信息」按钮抓包")
48 | }
49 |
50 | regexpGachaUrl, err := regexp.Compile(`"gacha_record_url":"(.*?)"`)
51 | if err != nil {
52 | return model.LogInfo{}, errors.WithStack(err)
53 | }
54 | resultGachaUrlList := regexpGachaUrl.FindAllSubmatch(logData, -1)
55 | if len(resultGachaUrlList) == 0 {
56 | return model.LogInfo{}, errors.New("未在日志中找到抽卡链接,官服请使用「捕获信息」按钮抓包")
57 | }
58 | resultGachaUrl := resultGachaUrlList[len(resultGachaUrlList)-1]
59 | if len(resultGachaUrl) == 2 {
60 | logInfo.GachaUrl = string(resultGachaUrl[1])
61 | } else {
62 | return model.LogInfo{}, errors.New("未在日志中找到抽卡链接")
63 | }
64 |
65 | return logInfo, nil
66 | }
67 |
--------------------------------------------------------------------------------
/util/GetTableData.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import (
4 | "bytes"
5 | "encoding/binary"
6 | "github.com/pkg/errors"
7 | "google.golang.org/protobuf/proto"
8 | "os"
9 | "path/filepath"
10 | "reflect"
11 | )
12 |
13 | func GetTableData[T proto.Message](tablePath string, tableData T) error {
14 | tableType := reflect.TypeOf(tableData)
15 |
16 | fileName := tableType.Elem().Name() + ".bytes"
17 | fileData, err := os.ReadFile(filepath.Join(tablePath, fileName))
18 | if err != nil {
19 | return errors.WithStack(err)
20 | }
21 |
22 | var head uint32
23 | err = binary.Read(bytes.NewReader(fileData[:4]), binary.LittleEndian, &head)
24 | if err != nil {
25 | return errors.WithStack(err)
26 | }
27 |
28 | err = proto.Unmarshal(fileData[head+4:], tableData)
29 | if err != nil {
30 | return errors.WithStack(err)
31 | }
32 |
33 | return nil
34 | }
35 |
--------------------------------------------------------------------------------
/util/SysProxy.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import (
4 | "fmt"
5 | "golang.org/x/sys/windows/registry"
6 | )
7 |
8 | var (
9 | originProxyServer string
10 | originProxyEnable uint32
11 | )
12 |
13 | func EnableSysProxy(port int) error {
14 | key, err := registry.OpenKey(
15 | registry.CURRENT_USER,
16 | `Software\Microsoft\Windows\CurrentVersion\Internet Settings`,
17 | registry.ALL_ACCESS,
18 | )
19 | if err != nil {
20 | return err
21 | }
22 | defer key.Close()
23 |
24 | proxyServer, _, err := key.GetStringValue("ProxyServer")
25 | if err != nil {
26 | return err
27 | }
28 | originProxyServer = proxyServer
29 |
30 | if err := key.SetStringValue("ProxyServer", fmt.Sprintf("127.0.0.1:%d", port)); err != nil {
31 | return err
32 | }
33 |
34 | proxyEnable, _, err := key.GetIntegerValue("ProxyEnable")
35 | if err != nil {
36 | return err
37 | }
38 | originProxyEnable = uint32(proxyEnable)
39 |
40 | if err := key.SetDWordValue("ProxyEnable", 1); err != nil {
41 | return err
42 | }
43 |
44 | return nil
45 | }
46 |
47 | func DisableSysProxy() error {
48 | key, err := registry.OpenKey(
49 | registry.CURRENT_USER,
50 | `Software\Microsoft\Windows\CurrentVersion\Internet Settings`,
51 | registry.ALL_ACCESS,
52 | )
53 | if err != nil {
54 | return err
55 | }
56 | defer key.Close()
57 |
58 | if err := key.SetStringValue("ProxyServer", originProxyServer); err != nil {
59 | return err
60 | }
61 |
62 | if err := key.SetDWordValue("ProxyEnable", originProxyEnable); err != nil {
63 | return err
64 | }
65 |
66 | return nil
67 | }
68 |
--------------------------------------------------------------------------------
/util/Version.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | const version = ""
4 |
5 | func GetVersion() string {
6 | return version
7 | }
8 |
--------------------------------------------------------------------------------
/wails.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://wails.io/schemas/config.v2.json",
3 | "name": "gf2gacha",
4 | "outputfilename": "gf2gacha",
5 | "frontend:install": "npm install",
6 | "frontend:build": "npm run build",
7 | "frontend:dev:watcher": "npm run dev",
8 | "frontend:dev:serverUrl": "auto",
9 | "author": {
10 | "name": "Matcha",
11 | "email": "admin@mcc.wiki"
12 | },
13 | "info": {
14 | "companyName": "MccWiki",
15 | "productName": "gf2gacha",
16 | "productVersion": "",
17 | "copyright": "Copyright 2024 MccWiki",
18 | "comments": "A tool for storing and analyzing GFL2 gacha records"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------