├── .gitattributes
├── .github
├── feat_ddos2
│ └── Снимок экрана 2025-01-16 в 21.31.58.png
├── img
│ ├── feat_cloud.png
│ ├── feat_ddos1.png
│ ├── feat_ddos2.png
│ └── feat_json.png
└── workflows
│ └── go.yml
├── .gitignore
├── .idea
├── .gitignore
├── HalogenGhostCore.iml
├── discord.xml
└── modules.xml
├── README.md
├── _recover
├── Dockerfile
└── recover.sh
├── docker-compose.yaml
└── src
├── DOCS.md
├── Dockerfile
├── LICENSE
├── PLUGIN_DOCS.md
├── api
├── account.go
├── comment.go
├── communication.go
├── content.go
├── essential.go
├── level_levels.go
├── level_lists.go
├── level_packs.go
├── level_rates.go
├── profile.go
├── rewards.go
├── score.go
├── service.go
├── web_ballistics.go
├── web_cache.go
├── web_server.go
└── web_templates.go
├── build.sh
├── core
├── CAccount.go
├── CAccount_structs.go
├── CComment.go
├── CFriendship.go
├── CHalogen.go
├── CLevel.go
├── CLevelFilter.go
├── CLevelList.go
├── CLevelListFilter.go
├── CMessage.go
├── CMusic.go
├── CProtect.go
├── CQuests.go
├── CScores.go
├── ConfigBlob.go
├── DBManagement.go
├── Routines.go
├── actions.go
├── connectors
│ ├── GDConnector.go
│ ├── JSONConnector.go
│ ├── connector.go
│ └── output.go
├── cryptography.go
├── modCommandProcessor.go
├── modules
│ ├── PluginCore.go
│ ├── PluginCore_test.go
│ └── modDummy.go
├── sfxgen.go
└── utils.go
├── go.mod
├── go.sum
├── main.go
└── rpc
└── server.go
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.github/feat_ddos2/Снимок экрана 2025-01-16 в 21.31.58.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/m41denx/GDPSGhostCore/b585da9c5a29331749c580d28eabe17c80ee97cf/.github/feat_ddos2/Снимок экрана 2025-01-16 в 21.31.58.png
--------------------------------------------------------------------------------
/.github/img/feat_cloud.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/m41denx/GDPSGhostCore/b585da9c5a29331749c580d28eabe17c80ee97cf/.github/img/feat_cloud.png
--------------------------------------------------------------------------------
/.github/img/feat_ddos1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/m41denx/GDPSGhostCore/b585da9c5a29331749c580d28eabe17c80ee97cf/.github/img/feat_ddos1.png
--------------------------------------------------------------------------------
/.github/img/feat_ddos2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/m41denx/GDPSGhostCore/b585da9c5a29331749c580d28eabe17c80ee97cf/.github/img/feat_ddos2.png
--------------------------------------------------------------------------------
/.github/img/feat_json.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/m41denx/GDPSGhostCore/b585da9c5a29331749c580d28eabe17c80ee97cf/.github/img/feat_json.png
--------------------------------------------------------------------------------
/.github/workflows/go.yml:
--------------------------------------------------------------------------------
1 | name: Go
2 |
3 | on:
4 | push:
5 | branches: [ "master" ]
6 | pull_request:
7 | branches: [ "master" ]
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v3
14 |
15 | - name: Set up Go
16 | uses: actions/setup-go@v3
17 | with:
18 | go-version: 1.23
19 |
20 | - name: Log into registry
21 | run: docker login -u ${{ secrets.DOCKER_USERNAME }} --password ${{ secrets.DOCKER_PASSWORD }} cr.yandex
22 |
23 | - name: Build
24 | run: docker build -t cr.yandex/crpr24jcqm2dno6qlm3b/ghostcore src
25 |
26 | - name: Push
27 | run: docker push cr.yandex/crpr24jcqm2dno6qlm3b/ghostcore
28 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | /_minio/
3 | /_mysql/
4 | /_redis/
5 | .DS_Store
6 |
7 | HalogenGhostCore
8 | HalogenGhostCore.exe
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Editor-based HTTP Client requests
5 | /httpRequests/
6 | # Datasource local storage ignored files
7 | /dataSources/
8 | /dataSources.local.xml
9 |
--------------------------------------------------------------------------------
/.idea/HalogenGhostCore.iml:
--------------------------------------------------------------------------------
1 |
2 | ")[1], "")[0])
94 | acc.LvlsCompleted, _ = strconv.Atoi(strings.Split(strings.Split(strings.Split(saveData, "")[1], "")[0])
95 | acc.PushStatsAndExtra()
96 | //! Temp
97 | s3.DeleteFile("/savedata_old/" + vars["gdps"] + "/files/savedata/" + strconv.Itoa(acc.Uid) + ".hal")
98 | connector.Success("Backup successful")
99 | } else {
100 | connector.Error("-2", "Invalid credentials")
101 | }
102 | } else {
103 | connector.Error("-1", "Bad request")
104 | }
105 | }
106 |
107 | func AccountSync(resp http.ResponseWriter, req *http.Request, conf *core.GlobalConfig) {
108 | IPAddr := ipOf(req)
109 | vars := gorilla.Vars(req)
110 | logger := core.Logger{Output: os.Stderr}
111 | connector := connectors.NewConnector(req.URL.Query().Has("json"))
112 | defer func() { _, _ = io.WriteString(resp, connector.Output()) }()
113 | config, err := conf.LoadById(vars["gdps"])
114 | if logger.Should(err) != nil {
115 | serverError(connector)
116 | return
117 | }
118 | if core.CheckIPBan(IPAddr, config) {
119 | connector.Error("-1", "Banned")
120 | return
121 | }
122 | Post := ReadPost(req)
123 | if (Post.Get("userName") != "" && Post.Get("password") != "") || Post.Get("gjp2") != "" {
124 | uname := core.ClearGDRequest(Post.Get("userName"))
125 | pass := core.ClearGDRequest(Post.Get("password"))
126 | db := &core.MySQLConn{}
127 |
128 | if logger.Should(db.ConnectBlob(config)) != nil {
129 | return
130 | }
131 | acc := core.CAccount{DB: db}
132 | var res int
133 | if Post.Get("gameVersion") == "22" {
134 | res = core.ToInt(acc.PerformGJPAuth(Post, IPAddr))
135 | } else {
136 | res = acc.LogIn(uname, pass, IPAddr, 0)
137 | }
138 | if res > 0 {
139 | savepath := "/gdps_savedata/" + vars["gdps"] + "/" + strconv.Itoa(acc.Uid) + ".hsv"
140 | s3 := core.NewS3FS()
141 | if d, err := s3.GetFile(savepath); err == nil {
142 | taes := core.ThunderAES{}
143 | if logger.Should(taes.GenKey(config.ServerConfig.SrvKey)) != nil {
144 | serverError(connector)
145 | return
146 | }
147 | if logger.Should(taes.Init()) != nil {
148 | serverError(connector)
149 | return
150 | }
151 | data, err := taes.DecryptRaw(d)
152 | if err != nil {
153 | serverError(connector)
154 | core.ReportFail(fmt.Sprintf("[%s] NG savedata decrypt error for `%s`", vars["gdps"], uname))
155 | return
156 | }
157 | connector.Account_Sync(data)
158 | //! Temp transitional
159 | } else if d, err := s3.GetFile("/savedata_old/" + vars["gdps"] + "/files/savedata/" + strconv.Itoa(acc.Uid) + ".hal"); err == nil {
160 | taes := core.ThunderAES{}
161 | if logger.Should(taes.GenKey(pass)) != nil {
162 | serverError(connector)
163 | return
164 | }
165 | if logger.Should(taes.Init()) != nil {
166 | serverError(connector)
167 | return
168 | }
169 | data, err := taes.DecryptLegacy(string(d))
170 | if err != nil {
171 | serverError(connector)
172 | core.ReportFail(fmt.Sprintf("[%s] HAL savedata decrypt error for `%s`", vars["gdps"], uname))
173 | return
174 | }
175 | connector.Account_Sync(data)
176 | } else {
177 | connector.Error("-1", "No savedata found")
178 | }
179 | } else {
180 | connector.Error("-2", "Invalid credentials")
181 | }
182 | } else {
183 | connector.Error("-1", "Bad request")
184 | }
185 | }
186 |
187 | func AccountManagement(resp http.ResponseWriter, req *http.Request, conf *core.GlobalConfig) {
188 | vars := gorilla.Vars(req)
189 | http.Redirect(resp, req, "https://gofruit.space/gdps/"+vars["gdps"], http.StatusMovedPermanently)
190 | }
191 |
192 | func AccountLogin(resp http.ResponseWriter, req *http.Request, conf *core.GlobalConfig) {
193 | IPAddr := ipOf(req)
194 | vars := gorilla.Vars(req)
195 | logger := core.Logger{Output: os.Stderr}
196 | connector := connectors.NewConnector(req.URL.Query().Has("json"))
197 | defer func() { _, _ = io.WriteString(resp, connector.Output()) }()
198 | config, err := conf.LoadById(vars["gdps"])
199 | if logger.Should(err) != nil {
200 | connector.Error("-1", "Not Found")
201 | return
202 | }
203 | if core.CheckIPBan(IPAddr, config) {
204 | connector.Error("-1", "Banned")
205 | return
206 | }
207 | Post := ReadPost(req)
208 | if Post.Get("userName") != "" && Post.Get("password")+Post.Get("gjp2") != "" {
209 | gjp2 := core.ClearGDRequest(Post.Get("gjp2"))
210 | uname := core.ClearGDRequest(Post.Get("userName"))
211 | pass := core.ClearGDRequest(Post.Get("password"))
212 | db := &core.MySQLConn{}
213 |
214 | if logger.Should(db.ConnectBlob(config)) != nil {
215 | serverError(connector)
216 | return
217 | }
218 | acc := core.CAccount{DB: db}
219 | var uid int
220 | if len(gjp2) != 0 {
221 | uid = acc.LogIn22(uname, gjp2, IPAddr, 0)
222 | } else {
223 | uid = acc.LogIn(uname, pass, IPAddr, 0)
224 | }
225 |
226 | if uid < 0 {
227 | connector.Error("-1", "Invalid credentials")
228 | } else {
229 | connector.Account_Login(uid)
230 | core.RegisterAction(core.ACTION_USER_LOGIN, 0, uid, map[string]string{"uname": uname}, db)
231 | }
232 | } else {
233 | connector.Error("-1", "Bad request")
234 | }
235 | }
236 |
237 | func AccountRegister(resp http.ResponseWriter, req *http.Request, conf *core.GlobalConfig) {
238 | IPAddr := ipOf(req)
239 | vars := gorilla.Vars(req)
240 | if conf.MaintenanceMode {
241 | resp.WriteHeader(403)
242 | core.SendMessageDiscord(fmt.Sprintf("[%s] %s reached registration killswitch", vars["gdps"], IPAddr))
243 | return
244 | }
245 |
246 | //Ballistics
247 | if PrepareBallistics(req) {
248 | return
249 | }
250 |
251 | logger := core.Logger{Output: os.Stderr}
252 | connector := connectors.NewConnector(req.URL.Query().Has("json"))
253 | defer func() { _, _ = io.WriteString(resp, connector.Output()) }()
254 | config, err := conf.LoadById(vars["gdps"])
255 | if logger.Should(err) != nil {
256 | connector.Error("-1", "Not Found")
257 | return
258 | }
259 | if core.CheckIPBan(IPAddr, config) {
260 | connector.Error("-1", "Banned")
261 | return
262 | }
263 |
264 | Post := ReadPost(req)
265 | if Post.Get("userName") != "" && Post.Get("password") != "" && Post.Get("email") != "" {
266 | uname := core.ClearGDRequest(Post.Get("userName"))
267 | pass := core.ClearGDRequest(Post.Get("password"))
268 | email := core.ClearGDRequest(Post.Get("email"))
269 | db := &core.MySQLConn{}
270 |
271 | if logger.Should(db.ConnectBlob(config)) != nil {
272 | serverError(connector)
273 | return
274 | }
275 | acc := core.CAccount{DB: db}
276 | if core.OnRegister(db, conf, config) {
277 | uid := acc.Register(uname, pass, email, IPAddr, config.SecurityConfig.AutoActivate)
278 | if uid > 0 {
279 | core.RegisterAction(core.ACTION_USER_REGISTER, 0, uid, map[string]string{"uname": uname, "email": email}, db)
280 | connector.Success("Registered")
281 | } else {
282 | connector.Error(strconv.Itoa(uid), "Refer to https://github.com/gd-programming/gd.docs/blob/main/docs/topics/status_codes.md#registergjaccount")
283 | }
284 | } else {
285 | connector.Error("-1", "Player limits exceeded")
286 | }
287 | } else {
288 | connector.Error("-1", "Bad request")
289 | }
290 | }
291 |
--------------------------------------------------------------------------------
/src/api/comment.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "HalogenGhostCore/core"
5 | "HalogenGhostCore/core/connectors"
6 | "encoding/base64"
7 | gorilla "github.com/gorilla/mux"
8 | "io"
9 | "net/http"
10 | "os"
11 | )
12 |
13 | func AccountCommentDelete(resp http.ResponseWriter, req *http.Request, conf *core.GlobalConfig) {
14 | IPAddr := ipOf(req)
15 | vars := gorilla.Vars(req)
16 | logger := core.Logger{Output: os.Stderr}
17 | connector := connectors.NewConnector(req.URL.Query().Has("json"))
18 | defer func() { _, _ = io.WriteString(resp, connector.Output()) }()
19 | config, err := conf.LoadById(vars["gdps"])
20 | if logger.Should(err) != nil {
21 | connector.Error("-1", "Not Found")
22 | return
23 | }
24 | if core.CheckIPBan(IPAddr, config) {
25 | connector.Error("-1", "Banned")
26 | return
27 | }
28 | Post := ReadPost(req)
29 | if core.CheckGDAuth(Post) && Post.Get("commentID") != "" {
30 | db := &core.MySQLConn{}
31 | if logger.Should(db.ConnectBlob(config)) != nil {
32 | serverError(connector)
33 | return
34 | }
35 | xacc := core.CAccount{DB: db}
36 | if !xacc.PerformGJPAuth(Post, IPAddr) {
37 | connector.Error("-1", "Invalid Credentials")
38 | return
39 | }
40 | cc := core.CComment{DB: db}
41 | var id int
42 | core.TryInt(&id, Post.Get("commentID"))
43 | cc.DeleteAccComment(id, xacc.Uid)
44 | core.OnPost(db, conf, config)
45 | connector.Success("Comment deleted")
46 | } else {
47 | connector.Error("-1", "Bad Request")
48 | }
49 | }
50 |
51 | func AccountCommentGet(resp http.ResponseWriter, req *http.Request, conf *core.GlobalConfig) {
52 | IPAddr := ipOf(req)
53 | vars := gorilla.Vars(req)
54 | logger := core.Logger{Output: os.Stderr}
55 | connector := connectors.NewConnector(req.URL.Query().Has("json"))
56 | defer func() { _, _ = io.WriteString(resp, connector.Output()) }()
57 | config, err := conf.LoadById(vars["gdps"])
58 | if logger.Should(err) != nil {
59 | connector.Error("-1", "Not Found")
60 | return
61 | }
62 | if core.CheckIPBan(IPAddr, config) {
63 | connector.Error("-1", "Banned")
64 | return
65 | }
66 |
67 | Post := ReadPost(req)
68 | if Post.Get("accountID") != "" {
69 | page := 0
70 | core.TryInt(&page, Post.Get("page"))
71 | db := &core.MySQLConn{}
72 |
73 | if logger.Should(db.ConnectBlob(config)) != nil {
74 | serverError(connector)
75 | return
76 | }
77 | var uid int
78 | xuid := Post.Get("accountID")
79 | if len(Post["accountID"]) > 1 {
80 | xuid = Post["accountID"][1]
81 | }
82 | core.TryInt(&uid, xuid)
83 | cc := core.CComment{DB: db}
84 | comments := cc.GetAllAccComments(uid, page)
85 | connector.Comment_AccountGet(comments, cc.CountAccComments(uid), page)
86 | } else {
87 | connector.Error("-1", "Bad Request")
88 | }
89 | }
90 |
91 | func AccountCommentUpload(resp http.ResponseWriter, req *http.Request, conf *core.GlobalConfig) {
92 | IPAddr := ipOf(req)
93 | vars := gorilla.Vars(req)
94 | logger := core.Logger{Output: os.Stderr}
95 | connector := connectors.NewConnector(req.URL.Query().Has("json"))
96 | defer func() { _, _ = io.WriteString(resp, connector.Output()) }()
97 | config, err := conf.LoadById(vars["gdps"])
98 | if logger.Should(err) != nil {
99 | connector.Error("-1", "Not Found")
100 | return
101 | }
102 |
103 | if conf.MaintenanceMode {
104 | config.SecurityConfig.DisableProtection = false
105 | }
106 |
107 | if core.CheckIPBan(IPAddr, config) {
108 | connector.Error("-1", "Banned")
109 | return
110 | }
111 |
112 | Post := ReadPost(req)
113 | if core.CheckGDAuth(Post) && Post.Get("comment") != "" {
114 | db := &core.MySQLConn{}
115 |
116 | if logger.Should(db.ConnectBlob(config)) != nil {
117 | serverError(connector)
118 | return
119 | }
120 | xacc := core.CAccount{DB: db}
121 | if !xacc.PerformGJPAuth(Post, IPAddr) {
122 | connector.Error("-1", "Invalid Credentials")
123 | return
124 | }
125 | comment := core.ClearGDRequest(Post.Get("comment"))
126 | cc := core.CComment{DB: db, Uid: xacc.Uid, Comment: comment}
127 | protect := core.CProtect{DB: db, DisableProtection: config.SecurityConfig.DisableProtection}
128 | if !core.OnPost(db, conf, config) {
129 | connector.Error("-1", "Post limits exceeded")
130 | return
131 | }
132 | if protect.DetectPosts(xacc.Uid) && cc.PostAccComment() {
133 | connector.Success("Comment posted")
134 | return
135 | }
136 | serverError(connector)
137 | } else {
138 | connector.Error("-1", "Bad Request")
139 | }
140 | }
141 |
142 | func CommentDelete(resp http.ResponseWriter, req *http.Request, conf *core.GlobalConfig) {
143 | IPAddr := ipOf(req)
144 | vars := gorilla.Vars(req)
145 | logger := core.Logger{Output: os.Stderr}
146 | connector := connectors.NewConnector(req.URL.Query().Has("json"))
147 | defer func() { _, _ = io.WriteString(resp, connector.Output()) }()
148 | config, err := conf.LoadById(vars["gdps"])
149 | if logger.Should(err) != nil {
150 | connector.Error("-1", "Not Found")
151 | return
152 | }
153 | if core.CheckIPBan(IPAddr, config) {
154 | connector.Error("-1", "Banned")
155 | return
156 | }
157 |
158 | Post := ReadPost(req)
159 | if core.CheckGDAuth(Post) && Post.Get("commentID") != "" && Post.Get("levelID") != "" {
160 | db := &core.MySQLConn{}
161 |
162 | if logger.Should(db.ConnectBlob(config)) != nil {
163 | serverError(connector)
164 | return
165 | }
166 | xacc := core.CAccount{DB: db}
167 | if !xacc.PerformGJPAuth(Post, IPAddr) {
168 | connector.Error("-1", "Invalid Credentials")
169 | return
170 | }
171 | cc := core.CComment{DB: db}
172 | var id, lvl_id int
173 | core.TryInt(&id, Post.Get("commentID"))
174 | core.TryInt(&lvl_id, Post.Get("levelID"))
175 | if lvl_id > 0 {
176 | // levels
177 | cl := core.CLevel{DB: db, Id: lvl_id}
178 | if cl.IsOwnedBy(xacc.Uid) {
179 | cc.DeleteOwnerLevelComment(id, lvl_id)
180 | } else {
181 | cc.DeleteLevelComment(id, xacc.Uid)
182 | }
183 | } else {
184 | // RobTop is retarded
185 | cl := core.CLevelList{DB: db, ID: lvl_id}
186 | if cl.IsOwnedBy(xacc.Uid) {
187 | cc.DeleteOwnerLevelComment(id, lvl_id)
188 | } else {
189 | cc.DeleteLevelComment(id, xacc.Uid)
190 | }
191 | }
192 | core.OnComment(db, conf, config)
193 | connector.Success("Comment deleted")
194 | } else {
195 | connector.Error("-1", "Bad Request")
196 | }
197 | }
198 |
199 | func CommentGet(resp http.ResponseWriter, req *http.Request, conf *core.GlobalConfig) {
200 | IPAddr := ipOf(req)
201 | vars := gorilla.Vars(req)
202 | logger := core.Logger{Output: os.Stderr}
203 | connector := connectors.NewConnector(req.URL.Query().Has("json"))
204 | defer func() { _, _ = io.WriteString(resp, connector.Output()) }()
205 | config, err := conf.LoadById(vars["gdps"])
206 | if logger.Should(err) != nil {
207 | connector.Error("-1", "Not Found")
208 | return
209 | }
210 | if core.CheckIPBan(IPAddr, config) {
211 | connector.Error("-1", "Banned")
212 | return
213 | }
214 |
215 | Post := ReadPost(req)
216 | if Post.Get("levelID") != "" {
217 | page := 0
218 | core.TryInt(&page, Post.Get("page"))
219 | mode := false
220 | if Post.Get("mode") != "0" {
221 | mode = true
222 | }
223 | db := &core.MySQLConn{}
224 |
225 | if logger.Should(db.ConnectBlob(config)) != nil {
226 | serverError(connector)
227 | return
228 | }
229 | var lvlId int
230 | core.TryInt(&lvlId, Post.Get("levelID"))
231 | cc := core.CComment{DB: db}
232 | comments := cc.GetAllLevelComments(lvlId, page, mode)
233 | connector.Comment_LevelGet(comments, cc.CountLevelComments(lvlId), page)
234 | } else {
235 | connector.Error("-1", "Bad Request")
236 | }
237 | }
238 |
239 | func CommentGetHistory(resp http.ResponseWriter, req *http.Request, conf *core.GlobalConfig) {
240 | IPAddr := ipOf(req)
241 | vars := gorilla.Vars(req)
242 | logger := core.Logger{Output: os.Stderr}
243 | connector := connectors.NewConnector(req.URL.Query().Has("json"))
244 | defer func() { _, _ = io.WriteString(resp, connector.Output()) }()
245 | config, err := conf.LoadById(vars["gdps"])
246 | if logger.Should(err) != nil {
247 | connector.Error("-1", "Not Found")
248 | return
249 | }
250 | if core.CheckIPBan(IPAddr, config) {
251 | connector.Error("-1", "Banned")
252 | return
253 | }
254 |
255 | Post := ReadPost(req)
256 | if Post.Get("userID") != "" {
257 | page := 0
258 | core.TryInt(&page, Post.Get("page"))
259 | mode := false
260 | if Post.Get("mode") != "0" {
261 | mode = true
262 | }
263 | db := &core.MySQLConn{}
264 |
265 | if logger.Should(db.ConnectBlob(config)) != nil {
266 | serverError(connector)
267 | return
268 | }
269 | acc := core.CAccount{DB: db}
270 | core.TryInt(&acc.Uid, Post.Get("userID"))
271 | if !acc.Exists(acc.Uid) {
272 | connector.Error("-1", "Invalid Credentials")
273 | return
274 | }
275 | acc.LoadAuth(core.CAUTH_UID)
276 | acc.LoadStats()
277 | acc.LoadVessels()
278 | role := acc.GetRoleObj(false)
279 | cc := core.CComment{DB: db}
280 | comments := cc.GetAllCommentsHistory(acc.Uid, page, mode)
281 | connector.Comment_HistoryGet(comments, acc, role, cc.CountCommentHistory(acc.Uid), page)
282 | } else {
283 | connector.Error("-1", "Bad Request")
284 | }
285 | }
286 |
287 | func CommentUpload(resp http.ResponseWriter, req *http.Request, conf *core.GlobalConfig) {
288 | IPAddr := ipOf(req)
289 | vars := gorilla.Vars(req)
290 | logger := core.Logger{Output: os.Stderr}
291 | connector := connectors.NewConnector(req.URL.Query().Has("json"))
292 | defer func() { _, _ = io.WriteString(resp, connector.Output()) }()
293 | config, err := conf.LoadById(vars["gdps"])
294 | if logger.Should(err) != nil {
295 | connector.Error("-1", "Not Found")
296 | return
297 | }
298 | if core.CheckIPBan(IPAddr, config) {
299 | connector.Error("-1", "Banned")
300 | return
301 | }
302 |
303 | Post := ReadPost(req)
304 | if core.CheckGDAuth(Post) && Post.Get("comment") != "" && Post.Get("levelID") != "" {
305 | db := &core.MySQLConn{}
306 |
307 | if logger.Should(db.ConnectBlob(config)) != nil {
308 | serverError(connector)
309 | return
310 | }
311 | xacc := core.CAccount{DB: db}
312 | if !xacc.PerformGJPAuth(Post, IPAddr) {
313 | connector.Error("-1", "Invalid Credentials")
314 | return
315 | }
316 | comment := core.ClearGDRequest(Post.Get("comment"))
317 | percent := 0
318 | core.TryInt(&percent, Post.Get("percent"))
319 | percent %= 101
320 | cl := core.CLevel{DB: db}
321 | core.TryInt(&cl.Id, Post.Get("levelID"))
322 | if cl.Id < 0 {
323 | // CLevelList
324 | list := core.CLevelList{DB: db, ID: cl.Id * -1}
325 | if !list.Exists(list.ID) {
326 | connector.Error("-1", "Invalid LevelList ID")
327 | return
328 | }
329 | cc := core.CComment{DB: db, Uid: xacc.Uid, LvlId: cl.Id, Comment: comment, Percent: percent}
330 | protect := core.CProtect{DB: db, DisableProtection: config.SecurityConfig.DisableProtection}
331 | if !core.OnComment(db, conf, config) {
332 | connector.Error("-1", "Comment limit exceeded")
333 | return
334 | }
335 | if protect.DetectComments(xacc.Uid) && cc.PostLevelComment() {
336 | connector.Success("Comment posted")
337 | } else {
338 | serverError(connector)
339 | }
340 | return
341 | }
342 |
343 | // Next
344 | if !cl.Exists(cl.Id) {
345 | connector.Error("-1", "Invalid Level ID")
346 | return
347 | }
348 | acc := core.CAccount{DB: db, Uid: xacc.Uid}
349 | acc.LoadAuth(core.CAUTH_UID)
350 | role := acc.GetRoleObj(true)
351 | isOwned := cl.IsOwnedBy(xacc.Uid)
352 | if len(role.Privs) > 0 || isOwned {
353 | modCommentByte, err := base64.StdEncoding.DecodeString(comment)
354 | modComment := string(modCommentByte)
355 | if err == nil && modComment[0] == '!' {
356 | cl.LoadMain()
357 | cl.LoadParams()
358 | cmdRes := core.InvokeCommands(db, cl, acc, modComment, isOwned, role)
359 | switch cmdRes {
360 | case "err":
361 | connector.Error("-1", cmdRes)
362 | case "ok":
363 | connector.Success("Command executed")
364 | default:
365 | connector.Error("-1", cmdRes)
366 | }
367 | } else {
368 | cc := core.CComment{DB: db, Uid: xacc.Uid, LvlId: cl.Id, Comment: comment, Percent: percent}
369 | protect := core.CProtect{DB: db, DisableProtection: config.SecurityConfig.DisableProtection}
370 | if !core.OnComment(db, conf, config) {
371 | connector.Error("-1", "Comment limit exceeded")
372 | return
373 | }
374 | if protect.DetectComments(xacc.Uid) && cc.PostLevelComment() {
375 | connector.Success("Comment posted")
376 | } else {
377 | serverError(connector)
378 | }
379 | }
380 | } else {
381 | cc := core.CComment{DB: db, Uid: xacc.Uid, LvlId: cl.Id, Comment: comment, Percent: percent}
382 | protect := core.CProtect{DB: db, DisableProtection: config.SecurityConfig.DisableProtection}
383 | if !core.OnComment(db, conf, config) {
384 | connector.Error("-1", "Comment limit exceeded")
385 | return
386 | }
387 | if protect.DetectComments(xacc.Uid) && cc.PostLevelComment() {
388 | connector.Success("Comment posted")
389 | } else {
390 | serverError(connector)
391 | }
392 | }
393 | } else {
394 | connector.Error("-1", "Bad Request")
395 | }
396 | }
397 |
--------------------------------------------------------------------------------
/src/api/content.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "HalogenGhostCore/core"
5 | "fmt"
6 | gorilla "github.com/gorilla/mux"
7 | "io"
8 | "net/http"
9 | "os"
10 | "strconv"
11 | "time"
12 | )
13 |
14 | func GetContentURL(resp http.ResponseWriter, req *http.Request, conf *core.GlobalConfig) {
15 | vars := gorilla.Vars(req)
16 | io.WriteString(resp, "https://rugd.gofruit.space/"+vars["gdps"]+"/db/content")
17 | }
18 |
19 | func GetSFXLibraryVersion(resp http.ResponseWriter, req *http.Request, conf *core.GlobalConfig) {
20 | resp.Header().Set("Location", "https://cdn.fruitspace.one/gdps_sfx/sfxlibrary_version.txt")
21 | resp.WriteHeader(301)
22 | }
23 |
24 | func GetSFXLibrary(resp http.ResponseWriter, req *http.Request, conf *core.GlobalConfig) {
25 | resp.Header().Set("Location", "https://cdn.fruitspace.one/gdps_sfx/sfxlibrary.dat")
26 | }
27 |
28 | func GetSFXTrack(resp http.ResponseWriter, req *http.Request, conf *core.GlobalConfig) {
29 | vars := gorilla.Vars(req)
30 | resp.Header().Set("Location", "https://geometrydashfiles.b-cdn.net/sfx/s"+vars["sfxid"]+".ogg")
31 | resp.WriteHeader(301)
32 | resp.WriteHeader(301)
33 | }
34 |
35 | func RelaySFX(resp http.ResponseWriter, req *http.Request, conf *core.GlobalConfig) {
36 | vars := gorilla.Vars(req)
37 | resp.Header().Set("Location", "https://geometrydashfiles.b-cdn.net/sfx/"+vars["sfxid"])
38 | resp.WriteHeader(301)
39 | }
40 |
41 | func GetMusicLibraryVersion(resp http.ResponseWriter, req *http.Request, conf *core.GlobalConfig) {
42 | t := time.Now()
43 | v := fmt.Sprintf("%s%d", strconv.Itoa(t.Year())[2:], t.YearDay())
44 | io.WriteString(resp, v)
45 | }
46 |
47 | func GetMusicLibrary(resp http.ResponseWriter, req *http.Request, conf *core.GlobalConfig) {
48 | vars := gorilla.Vars(req)
49 | logger := core.Logger{Output: os.Stderr}
50 | config, err := conf.LoadById(vars["gdps"])
51 | if logger.Should(err) != nil {
52 | return
53 | }
54 | db := &core.MySQLConn{}
55 |
56 | if logger.Should(db.ConnectBlob(config)) != nil {
57 | return
58 | }
59 |
60 | url := fmt.Sprintf("https://cdn.fruitspace.one/gdps_sfx/%s_library.dat", vars["gdps"])
61 | mdata, err := http.Head(url)
62 | if err != nil {
63 | fmt.Println(err)
64 | resp.WriteHeader(500)
65 | return
66 | }
67 | lmod := mdata.Header.Get("last-modified")
68 | if mdata.StatusCode != 200 {
69 | lmod = "Mon, 02 Jan 2006 15:04:05 GMT"
70 | }
71 | date, err := time.Parse("Mon, 02 Jan 2006 15:04:05 MST", lmod)
72 | if err != nil {
73 | fmt.Println(err)
74 | resp.WriteHeader(500)
75 | return
76 | }
77 | if date.YearDay() != time.Now().YearDay() {
78 | core.GenerateMusicLibraryFile(db, core.NewS3FS(), vars["gdps"])
79 | }
80 | resp.Header().Set("Location", url)
81 | resp.WriteHeader(301)
82 | }
83 |
--------------------------------------------------------------------------------
/src/api/essential.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "HalogenGhostCore/core"
5 | "HalogenGhostCore/core/connectors"
6 | gorilla "github.com/gorilla/mux"
7 | "io"
8 | "net/http"
9 | "os"
10 | )
11 |
12 | func GetAccountUrl(resp http.ResponseWriter, req *http.Request, conf *core.GlobalConfig) {
13 | vars := gorilla.Vars(req)
14 | _, _ = io.WriteString(resp, "https://rugd.gofruit.space/"+vars["gdps"]+"/db")
15 | }
16 |
17 | func GetSongInfo(resp http.ResponseWriter, req *http.Request, conf *core.GlobalConfig) {
18 | IPAddr := ipOf(req)
19 | vars := gorilla.Vars(req)
20 | logger := core.Logger{Output: os.Stderr}
21 | connector := connectors.NewConnector(req.URL.Query().Has("json"))
22 | defer func() { _, _ = io.WriteString(resp, connector.Output()) }()
23 | config, err := conf.LoadById(vars["gdps"])
24 | if logger.Should(err) != nil {
25 | connector.Error("-1", "Not Found")
26 | return
27 | }
28 | if core.CheckIPBan(IPAddr, config) {
29 | connector.Error("-1", "Banned")
30 | return
31 | }
32 |
33 | Post := ReadPost(req)
34 | songid := Post.Get("songID")
35 | linkmode := false
36 | if songid == "" {
37 | songid = req.URL.Query().Get("id")
38 | linkmode = true
39 | }
40 | if songid != "" {
41 | db := &core.MySQLConn{}
42 |
43 | if logger.Should(db.ConnectBlob(config)) != nil {
44 | serverError(connector)
45 | return
46 | }
47 | mus := core.CMusic{DB: db, ConfBlob: config, Config: conf}
48 | var id int
49 | core.TryInt(&id, songid)
50 | if mus.GetSong(id) {
51 | if linkmode {
52 | resp.Header().Set("Location", mus.Url)
53 | resp.WriteHeader(301)
54 | } else {
55 | connector.Essential_GetMusic(mus)
56 | }
57 | } else {
58 | connector.Error("-1", "Music Not Found")
59 | }
60 | } else {
61 | connector.Error("-1", "Bad Request")
62 | }
63 | }
64 |
65 | func GetTopArtists(resp http.ResponseWriter, req *http.Request, conf *core.GlobalConfig) {
66 | IPAddr := ipOf(req)
67 | vars := gorilla.Vars(req)
68 | logger := core.Logger{Output: os.Stderr}
69 | connector := connectors.NewConnector(req.URL.Query().Has("json"))
70 | defer func() { _, _ = io.WriteString(resp, connector.Output()) }()
71 | config, err := conf.LoadById(vars["gdps"])
72 | if logger.Should(err) != nil {
73 | connector.Error("-1", "Not Found")
74 | return
75 | }
76 | if core.CheckIPBan(IPAddr, config) {
77 | connector.Error("-1", "Banned")
78 | return
79 | }
80 |
81 | Post := ReadPost(req)
82 | db := &core.MySQLConn{}
83 |
84 | if logger.Should(db.ConnectBlob(config)) != nil {
85 | serverError(connector)
86 | return
87 | }
88 | page := 0
89 | core.TryInt(&page, Post.Get("page"))
90 | if page < 0 {
91 | page = 0
92 | }
93 | if logger.Should(db.ConnectBlob(config)) != nil {
94 | return
95 | }
96 | mus := core.CMusic{DB: db, ConfBlob: config, Config: conf}
97 | artists := mus.GetTopArtists()
98 | connector.Essential_GetTopArtists(artists)
99 | }
100 |
101 | func LikeItem(resp http.ResponseWriter, req *http.Request, conf *core.GlobalConfig) {
102 | IPAddr := ipOf(req)
103 | vars := gorilla.Vars(req)
104 | logger := core.Logger{Output: os.Stderr}
105 | connector := connectors.NewConnector(req.URL.Query().Has("json"))
106 | defer func() { _, _ = io.WriteString(resp, connector.Output()) }()
107 | config, err := conf.LoadById(vars["gdps"])
108 | if logger.Should(err) != nil {
109 | connector.Error("-1", "Not Found")
110 | return
111 | }
112 | if core.CheckIPBan(IPAddr, config) {
113 | connector.Error("-1", "Banned")
114 | return
115 | }
116 |
117 | Post := ReadPost(req)
118 | if core.CheckGDAuth(Post) && Post.Get("itemID") != "" && Post.Get("type") != "" {
119 | db := &core.MySQLConn{}
120 |
121 | if logger.Should(db.ConnectBlob(config)) != nil {
122 | serverError(connector)
123 | return
124 | }
125 | xacc := core.CAccount{DB: db}
126 | if !xacc.PerformGJPAuth(Post, IPAddr) {
127 | connector.Error("-1", "Invalid Credentials")
128 | return
129 | }
130 | var itemId, cType int
131 | like := Post.Get("like") == "1"
132 | core.TryInt(&itemId, Post.Get("itemID"))
133 | core.TryInt(&cType, Post.Get("type"))
134 | switch cType {
135 | case 1:
136 | cl := core.CLevel{DB: db}
137 | if cl.Exists(itemId) {
138 | likeAction := core.CLEVEL_ACTION_DISLIKE
139 | if like {
140 | likeAction = core.CLEVEL_ACTION_LIKE
141 | }
142 | cl.LikeLevel(itemId, xacc.Uid, likeAction)
143 | connector.Success("Liked level")
144 | } else {
145 | connector.Error("-1", "Level Not Found")
146 | }
147 | case 2:
148 | comm := core.CComment{DB: db}
149 | if comm.ExistsLevelComment(itemId) {
150 | comm.LikeLevelComment(itemId, xacc.Uid, like)
151 | connector.Success("Liked comment")
152 | } else {
153 | connector.Error("-1", "Comment Not Found")
154 | }
155 | case 3:
156 | comm := core.CComment{DB: db}
157 | if comm.ExistsAccComment(itemId) {
158 | comm.LikeAccComment(itemId, xacc.Uid, like)
159 | connector.Success("Liked account comment")
160 | } else {
161 | connector.Error("-1", "Account Comment Not Found")
162 | }
163 | case 4:
164 | clist := core.CLevelList{DB: db, ID: itemId}
165 | if clist.Exists(itemId) {
166 | likeAction := core.CLEVEL_ACTION_DISLIKE
167 | if like {
168 | likeAction = core.CLEVEL_ACTION_LIKE
169 | }
170 | clist.LikeList(itemId, xacc.Uid, likeAction)
171 | connector.Success("Liked list")
172 | } else {
173 | connector.Error("-1", "List Not Found")
174 | }
175 | default:
176 | connector.Error("-1", "Invalid Type")
177 | }
178 | } else {
179 | connector.Error("-1", "Bad Request")
180 | }
181 | }
182 |
183 | func RequestMod(resp http.ResponseWriter, req *http.Request, conf *core.GlobalConfig) {
184 | IPAddr := ipOf(req)
185 | vars := gorilla.Vars(req)
186 | logger := core.Logger{Output: os.Stderr}
187 | connector := connectors.NewConnector(req.URL.Query().Has("json"))
188 | defer func() { _, _ = io.WriteString(resp, connector.Output()) }()
189 | config, err := conf.LoadById(vars["gdps"])
190 | if logger.Should(err) != nil {
191 | connector.Error("-1", "Not Found")
192 | return
193 | }
194 | if core.CheckIPBan(IPAddr, config) {
195 | connector.Error("-1", "Banned")
196 | return
197 | }
198 |
199 | Post := ReadPost(req)
200 | if core.CheckGDAuth(Post) {
201 | db := &core.MySQLConn{}
202 |
203 | if logger.Should(db.ConnectBlob(config)) != nil {
204 | serverError(connector)
205 | return
206 | }
207 | xacc := core.CAccount{DB: db}
208 | if !xacc.PerformGJPAuth(Post, IPAddr) {
209 | connector.Error("-1", "Invalid Credentials")
210 | return
211 | }
212 | role := xacc.GetRoleObj(true)
213 | if len(role.Privs) > 0 && role.Privs["aReqMod"] > 0 {
214 | connector.NumberedSuccess(role.ModLevel)
215 | } else {
216 | connector.Error("-1", "Insufficient Privileges")
217 | }
218 | } else {
219 | connector.Error("-1", "Bad Request")
220 | }
221 | }
222 |
--------------------------------------------------------------------------------
/src/api/level_lists.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "HalogenGhostCore/core"
5 | "HalogenGhostCore/core/connectors"
6 | gorilla "github.com/gorilla/mux"
7 | "io"
8 | "log"
9 | "net/http"
10 | "os"
11 | "regexp"
12 | "strings"
13 | )
14 |
15 | func LevelListDelete(resp http.ResponseWriter, req *http.Request, conf *core.GlobalConfig) {
16 | IPAddr := ipOf(req)
17 | vars := gorilla.Vars(req)
18 | logger := core.Logger{Output: os.Stderr}
19 | connector := connectors.NewConnector(req.URL.Query().Has("json"))
20 | defer func() { _, _ = io.WriteString(resp, connector.Output()) }()
21 | config, err := conf.LoadById(vars["gdps"])
22 | if logger.Should(err) != nil {
23 | connector.Error("-1", "Not Found")
24 | return
25 | }
26 | if core.CheckIPBan(IPAddr, config) {
27 | connector.Error("-1", "Banned")
28 | return
29 | }
30 |
31 | Post := ReadPost(req)
32 | if core.CheckGDAuth(Post) && Post.Get("listID") != "" {
33 | db := &core.MySQLConn{}
34 |
35 | if logger.Should(db.ConnectBlob(config)) != nil {
36 | serverError(connector)
37 | return
38 | }
39 | xacc := core.CAccount{DB: db}
40 | if !xacc.PerformGJPAuth(Post, IPAddr) {
41 | connector.Error("-1", "Invalid credentials")
42 | return
43 | }
44 | var list_id int
45 | core.TryInt(&list_id, Post.Get("listID"))
46 | cl := core.CLevelList{DB: db, ID: list_id}
47 | if !cl.IsOwnedBy(xacc.Uid) {
48 | connector.Error("-1", "List not found or not owned by user")
49 | return
50 | }
51 | cl.DeleteList()
52 | connector.Success("List deleted")
53 | } else {
54 | connector.Error("-1", "Bad Request")
55 | }
56 | }
57 |
58 | func LevelListUpload(resp http.ResponseWriter, req *http.Request, conf *core.GlobalConfig) {
59 | IPAddr := ipOf(req)
60 | vars := gorilla.Vars(req)
61 | logger := core.Logger{Output: os.Stderr}
62 | connector := connectors.NewConnector(req.URL.Query().Has("json"))
63 | defer func() { _, _ = io.WriteString(resp, connector.Output()) }()
64 | config, err := conf.LoadById(vars["gdps"])
65 | if logger.Should(err) != nil {
66 | connector.Error("-1", "Not Found")
67 | return
68 | }
69 |
70 | if conf.MaintenanceMode {
71 | config.SecurityConfig.DisableProtection = false
72 | }
73 |
74 | if core.CheckIPBan(IPAddr, config) {
75 | connector.Error("-1", "Banned")
76 | return
77 | }
78 |
79 | Post := ReadPost(req)
80 | if core.CheckGDAuth(Post) && Post.Get("listLevels") != "" {
81 | db := &core.MySQLConn{}
82 |
83 | if logger.Should(db.ConnectBlob(config)) != nil {
84 | serverError(connector)
85 | return
86 | }
87 | xacc := core.CAccount{DB: db}
88 | if !xacc.PerformGJPAuth(Post, IPAddr) {
89 | connector.Error("-1", "Invalid credentials")
90 | return
91 | }
92 | cl := core.CLevelList{DB: db}
93 |
94 | cl.UID = xacc.Uid
95 | core.TryInt(&cl.ID, Post.Get("listID"))
96 | cl.Name = core.ClearGDRequest(Post.Get("listName"))
97 | cl.Description = core.ClearGDRequest(Post.Get("listDesc"))
98 | cl.Levels = core.ClearGDRequest(Post.Get("listLevels"))
99 | core.TryInt(&cl.Difficulty, Post.Get("difficulty"))
100 | core.TryInt(&cl.Version, Post.Get("listVersion"))
101 | //core.TryInt(&cl., Post.Get("original")) //???
102 | core.TryInt(&cl.Unlisted, Post.Get("unlisted"))
103 |
104 | if cl.Name == "" {
105 | cl.Name = "Unnamed"
106 | }
107 | if cl.Version == 0 {
108 | cl.Version = 1
109 | }
110 |
111 | cl.Preload() // Because we have no tables
112 |
113 | if cl.IsOwnedBy(xacc.Uid) {
114 | res := cl.UpdateList()
115 | if res > 0 {
116 | connector.NumberedSuccess(res)
117 | } else {
118 | connector.Error("-1", "Failed to update list")
119 | }
120 | } else {
121 | if !cl.CheckParams() {
122 | connector.Error("-1", "Invalid parameters")
123 | return
124 | }
125 | res := cl.UploadList()
126 | if res > 0 {
127 | connector.NumberedSuccess(res)
128 | } else {
129 | connector.Error("-1", "Failed to upload list")
130 | }
131 | }
132 | } else {
133 | connector.Error("-1", "Bad Request")
134 | }
135 | }
136 |
137 | func LevelListSearch(resp http.ResponseWriter, req *http.Request, conf *core.GlobalConfig) {
138 | IPAddr := ipOf(req)
139 | vars := gorilla.Vars(req)
140 | logger := core.Logger{Output: os.Stderr}
141 | connector := connectors.NewConnector(req.URL.Query().Has("json"))
142 | defer func() { _, _ = io.WriteString(resp, connector.Output()) }()
143 | config, err := conf.LoadById(vars["gdps"])
144 | if logger.Should(err) != nil {
145 | connector.Error("-1", "Not Found")
146 | return
147 | }
148 | if core.CheckIPBan(IPAddr, config) {
149 | connector.Error("-1", "Banned")
150 | return
151 | }
152 |
153 | Post := ReadPost(req)
154 | log.Printf("%s: %s\n", vars["gdps"], Post)
155 |
156 | var mode, page int
157 | core.TryInt(&mode, Post.Get("type"))
158 | core.TryInt(&page, Post.Get("page"))
159 |
160 | Params := make(map[string]string)
161 | if sterm := Post.Get("str"); sterm != "" {
162 | Params["sterm"] = core.ClearGDRequest(Post.Get("str"))
163 | }
164 |
165 | //Difficulty selector
166 | if diff := Post.Get("diff"); diff != "" {
167 | preg, err := regexp.Compile("[^0-9,-]")
168 | if logger.Should(err) != nil {
169 | connector.Error("-1", "Invalid difficulty filter")
170 | return
171 | }
172 | diff = core.CleanDoubles(preg.ReplaceAllString(diff, ""), ",")
173 | if diff != "-" && diff != "," {
174 | // The real diff filter begins
175 | difflist := strings.Split(diff, ",")
176 | var diffl []string
177 | for _, sdiff := range difflist {
178 | if sdiff == "" || sdiff == "-" {
179 | continue
180 | }
181 | diffl = append(diffl, sdiff)
182 | }
183 | Params["diff"] = strings.Join(diffl, ",")
184 | }
185 | }
186 |
187 | //Other params
188 |
189 | var star int
190 |
191 | core.TryInt(&star, Post.Get("star"))
192 | if star != 0 {
193 | Params["star"] = "1"
194 | }
195 |
196 | db := &core.MySQLConn{}
197 |
198 | if logger.Should(db.ConnectBlob(config)) != nil {
199 | serverError(connector)
200 | return
201 | }
202 | filter := core.CLevelListFilter{DB: db}
203 | var lists []int
204 |
205 | switch Post.Get("type") {
206 | case "1":
207 | lists = filter.SearchLists(page, Params, core.CLEVELLISTFILTER_MOSTDOWNLOADED)
208 | case "3":
209 | lists = filter.SearchLists(page, Params, core.CLEVELLISTFILTER_TRENDING)
210 | case "4":
211 | lists = filter.SearchLists(page, Params, core.CLEVELLISTFILTER_LATEST)
212 | case "5":
213 | lists = filter.SearchUserLists(page, Params, false) //User lists (uid in sterm)
214 | case "7":
215 | lists = filter.SearchLists(page, Params, core.CLEVELLISTFILTER_MAGIC) // Robtop lobotomy
216 | case "11":
217 | lists = filter.SearchLists(page, Params, core.CLEVELLISTFILTER_AWARDED) //Awarded tab
218 | case "12":
219 | //Follow levels
220 | preg, err := regexp.Compile("[^0-9,-]")
221 | if logger.Should(err) != nil {
222 | return
223 | }
224 | Params["followList"] = preg.ReplaceAllString(core.ClearGDRequest(Post.Get("followed")), "")
225 | if Params["followList"] == "" {
226 | break
227 | }
228 | lists = filter.SearchUserLists(page, Params, true)
229 | case "13":
230 | //Friend levels
231 | xacc := core.CAccount{DB: db}
232 | if !(core.CheckGDAuth(Post) && xacc.PerformGJPAuth(Post, IPAddr)) {
233 | break
234 | }
235 | xacc.LoadSocial()
236 | if xacc.FriendsCount == 0 {
237 | break
238 | }
239 | fr := core.CFriendship{DB: db}
240 | friendships := core.Decompose(core.CleanDoubles(xacc.FriendshipIds, ","), ",")
241 | friends := []int{xacc.Uid}
242 | for _, frid := range friendships {
243 | id1, id2 := fr.GetFriendByFID(frid)
244 | fid := id1
245 | if id1 == xacc.Uid {
246 | fid = id2
247 | }
248 | friends = append(friends, fid)
249 | }
250 | Params["followList"] = strings.Join(core.ArrTranslate(friends), ",")
251 | lists = filter.SearchUserLists(page, Params, false)
252 | case "27":
253 | lists = filter.SearchLists(page, Params, core.CLEVELLISTFILTER_SENT)
254 |
255 | default:
256 | lists = filter.SearchLists(page, Params, core.CLEVELLISTFILTER_MOSTLIKED)
257 | }
258 |
259 | //Output, begins!
260 | if len(lists) == 0 {
261 | connector.Error("-2", "No results found")
262 | return
263 | }
264 |
265 | listCore := core.CLevelList{DB: db}
266 | llistsX := listCore.LoadBulkSearch(lists)
267 | connector.Level_SearchList(lists, llistsX, filter.Count, page)
268 | }
269 |
--------------------------------------------------------------------------------
/src/api/level_packs.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "HalogenGhostCore/core"
5 | "HalogenGhostCore/core/connectors"
6 | gorilla "github.com/gorilla/mux"
7 | "io"
8 | "net/http"
9 | "os"
10 | )
11 |
12 | func GetGauntlets(resp http.ResponseWriter, req *http.Request, conf *core.GlobalConfig) {
13 | IPAddr := ipOf(req)
14 | vars := gorilla.Vars(req)
15 | logger := core.Logger{Output: os.Stderr}
16 | connector := connectors.NewConnector(req.URL.Query().Has("json"))
17 | defer func() { _, _ = io.WriteString(resp, connector.Output()) }()
18 | config, err := conf.LoadById(vars["gdps"])
19 | if logger.Should(err) != nil {
20 | connector.Error("-1", "Not Found")
21 | return
22 | }
23 | if core.CheckIPBan(IPAddr, config) {
24 | connector.Error("-1", "Banned")
25 | return
26 | }
27 |
28 | //Post:=ReadPost(req)
29 | db := &core.MySQLConn{}
30 |
31 | if logger.Should(db.ConnectBlob(config)) != nil {
32 | serverError(connector)
33 | return
34 | }
35 | filter := core.CLevelFilter{DB: db}
36 | gaus, hash := filter.GetGauntlets()
37 | connector.Level_GetGauntlets(gaus, hash)
38 | }
39 |
40 | func GetMapPacks(resp http.ResponseWriter, req *http.Request, conf *core.GlobalConfig) {
41 | IPAddr := ipOf(req)
42 | vars := gorilla.Vars(req)
43 | logger := core.Logger{Output: os.Stderr}
44 | connector := connectors.NewConnector(req.URL.Query().Has("json"))
45 | defer func() { _, _ = io.WriteString(resp, connector.Output()) }()
46 | config, err := conf.LoadById(vars["gdps"])
47 | if logger.Should(err) != nil {
48 | connector.Error("-1", "Not Found")
49 | return
50 | }
51 | if core.CheckIPBan(IPAddr, config) {
52 | connector.Error("-1", "Banned")
53 | return
54 | }
55 |
56 | Post := ReadPost(req)
57 | db := &core.MySQLConn{}
58 |
59 | if logger.Should(db.ConnectBlob(config)) != nil {
60 | serverError(connector)
61 | return
62 | }
63 | filter := core.CLevelFilter{DB: db}
64 | var page int
65 | core.TryInt(&page, Post.Get("page"))
66 | packs, count := filter.GetMapPacks(page)
67 | connector.Level_GetMapPacks(packs, count, page)
68 | }
69 |
--------------------------------------------------------------------------------
/src/api/level_rates.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "HalogenGhostCore/core"
5 | "HalogenGhostCore/core/connectors"
6 | gorilla "github.com/gorilla/mux"
7 | "io"
8 | "net/http"
9 | "os"
10 | "strconv"
11 | )
12 |
13 | func RateDemon(resp http.ResponseWriter, req *http.Request, conf *core.GlobalConfig) {
14 | IPAddr := ipOf(req)
15 | vars := gorilla.Vars(req)
16 | logger := core.Logger{Output: os.Stderr}
17 | connector := connectors.NewConnector(req.URL.Query().Has("json"))
18 | defer func() { _, _ = io.WriteString(resp, connector.Output()) }()
19 | config, err := conf.LoadById(vars["gdps"])
20 | if logger.Should(err) != nil {
21 | connector.Error("-1", "Not Found")
22 | return
23 | }
24 | if core.CheckIPBan(IPAddr, config) {
25 | connector.Error("-1", "Banned")
26 | return
27 | }
28 |
29 | Post := ReadPost(req)
30 | if core.CheckGDAuth(Post) && Post.Get("levelID") != "" {
31 | db := &core.MySQLConn{}
32 |
33 | if logger.Should(db.ConnectBlob(config)) != nil {
34 | serverError(connector)
35 | return
36 | }
37 | xacc := core.CAccount{DB: db}
38 | if !xacc.PerformGJPAuth(Post, IPAddr) {
39 | connector.Error("-1", "Invalid Credentials")
40 | return
41 | }
42 | var lvl_id int
43 | core.TryInt(&lvl_id, Post.Get("levelID"))
44 | cl := core.CLevel{DB: db, Id: lvl_id}
45 | if !cl.Exists(cl.Id) {
46 | connector.Error("-1", "Level not found")
47 | return
48 | }
49 | role := xacc.GetRoleObj(true)
50 | var diff, mode int
51 | core.TryInt(&mode, Post.Get("mode"))
52 | core.TryInt(&diff, Post.Get("rating"))
53 | if len(role.Privs) > 0 && role.Privs["aRateDemon"] > 0 && mode != 0 {
54 | cl.RateDemon(diff % 6)
55 | if config.ServerConfig.EnableModules["discord"] {
56 | cl.LoadMain()
57 | cl.LoadParams()
58 | cl.LoadStats()
59 | builder := "[deleted]"
60 | acc := core.CAccount{DB: db, Uid: cl.Uid}
61 | if acc.Exists(acc.Uid) {
62 | acc.LoadAuth(core.CAUTH_UID)
63 | }
64 | if len(acc.Uname) > 0 {
65 | builder = acc.Uname
66 | }
67 | data := map[string]string{
68 | "id": strconv.Itoa(cl.Id),
69 | "name": cl.Name,
70 | "builder": builder,
71 | "diff": core.DiffToText(cl.StarsGot, cl.DemonDifficulty, cl.IsFeatured, cl.IsEpic),
72 | "stars": strconv.Itoa(cl.StarsGot),
73 | "likes": strconv.Itoa(cl.Likes),
74 | "downloads": strconv.Itoa(cl.Downloads),
75 | "len": strconv.Itoa(cl.Length),
76 | "rateuser": xacc.Uname,
77 | }
78 | core.SendAPIWebhook(vars["gdps"], "rate", data)
79 | }
80 | connector.NumberedSuccess(cl.Id)
81 | } else {
82 | connector.Error("-1", "Insufficient privileges")
83 | }
84 | } else {
85 | connector.Error("-1", "Bad Request")
86 | }
87 | }
88 |
89 | func RateStar(resp http.ResponseWriter, req *http.Request, conf *core.GlobalConfig) {
90 | IPAddr := ipOf(req)
91 | vars := gorilla.Vars(req)
92 | logger := core.Logger{Output: os.Stderr}
93 | connector := connectors.NewConnector(req.URL.Query().Has("json"))
94 | defer func() { _, _ = io.WriteString(resp, connector.Output()) }()
95 | config, err := conf.LoadById(vars["gdps"])
96 | if logger.Should(err) != nil {
97 | connector.Error("-1", "Not Found")
98 | return
99 | }
100 | if core.CheckIPBan(IPAddr, config) {
101 | connector.Error("-1", "Banned")
102 | return
103 | }
104 |
105 | Post := ReadPost(req)
106 | if core.CheckGDAuth(Post) && Post.Get("levelID") != "" {
107 | db := &core.MySQLConn{}
108 |
109 | if logger.Should(db.ConnectBlob(config)) != nil {
110 | serverError(connector)
111 | return
112 | }
113 | xacc := core.CAccount{DB: db}
114 | if !xacc.PerformGJPAuth(Post, IPAddr) {
115 | connector.Error("-1", "Invalid Credentials")
116 | return
117 | }
118 | var lvl_id int
119 | core.TryInt(&lvl_id, Post.Get("levelID"))
120 | cl := core.CLevel{DB: db, Id: lvl_id}
121 | if !cl.Exists(cl.Id) {
122 | connector.Error("-1", "Level not found")
123 | return
124 | }
125 | var diff int
126 | core.TryInt(&diff, Post.Get("stars"))
127 | cl.LoadMain()
128 | cl.DoSuggestDifficulty(diff % 11)
129 | connector.Success("Difficulty suggested")
130 | } else {
131 | connector.Error("-1", "Bad Request")
132 | }
133 | }
134 |
135 | func SuggestStars(resp http.ResponseWriter, req *http.Request, conf *core.GlobalConfig) {
136 | IPAddr := ipOf(req)
137 | vars := gorilla.Vars(req)
138 | logger := core.Logger{Output: os.Stderr}
139 | connector := connectors.NewConnector(req.URL.Query().Has("json"))
140 | defer func() { _, _ = io.WriteString(resp, connector.Output()) }()
141 | config, err := conf.LoadById(vars["gdps"])
142 | if logger.Should(err) != nil {
143 | connector.Error("-1", "Not Found")
144 | return
145 | }
146 | if core.CheckIPBan(IPAddr, config) {
147 | connector.Error("-1", "Banned")
148 | return
149 | }
150 |
151 | Post := ReadPost(req)
152 | if core.CheckGDAuth(Post) && Post.Get("levelID") != "" {
153 | db := &core.MySQLConn{}
154 |
155 | if logger.Should(db.ConnectBlob(config)) != nil {
156 | serverError(connector)
157 | return
158 | }
159 | xacc := core.CAccount{DB: db}
160 | if !xacc.PerformGJPAuth(Post, IPAddr) {
161 | connector.Error("-1", "Invalid Credentials")
162 | return
163 | }
164 | var lvl_id int
165 | core.TryInt(&lvl_id, Post.Get("levelID"))
166 | cl := core.CLevel{DB: db, Id: lvl_id}
167 | if !cl.Exists(cl.Id) {
168 | connector.Error("-1", "Level not found")
169 | return
170 | }
171 | cl.LoadMain()
172 | role := xacc.GetRoleObj(true)
173 | var diff, isFeature int
174 | core.TryInt(&isFeature, Post.Get("feature"))
175 | core.TryInt(&diff, Post.Get("stars"))
176 | if len(role.Privs) > 0 {
177 | if role.Privs["aRateStars"] > 0 {
178 | diff = diff % 11
179 | if v, _ := role.Privs["aRateNoDemon"]; v > 0 && diff == 10 {
180 | connector.Error("-1", "Insufficient privileges")
181 | return
182 | }
183 | cl.RateLevel(diff)
184 | cl.FeatureLevel(isFeature % 5)
185 | switch isFeature {
186 | case 2:
187 | cl.EpicLevel(true)
188 | case 3:
189 | cl.LegendaryLevel(true)
190 | case 4:
191 | cl.MythicLevel(true)
192 | default:
193 | cl.EpicLevel(false)
194 | }
195 | if config.ServerConfig.EnableModules["discord"] {
196 | cl.LoadMain()
197 | cl.LoadParams()
198 | cl.LoadStats()
199 | builder := "[deleted]"
200 | acc := core.CAccount{DB: db, Uid: cl.Uid}
201 | if acc.Exists(acc.Uid) {
202 | acc.LoadAuth(core.CAUTH_UID)
203 | }
204 | if len(acc.Uname) > 0 {
205 | builder = acc.Uname
206 | }
207 | data := map[string]string{
208 | "id": strconv.Itoa(cl.Id),
209 | "name": cl.Name,
210 | "builder": builder,
211 | "diff": core.DiffToText(cl.StarsGot, 3, isFeature, cl.IsEpic),
212 | "stars": strconv.Itoa(diff),
213 | "likes": strconv.Itoa(cl.Likes),
214 | "downloads": strconv.Itoa(cl.Downloads),
215 | "len": strconv.Itoa(cl.Length),
216 | "rateuser": xacc.Uname,
217 | }
218 | core.SendAPIWebhook(vars["gdps"], "rate", data)
219 | }
220 | core.RegisterAction(core.ACTION_LEVEL_RATE, xacc.Uid, cl.Id, map[string]string{
221 | "uname": xacc.Uname, "type": "StarRate:" + strconv.Itoa(diff%11),
222 | }, db)
223 | if isFeature != 0 {
224 | core.RegisterAction(core.ACTION_LEVEL_RATE, xacc.Uid, cl.Id, map[string]string{
225 | "uname": xacc.Uname, "type": "Feature",
226 | }, db)
227 | }
228 | } else if role.Privs["aRateReq"] > 0 {
229 | cl.SendReq(xacc.Uid, diff%11, isFeature)
230 | } else {
231 | connector.Error("-1", "Insufficient privileges")
232 | return
233 | }
234 | connector.Success("Rated successfully")
235 | } else {
236 | connector.Error("-1", "Insufficient privileges")
237 | }
238 | } else {
239 | connector.Error("-1", "Bad Request")
240 | }
241 | }
242 |
--------------------------------------------------------------------------------
/src/api/profile.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "HalogenGhostCore/core"
5 | "HalogenGhostCore/core/connectors"
6 | gorilla "github.com/gorilla/mux"
7 | "io"
8 | "net/http"
9 | "os"
10 | "slices"
11 | "strconv"
12 | "strings"
13 | )
14 |
15 | func GetUserInfo(resp http.ResponseWriter, req *http.Request, conf *core.GlobalConfig) {
16 | IPAddr := ipOf(req)
17 | vars := gorilla.Vars(req)
18 | logger := core.Logger{Output: os.Stderr}
19 | connector := connectors.NewConnector(req.URL.Query().Has("json"))
20 | defer func() { _, _ = io.WriteString(resp, connector.Output()) }()
21 | config, err := conf.LoadById(vars["gdps"])
22 | if logger.Should(err) != nil {
23 | connector.Error("-1", "Not Found")
24 | return
25 | }
26 | if core.CheckIPBan(IPAddr, config) {
27 | connector.Error("-1", "Banned")
28 | return
29 | }
30 |
31 | Post := ReadPost(req)
32 | if Post.Get("targetAccountID") != "" {
33 | db := &core.MySQLConn{}
34 |
35 | if logger.Should(db.ConnectBlob(config)) != nil {
36 | serverError(connector)
37 | return
38 | }
39 | acc := core.CAccount{DB: db}
40 | var uidSelf int
41 | core.TryInt(&acc.Uid, Post.Get("targetAccountID"))
42 | if core.CheckGDAuth(Post) {
43 | xacc := core.CAccount{DB: db}
44 | if !xacc.PerformGJPAuth(Post, IPAddr) {
45 | uidSelf = 0
46 | } else {
47 | uidSelf = xacc.Uid
48 | }
49 | }
50 | if !acc.Exists(acc.Uid) {
51 | connector.Error("-1", "User not Found")
52 | return
53 | }
54 | acc.LoadAll()
55 | blacklist := strings.Split(acc.Blacklist, ",")
56 | if uidSelf > 0 && slices.Contains(blacklist, strconv.Itoa(uidSelf)) {
57 | connector.Error("-1", "User has blacklisted you")
58 | return
59 | }
60 |
61 | connector.Profile_GetUserProfile(acc, uidSelf)
62 | } else {
63 | connector.Error("-1", "Bad Request")
64 | }
65 | }
66 |
67 | func GetUserList(resp http.ResponseWriter, req *http.Request, conf *core.GlobalConfig) {
68 | IPAddr := ipOf(req)
69 | vars := gorilla.Vars(req)
70 | logger := core.Logger{Output: os.Stderr}
71 | connector := connectors.NewConnector(req.URL.Query().Has("json"))
72 | defer func() { _, _ = io.WriteString(resp, connector.Output()) }()
73 | config, err := conf.LoadById(vars["gdps"])
74 | if logger.Should(err) != nil {
75 | connector.Error("-1", "Not Found")
76 | return
77 | }
78 | if core.CheckIPBan(IPAddr, config) {
79 | connector.Error("-1", "Banned")
80 | return
81 | }
82 |
83 | Post := ReadPost(req)
84 | if core.CheckGDAuth(Post) {
85 | db := &core.MySQLConn{}
86 |
87 | if logger.Should(db.ConnectBlob(config)) != nil {
88 | serverError(connector)
89 | return
90 | }
91 | var cType int
92 | core.TryInt(&cType, Post.Get("type"))
93 | xacc := core.CAccount{DB: db}
94 | if !xacc.PerformGJPAuth(Post, IPAddr) {
95 | connector.Error("-1", "Invalid Credentials")
96 | return
97 | }
98 | xacc.LoadSocial()
99 | var usersToDump []core.CAccount
100 | switch cType {
101 | case 0:
102 | if xacc.FriendsCount == 0 {
103 | connector.Error("-2", "No Friends")
104 | return
105 | } else {
106 | flist := strings.Split(xacc.FriendshipIds, ",")
107 | for _, fid := range flist {
108 | var Xfid int
109 | core.TryInt(&Xfid, fid)
110 | cf := core.CFriendship{DB: db}
111 | uid1, uid2 := cf.GetFriendByFID(Xfid)
112 | acc := core.CAccount{DB: db, Uid: uid1}
113 | if uid1 == xacc.Uid {
114 | acc.Uid = uid2
115 | }
116 | acc.LoadAuth(core.CAUTH_UID)
117 | acc.LoadVessels()
118 | acc.LoadStats()
119 | usersToDump = append(usersToDump, acc)
120 | }
121 | }
122 | case 1:
123 | blacklist := strings.Split(xacc.Blacklist, ",")
124 | if len(xacc.Blacklist) == 0 || len(blacklist) == 0 {
125 | connector.Error("-2", "No blacklisted users")
126 | return
127 | } else {
128 | for _, buid := range blacklist {
129 | acc := core.CAccount{DB: db}
130 | core.TryInt(&acc.Uid, buid)
131 | if acc.Uid == 0 {
132 | continue
133 | }
134 | acc.LoadAuth(core.CAUTH_UID)
135 | acc.LoadVessels()
136 | acc.LoadStats()
137 | usersToDump = append(usersToDump, acc)
138 | }
139 | }
140 | default:
141 | connector.Error("-1", "Bad Request")
142 | return
143 | }
144 |
145 | connector.Profile_ListUserProfiles(usersToDump)
146 | } else {
147 | connector.Error("-1", "Bad Request")
148 | }
149 | }
150 |
151 | func GetUsers(resp http.ResponseWriter, req *http.Request, conf *core.GlobalConfig) {
152 | IPAddr := ipOf(req)
153 | vars := gorilla.Vars(req)
154 | logger := core.Logger{Output: os.Stderr}
155 | connector := connectors.NewConnector(req.URL.Query().Has("json"))
156 | defer func() { _, _ = io.WriteString(resp, connector.Output()) }()
157 | config, err := conf.LoadById(vars["gdps"])
158 | if logger.Should(err) != nil {
159 | connector.Error("-1", "Not Found")
160 | return
161 | }
162 | if core.CheckIPBan(IPAddr, config) {
163 | connector.Error("-1", "Banned")
164 | return
165 | }
166 |
167 | Post := ReadPost(req)
168 | if Post.Get("str") != "" {
169 | db := &core.MySQLConn{}
170 |
171 | if logger.Should(db.ConnectBlob(config)) != nil {
172 | serverError(connector)
173 | return
174 | }
175 | xacc := core.CAccount{DB: db}
176 | uids := xacc.SearchUsers(core.ClearGDRequest(Post.Get("str")))
177 | if len(uids) == 0 {
178 | connector.Error("-1", "No users found")
179 | } else {
180 | var accs []core.CAccount
181 | for _, uid := range uids {
182 | accs = append(accs, core.CAccount{DB: db, Uid: uid})
183 | }
184 | connector.Profile_GetSearchableUsers(accs, len(uids), 0)
185 | }
186 | } else {
187 | connector.Error("-1", "Bad Request")
188 | }
189 | }
190 |
191 | func UpdateAccountSettings(resp http.ResponseWriter, req *http.Request, conf *core.GlobalConfig) {
192 | IPAddr := ipOf(req)
193 | vars := gorilla.Vars(req)
194 | logger := core.Logger{Output: os.Stderr}
195 | connector := connectors.NewConnector(req.URL.Query().Has("json"))
196 | defer func() { _, _ = io.WriteString(resp, connector.Output()) }()
197 | config, err := conf.LoadById(vars["gdps"])
198 | if logger.Should(err) != nil {
199 | connector.Error("-1", "Not Found")
200 | return
201 | }
202 | if core.CheckIPBan(IPAddr, config) {
203 | connector.Error("-1", "Banned")
204 | return
205 | }
206 |
207 | Post := ReadPost(req)
208 | if core.CheckGDAuth(Post) {
209 | db := &core.MySQLConn{}
210 |
211 | if logger.Should(db.ConnectBlob(config)) != nil {
212 | serverError(connector)
213 | return
214 | }
215 | xacc := core.CAccount{DB: db}
216 | if !xacc.PerformGJPAuth(Post, IPAddr) {
217 | connector.Error("-1", "Invalid Credentials")
218 | return
219 | }
220 |
221 | core.TryInt(&xacc.MS, Post.Get("mS"))
222 | core.TryInt(&xacc.FrS, Post.Get("frS"))
223 | core.TryInt(&xacc.CS, Post.Get("cS"))
224 | xacc.Youtube = core.ClearGDRequest(Post.Get("yt"))
225 | xacc.Twitter = core.ClearGDRequest(Post.Get("twitter"))
226 | xacc.Twitch = core.ClearGDRequest(Post.Get("twitch"))
227 | xacc.PushSettings()
228 | connector.Success("Account updated")
229 | } else {
230 | connector.Error("-1", "Bad Request")
231 | }
232 | }
233 |
--------------------------------------------------------------------------------
/src/api/rewards.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "HalogenGhostCore/core"
5 | "HalogenGhostCore/core/connectors"
6 | "encoding/base64"
7 | gorilla "github.com/gorilla/mux"
8 | "io"
9 | "net/http"
10 | "os"
11 | "time"
12 | )
13 |
14 | func GetChallenges(resp http.ResponseWriter, req *http.Request, conf *core.GlobalConfig) {
15 | IPAddr := ipOf(req)
16 | vars := gorilla.Vars(req)
17 | logger := core.Logger{Output: os.Stderr}
18 | connector := connectors.NewConnector(req.URL.Query().Has("json"))
19 | defer func() { _, _ = io.WriteString(resp, connector.Output()) }()
20 | config, err := conf.LoadById(vars["gdps"])
21 | if logger.Should(err) != nil {
22 | connector.Error("-1", "Not Found")
23 | return
24 | }
25 | if core.CheckIPBan(IPAddr, config) {
26 | connector.Error("-1", "Banned")
27 | return
28 | }
29 |
30 | Post := ReadPost(req)
31 | if Post.Get("chk") != "" && Post.Get("udid") != "" {
32 | db := &core.MySQLConn{}
33 |
34 | if logger.Should(db.ConnectBlob(config)) != nil {
35 | serverError(connector)
36 | return
37 | }
38 | cq := core.CQuests{DB: db}
39 | if cq.Exists(core.QUEST_TYPE_CHALLENGE) {
40 | chalk, _ := base64.StdEncoding.DecodeString(Post.Get("chk")[5:])
41 | chk := core.DoXOR(string(chalk), "19847")
42 | var uid int
43 | core.TryInt(&uid, Post.Get("accountID"))
44 | connector.Rewards_ChallengesOutput(cq, uid, chk, Post.Get("udid"))
45 | } else {
46 | connector.Error("-2", "Challenge not found")
47 | }
48 | } else {
49 | connector.Error("-1", "Bad Request")
50 | }
51 | }
52 |
53 | func GetRewards(resp http.ResponseWriter, req *http.Request, conf *core.GlobalConfig) {
54 | IPAddr := ipOf(req)
55 | vars := gorilla.Vars(req)
56 | logger := core.Logger{Output: os.Stderr}
57 | connector := connectors.NewConnector(req.URL.Query().Has("json"))
58 | defer func() { _, _ = io.WriteString(resp, connector.Output()) }()
59 | config, err := conf.LoadById(vars["gdps"])
60 | if logger.Should(err) != nil {
61 | connector.Error("-1", "Not Found")
62 | return
63 | }
64 | if core.CheckIPBan(IPAddr, config) {
65 | connector.Error("-1", "Banned")
66 | return
67 | }
68 |
69 | Post := ReadPost(req)
70 | if core.CheckGDAuth(Post) && len(Post.Get("chk")) > 5 && Post.Get("udid") != "" {
71 | db := &core.MySQLConn{}
72 |
73 | if logger.Should(db.ConnectBlob(config)) != nil {
74 | serverError(connector)
75 | return
76 | }
77 | xacc := core.CAccount{DB: db}
78 | if !xacc.PerformGJPAuth(Post, IPAddr) {
79 | connector.Error("-1", "Invalid Credentials")
80 | return
81 | }
82 | var chestType int
83 | core.TryInt(&chestType, Post.Get("rewardType"))
84 | chestType %= 3 //Strip to 2 options
85 | xacc.LoadChests()
86 | chalk, _ := base64.StdEncoding.DecodeString(Post.Get("chk")[5:])
87 | chk := core.DoXOR(string(chalk), "59182")
88 |
89 | chestSmallLeft := core.MaxInt(0, config.ChestConfig.ChestSmallWait-100+xacc.ChestSmallTime-int(time.Now().Unix())) //!+10800
90 | chestBigLeft := core.MaxInt(0, config.ChestConfig.ChestBigWait-100+xacc.ChestBigTime-int(time.Now().Unix())) //!+10800
91 | switch chestType {
92 | case 1:
93 | if chestSmallLeft == 0 {
94 | xacc.ChestSmallCount++
95 | xacc.ChestSmallTime = int(time.Now().Unix())
96 | xacc.PushChests()
97 | chestSmallLeft = config.ChestConfig.ChestSmallWait
98 | } else {
99 | connector.Error("-1", "Small chest is not ready yet")
100 | return
101 | }
102 | case 2:
103 | if chestBigLeft == 0 {
104 | xacc.ChestBigCount++
105 | xacc.ChestBigTime = int(time.Now().Unix())
106 | xacc.PushChests()
107 | chestBigLeft = config.ChestConfig.ChestBigWait
108 | } else {
109 | connector.Error("-1", "Big chest is not ready yet")
110 | return
111 | }
112 | }
113 | connector.Rewards_ChestOutput(xacc, config, Post.Get("udid"), chk, chestSmallLeft, chestBigLeft, chestType)
114 | } else {
115 | connector.Error("-1", "Bad Request")
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/src/api/score.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "HalogenGhostCore/core"
5 | "HalogenGhostCore/core/connectors"
6 | gorilla "github.com/gorilla/mux"
7 | "io"
8 | "math"
9 | "net/http"
10 | "os"
11 | "strconv"
12 | "strings"
13 | )
14 |
15 | func GetCreators(resp http.ResponseWriter, req *http.Request, conf *core.GlobalConfig) {
16 | IPAddr := ipOf(req)
17 | vars := gorilla.Vars(req)
18 | logger := core.Logger{Output: os.Stderr}
19 | connector := connectors.NewConnector(req.URL.Query().Has("json"))
20 | defer func() { _, _ = io.WriteString(resp, connector.Output()) }()
21 | config, err := conf.LoadById(vars["gdps"])
22 | if logger.Should(err) != nil {
23 | connector.Error("-1", "Not Found")
24 | return
25 | }
26 | if core.CheckIPBan(IPAddr, config) {
27 | connector.Error("-1", "Banned")
28 | return
29 | }
30 |
31 | //Post:=ReadPost(req)
32 | db := &core.MySQLConn{}
33 |
34 | if logger.Should(db.ConnectBlob(config)) != nil {
35 | serverError(connector)
36 | return
37 | }
38 | acc := core.CAccount{DB: db}
39 | users := acc.GetLeaderboard(core.CLEADERBOARD_BY_CPOINTS, []string{}, 0, config.ServerConfig.TopSize)
40 | if len(users) == 0 {
41 | connector.Error("-2", "No users found")
42 | } else {
43 | xacc := core.CAccount{DB: db}
44 | connector.Score_GetLeaderboard(users, xacc)
45 | }
46 | }
47 |
48 | func GetLevelScores(resp http.ResponseWriter, req *http.Request, conf *core.GlobalConfig) {
49 | IPAddr := ipOf(req)
50 | vars := gorilla.Vars(req)
51 | logger := core.Logger{Output: os.Stderr}
52 | connector := connectors.NewConnector(req.URL.Query().Has("json"))
53 | defer func() { _, _ = io.WriteString(resp, connector.Output()) }()
54 | config, err := conf.LoadById(vars["gdps"])
55 | if logger.Should(err) != nil {
56 | connector.Error("-1", "Not Found")
57 | return
58 | }
59 | if core.CheckIPBan(IPAddr, config) {
60 | connector.Error("-1", "Banned")
61 | return
62 | }
63 |
64 | Post := ReadPost(req)
65 | if core.CheckGDAuth(Post) && Post.Get("levelID") != "" {
66 | db := &core.MySQLConn{}
67 |
68 | if logger.Should(db.ConnectBlob(config)) != nil {
69 | serverError(connector)
70 | return
71 | }
72 | xacc := core.CAccount{DB: db}
73 | if !xacc.PerformGJPAuth(Post, IPAddr) {
74 | connector.Error("-1", "Invalid credentials")
75 | return
76 | }
77 |
78 | cs := core.CScores{DB: db}
79 |
80 | var percent, attempts, coins, lvlId, mode int
81 | core.TryInt(&lvlId, Post.Get("levelID"))
82 | core.TryInt(&mode, Post.Get("mode"))
83 | core.TryInt(&percent, Post.Get("percent"))
84 | core.TryInt(&attempts, Post.Get("s1"))
85 | core.TryInt(&coins, Post.Get("s9"))
86 | percent = int(math.Abs(float64(percent))) % 101
87 | attempts = int(math.Abs(float64(attempts)))
88 | if percent > 0 && attempts > 0 {
89 | // Upload score
90 | if attempts < 8355 {
91 | attempts = 1
92 | } else {
93 | attempts -= 8354
94 | }
95 | if coins < 5820 {
96 | coins = 0
97 | } else {
98 | coins = (coins - 5819) % 4
99 | }
100 | cs.Uid = xacc.Uid
101 | cs.LvlId = lvlId
102 | cs.Percent = percent
103 | cs.Attempts = attempts
104 | cs.Coins = coins
105 | if cs.ScoreExistsByUid(xacc.Uid, lvlId) {
106 | cs.UpdateLevelScore()
107 | } else {
108 | cs.UploadLevelScore()
109 | }
110 | }
111 | //Retrieve all scores
112 | scores := cs.GetScoresForLevelId(lvlId, mode%4+400, xacc)
113 | if len(scores) == 0 {
114 | connector.Error("-2", "No scores found")
115 | return
116 | }
117 | connector.Score_GetScores(scores, "default")
118 | } else {
119 | connector.Error("-1", "Bad Request")
120 | }
121 | }
122 |
123 | func GetLevelPlatScores(resp http.ResponseWriter, req *http.Request, conf *core.GlobalConfig) {
124 | IPAddr := ipOf(req)
125 | vars := gorilla.Vars(req)
126 | logger := core.Logger{Output: os.Stderr}
127 | connector := connectors.NewConnector(req.URL.Query().Has("json"))
128 | defer func() { _, _ = io.WriteString(resp, connector.Output()) }()
129 | config, err := conf.LoadById(vars["gdps"])
130 | if logger.Should(err) != nil {
131 | connector.Error("-1", "Not Found")
132 | return
133 | }
134 | if core.CheckIPBan(IPAddr, config) {
135 | connector.Error("-1", "Banned")
136 | return
137 | }
138 |
139 | Post := ReadPost(req)
140 | if core.CheckGDAuth(Post) && Post.Get("levelID") != "" {
141 | db := &core.MySQLConn{}
142 |
143 | if logger.Should(db.ConnectBlob(config)) != nil {
144 | serverError(connector)
145 | return
146 | }
147 | xacc := core.CAccount{DB: db}
148 | if !xacc.PerformGJPAuth(Post, IPAddr) {
149 | connector.Error("-1", "Invalid credentials")
150 | return
151 | }
152 |
153 | cs := core.CScores{DB: db}
154 |
155 | var percent, time, points, attempts, coins, lvlId, xtype, mode int
156 | core.TryInt(&lvlId, Post.Get("levelID"))
157 | core.TryInt(&xtype, Post.Get("type"))
158 | core.TryInt(&mode, Post.Get("mode"))
159 | core.TryInt(&percent, Post.Get("percent"))
160 | core.TryInt(&points, Post.Get("points"))
161 | core.TryInt(&time, Post.Get("time"))
162 | core.TryInt(&attempts, Post.Get("s1"))
163 | core.TryInt(&coins, Post.Get("s9"))
164 |
165 | // COINS = POINTS
166 | // ATTEMPTS = TIME
167 | //1: Username
168 | //2: playerID
169 | //3: время прохождения в миллисекундах или поинты
170 | //6: ранг
171 | //9: иконка
172 | //10: цвет1
173 | //11: цвет2
174 | //14: тип иконки
175 | //15: special
176 | //16: accountID
177 | //42: как давно
178 | percent = int(math.Abs(float64(percent))) % 101
179 | attempts = int(math.Abs(float64(attempts)))
180 | if percent > 0 && attempts > 0 {
181 | // Upload score
182 | cs.Attempts = time
183 | cs.Coins = points
184 | cs.Uid = xacc.Uid
185 | cs.LvlId = lvlId
186 | cs.Percent = percent
187 | if cs.ScoreExistsByUid(xacc.Uid, lvlId) {
188 | cs.UpdateLevelScore()
189 | } else {
190 | cs.UploadLevelScore()
191 | }
192 | }
193 | //Retrieve all scores
194 | scores := cs.GetScoresForPlatformerLevelId(lvlId, xtype%4+500, mode == 1, xacc)
195 | if len(scores) == 0 {
196 | connector.Error("-2", "No scores found")
197 | return
198 | }
199 | modes := "attempts"
200 | if mode == 1 {
201 | modes = "coins"
202 | }
203 | connector.Score_GetScores(scores, modes)
204 | } else {
205 | connector.Error("-1", "Bad Request")
206 | }
207 | }
208 |
209 | func GetScores(resp http.ResponseWriter, req *http.Request, conf *core.GlobalConfig) {
210 | IPAddr := ipOf(req)
211 | vars := gorilla.Vars(req)
212 | logger := core.Logger{Output: os.Stderr}
213 | connector := connectors.NewConnector(req.URL.Query().Has("json"))
214 | defer func() { _, _ = io.WriteString(resp, connector.Output()) }()
215 | config, err := conf.LoadById(vars["gdps"])
216 | if logger.Should(err) != nil {
217 | connector.Error("-1", "Not Found")
218 | return
219 | }
220 | if core.CheckIPBan(IPAddr, config) {
221 | connector.Error("-1", "Banned")
222 | return
223 | }
224 |
225 | Post := ReadPost(req)
226 | xType := Post.Get("type")
227 | if xType == "" {
228 | xType = "top"
229 | }
230 | db := &core.MySQLConn{}
231 |
232 | if logger.Should(db.ConnectBlob(config)) != nil {
233 | serverError(connector)
234 | return
235 | }
236 | acc := core.CAccount{DB: db}
237 | var users []int
238 | switch xType {
239 | case "relative":
240 | if !acc.PerformGJPAuth(Post, IPAddr) {
241 | connector.Error("-1", "Invalid credentials")
242 | return
243 | }
244 | acc.LoadStats()
245 | users = acc.GetLeaderboard(core.CLEADERBOARD_GLOBAL, []string{}, acc.Stars, config.ServerConfig.TopSize)
246 | case "friends":
247 | if !acc.PerformGJPAuth(Post, IPAddr) {
248 | connector.Error("-1", "Invalid credentials")
249 | return
250 | }
251 | acc.LoadSocial()
252 | if acc.FriendsCount == 0 {
253 | users = []int{}
254 | break
255 | }
256 | cf := core.CFriendship{DB: db}
257 | frs := strings.Split(acc.FriendshipIds, ",")
258 | var friends []string
259 | for _, fr := range frs {
260 | id, err := strconv.Atoi(fr)
261 | if err != nil {
262 | continue
263 | }
264 | uid1, uid2 := cf.GetFriendByFID(id)
265 | if uid1 == 0 {
266 | continue
267 | }
268 | xuid := uid1
269 | if acc.Uid == uid1 {
270 | xuid = uid2
271 | }
272 | friends = append(friends, strconv.Itoa(xuid))
273 | }
274 | friends = append(friends, strconv.Itoa(acc.Uid))
275 | users = acc.GetLeaderboard(core.CLEADERBOARD_FRIENDS, friends, 0, config.ServerConfig.TopSize)
276 | case "creators":
277 | users = acc.GetLeaderboard(core.CLEADERBOARD_BY_CPOINTS, []string{}, 0, config.ServerConfig.TopSize)
278 | default:
279 | users = acc.GetLeaderboard(core.CLEADERBOARD_BY_STARS, []string{}, 0, config.ServerConfig.TopSize)
280 | }
281 | if len(users) == 0 {
282 | connector.Error("-2", "No users found")
283 | } else {
284 | connector.Score_GetLeaderboard(users, acc)
285 | }
286 | }
287 |
288 | func UpdateUserScore(resp http.ResponseWriter, req *http.Request, conf *core.GlobalConfig) {
289 | IPAddr := ipOf(req)
290 | vars := gorilla.Vars(req)
291 | logger := core.Logger{Output: os.Stderr}
292 | connector := connectors.NewConnector(req.URL.Query().Has("json"))
293 | defer func() { _, _ = io.WriteString(resp, connector.Output()) }()
294 | config, err := conf.LoadById(vars["gdps"])
295 | if logger.Should(err) != nil {
296 | connector.Error("-1", "Not Found")
297 | return
298 | }
299 |
300 | if conf.MaintenanceMode {
301 | config.SecurityConfig.DisableProtection = false
302 | }
303 |
304 | if core.CheckIPBan(IPAddr, config) {
305 | connector.Error("-1", "Banned")
306 | return
307 | }
308 |
309 | Post := ReadPost(req)
310 | if core.CheckGDAuth(Post) {
311 | db := &core.MySQLConn{}
312 |
313 | if logger.Should(db.ConnectBlob(config)) != nil {
314 | serverError(connector)
315 | return
316 | }
317 | xacc := core.CAccount{DB: db}
318 | if !xacc.PerformGJPAuth(Post, IPAddr) {
319 | connector.Success("Invalid Credentials, but as per Geometry Dash API we should return 1 no matter what")
320 | return
321 | }
322 | xacc.LoadStats()
323 | core.TryInt(&xacc.ColorPrimary, Post.Get("color1"))
324 | core.TryInt(&xacc.ColorSecondary, Post.Get("color2"))
325 | core.TryInt(&xacc.ColorGlow, Post.Get("color3"))
326 | core.TryInt(&xacc.Stars, Post.Get("stars"))
327 | core.TryInt(&xacc.Demons, Post.Get("demons"))
328 | core.TryInt(&xacc.Diamonds, Post.Get("diamonds"))
329 | core.TryInt(&xacc.IconType, Post.Get("iconType"))
330 | core.TryInt(&xacc.Coins, Post.Get("coins"))
331 | core.TryInt(&xacc.UCoins, Post.Get("userCoins"))
332 | core.TryInt(&xacc.Moons, Post.Get("moons"))
333 | core.TryInt(&xacc.Special, Post.Get("special"))
334 | core.TryInt(&xacc.Cube, Post.Get("accIcon"))
335 | core.TryInt(&xacc.Ship, Post.Get("accShip"))
336 | core.TryInt(&xacc.Wave, Post.Get("accDart"))
337 | core.TryInt(&xacc.Ball, Post.Get("accBall"))
338 | core.TryInt(&xacc.Ufo, Post.Get("accBird"))
339 | core.TryInt(&xacc.Robot, Post.Get("accRobot"))
340 | core.TryInt(&xacc.Spider, Post.Get("accSpider"))
341 | core.TryInt(&xacc.Swing, Post.Get("accSwing"))
342 | core.TryInt(&xacc.Jetpack, Post.Get("accJetpack"))
343 | core.TryInt(&xacc.Trace, Post.Get("accGlow"))
344 | core.TryInt(&xacc.Death, Post.Get("accExplosion"))
345 |
346 | protect := core.CProtect{DB: db, Savepath: conf.SavePath + "/" + vars["gdps"], DisableProtection: config.SecurityConfig.DisableProtection}
347 | protect.LoadModel(conf, config)
348 | if !protect.DetectStats(xacc.Uid, xacc.Stars, xacc.Diamonds, xacc.Demons, xacc.Coins, xacc.UCoins) {
349 | connector.Error("-1", "Invalid stats breh") // Le trolling
350 | return
351 | }
352 |
353 | // 2.2 demon stats
354 | {
355 | core.TryInt(&xacc.ExtraData.DemonStats.Weeklies, Post.Get("dinfow"))
356 | core.TryInt(&xacc.ExtraData.DemonStats.Gauntlets, Post.Get("dinfog"))
357 | cf := &core.CLevelFilter{DB: db}
358 | data := cf.CountDemonTypes(core.Decompose(
359 | core.CleanDoubles(core.ClearGDRequest(Post.Get("dinfo")), ","),
360 | ","))
361 |
362 | xacc.ExtraData.DemonStats.Standard.Easy = data.Standard.Easy
363 | xacc.ExtraData.DemonStats.Standard.Medium = data.Standard.Medium
364 | xacc.ExtraData.DemonStats.Standard.Hard = data.Standard.Hard
365 | xacc.ExtraData.DemonStats.Standard.Insane = data.Standard.Insane
366 | xacc.ExtraData.DemonStats.Standard.Extreme = data.Standard.Extreme
367 |
368 | xacc.ExtraData.DemonStats.Platformer.Easy = data.Platformer.Easy
369 | xacc.ExtraData.DemonStats.Platformer.Medium = data.Platformer.Medium
370 | xacc.ExtraData.DemonStats.Platformer.Hard = data.Platformer.Hard
371 | xacc.ExtraData.DemonStats.Platformer.Insane = data.Platformer.Insane
372 | xacc.ExtraData.DemonStats.Platformer.Extreme = data.Platformer.Extreme
373 | }
374 |
375 | // 2.2 standard stats
376 | {
377 | core.TryInt(&xacc.ExtraData.StandardStats.Daily, Post.Get("sinfod"))
378 | core.TryInt(&xacc.ExtraData.StandardStats.Gauntlet, Post.Get("sinfog"))
379 | sinfo := core.Decompose(core.CleanDoubles(core.ClearGDRequest(Post.Get("sinfo")), ","), ",")
380 | if len(sinfo) == 12 {
381 | xacc.ExtraData.StandardStats.Auto = sinfo[0]
382 | xacc.ExtraData.StandardStats.Easy = sinfo[1]
383 | xacc.ExtraData.StandardStats.Normal = sinfo[2]
384 | xacc.ExtraData.StandardStats.Hard = sinfo[3]
385 | xacc.ExtraData.StandardStats.Harder = sinfo[4]
386 | xacc.ExtraData.StandardStats.Insane = sinfo[5]
387 | xacc.ExtraData.PlatformerStats.Auto = sinfo[6]
388 | xacc.ExtraData.PlatformerStats.Easy = sinfo[7]
389 | xacc.ExtraData.PlatformerStats.Normal = sinfo[8]
390 | xacc.ExtraData.PlatformerStats.Hard = sinfo[9]
391 | xacc.ExtraData.PlatformerStats.Harder = sinfo[10]
392 | xacc.ExtraData.PlatformerStats.Insane = sinfo[11]
393 | d := 0
394 | for _, v := range sinfo {
395 | d += v
396 | }
397 | if xacc.Demons > d {
398 | // Still have no idea why
399 | xacc.ExtraData.StandardStats.Hard += min(xacc.Demons-d, 5)
400 | }
401 | }
402 | }
403 |
404 | xacc.PushVessels()
405 | xacc.PushStatsAndExtra()
406 | connector.NumberedSuccess(xacc.Uid)
407 | } else {
408 | connector.Success("Bad Request, but as per Geometry Dash API we should return 1 no matter what")
409 | }
410 | }
411 |
--------------------------------------------------------------------------------
/src/api/service.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "HalogenGhostCore/core"
5 | "encoding/json"
6 | gorilla "github.com/gorilla/mux"
7 | "html"
8 | "io"
9 | "net/http"
10 | "strings"
11 | )
12 |
13 | var NotFoundTemplate = `
14 |
15 |
The chances are there is something, but it's not exactly here
25 | 26 | ` 27 | 28 | func Shield(resp http.ResponseWriter, req *http.Request, conf *core.GlobalConfig) { 29 | vars := gorilla.Vars(req) 30 | io.WriteString(resp, "[GhostCore] Serving //"+vars["gdps"]+"//") 31 | } 32 | 33 | func Redirector(resp http.ResponseWriter, req *http.Request) { 34 | http.Redirect(resp, req, "https://fruitspace.ru/", http.StatusMovedPermanently) 35 | } 36 | 37 | type NotFoundHandler int 38 | 39 | func (n NotFoundHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) { 40 | io.WriteString(resp, strings.ReplaceAll(NotFoundTemplate, "[PATH]", html.EscapeString(req.URL.Path))) 41 | } 42 | 43 | // Private API 44 | 45 | func TriggerMaintenance(resp http.ResponseWriter, req *http.Request, conf *core.GlobalConfig) { 46 | if req.URL.Query().Get("key") != conf.MasterKey { 47 | return 48 | } 49 | io.WriteString(resp, "OK") 50 | //go core.MaintainTasks() 51 | } 52 | 53 | func EventAction(resp http.ResponseWriter, req *http.Request, conf *core.GlobalConfig) { 54 | q := req.URL.Query() 55 | if q.Get("key") != conf.MasterKey { 56 | io.WriteString(resp, "KEY") 57 | return 58 | } 59 | mk := "off" 60 | if conf.MaintenanceMode { 61 | mk = "on" 62 | } 63 | switch q.Get("a") { 64 | case "on": 65 | conf.MaintenanceMode = true 66 | case "off": 67 | conf.MaintenanceMode = false 68 | default: 69 | io.WriteString(resp, mk) 70 | } 71 | core.SendMessageDiscord("Touched killswitch: status: " + mk) 72 | } 73 | 74 | func SendJson(resp http.ResponseWriter, jsonData map[string]string) { 75 | data, _ := json.Marshal(jsonData) 76 | io.WriteString(resp, string(data)) 77 | } 78 | -------------------------------------------------------------------------------- /src/api/web_ballistics.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "HalogenGhostCore/core" 5 | "fmt" 6 | "net/http" 7 | "strings" 8 | "time" 9 | ) 10 | 11 | var BallisticsCache map[string]int64 12 | var BadRepIP map[string]int 13 | var clusterFuck map[string][]string // map[timespan][]ip 14 | 15 | const BADREP_THRESHOLD = 20 16 | const CLUSTERFUCK_THRESHOLD = 10 17 | 18 | func PrepareBallistics(req *http.Request) bool { 19 | IPAddr := ipOf(req) 20 | if thr, ok := BadRepIP[IPAddr]; ok && thr > BADREP_THRESHOLD { 21 | // Here we block DDoSers from fucking everywhere 22 | return false 23 | } 24 | //rl := ratelimit.New(10) 25 | // Last timestamp for this path 26 | tm, ok := BallisticsCache[req.URL.Path] 27 | if !ok { 28 | BallisticsCache[req.URL.Path] = time.Now().UnixMilli() 29 | return false 30 | } 31 | BallisticsCache[req.URL.Path] = time.Now().UnixMilli() 32 | 33 | if time.Now().UnixMilli()-tm > 30000 { 34 | // I mean how the fuck are we going to stop that? 35 | return false 36 | } 37 | 38 | if _, ok := BadRepIP[IPAddr]; !ok { 39 | BadRepIP[IPAddr] = 0 40 | } 41 | 42 | // If registered from single IP and multiple requests less in than 30 seconds 43 | if time.Now().UnixMilli()-tm > 2000 && BadRepIP[IPAddr] < 5 { 44 | BadRepIP[IPAddr]++ 45 | return false 46 | } 47 | if time.Now().UnixMilli()-tm > 400 && BadRepIP[IPAddr] < BADREP_THRESHOLD { 48 | t := fmt.Sprintf("[%s, rep=%d] Unusual request speed .4s-2s at `%s`\nResult: `Throttle 5s`", IPAddr, BadRepIP[IPAddr], req.URL.Path) 49 | BadRepIP[IPAddr] += 2 50 | go core.SendMessageDiscord(t) 51 | time.Sleep(time.Second * 5) 52 | return false 53 | } 54 | clusterFuckTime := time.Now().Format("2006-01-02 15:04") 55 | if _, ok := clusterFuck[clusterFuckTime]; !ok { 56 | // Current minute wasn't registered, check previous 57 | if len(clusterFuck) > 0 { 58 | // We had previous keys - most likely from previous minute 59 | for k, v := range clusterFuck { 60 | // Most likely there will be only one key 61 | t := fmt.Sprintf("[Ballistics Clusterfuck] Found unfinished block at %s\n```\n%s\n```", k, strings.Join(v, "\n")) 62 | go core.SendMessageDiscord(t) 63 | break 64 | } 65 | clusterFuck = make(map[string][]string) 66 | } 67 | clusterFuck[clusterFuckTime] = []string{} 68 | } 69 | 70 | clusterFuck[clusterFuckTime] = append(clusterFuck[clusterFuckTime], IPAddr) 71 | if len(clusterFuck[clusterFuckTime]) > CLUSTERFUCK_THRESHOLD { 72 | // 99% it's a fucking DDoS 73 | for _, ip := range clusterFuck[clusterFuckTime] { 74 | BadRepIP[ip] = 100 75 | } 76 | t := fmt.Sprintf("[Ballistics Clusterfuck] Banned IPs at %s\n```\n%s\n```", clusterFuckTime, strings.Join(clusterFuck[clusterFuckTime], "\n")) 77 | go core.SendMessageDiscord(t) 78 | return true 79 | } 80 | 81 | BadRepIP[IPAddr] += 5 82 | time.Sleep(time.Second * 30) 83 | return true 84 | } 85 | -------------------------------------------------------------------------------- /src/api/web_cache.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "HalogenGhostCore/core" 5 | "context" 6 | "github.com/go-redis/redis/v8" 7 | "time" 8 | ) 9 | 10 | var cacheClient *redis.Client 11 | 12 | func InitCache(conf core.GlobalConfig, cacheDB int) { 13 | cacheClient = redis.NewClient(&redis.Options{ 14 | Addr: conf.RedisHost + ":" + conf.RedisPort, 15 | Password: conf.RedisPassword, 16 | DB: cacheDB, 17 | }) 18 | } 19 | 20 | func cached(key string) (string, error) { 21 | ctx := context.Background() 22 | return cacheClient.Get(ctx, key).Result() 23 | } 24 | 25 | func withCache(key string, value string) string { 26 | return withCacheDuration(key, value, time.Second*2) 27 | } 28 | 29 | func withCacheDuration(key string, value string, duration time.Duration) string { 30 | ctx := context.Background() 31 | cacheClient.Set(ctx, key, value, duration) 32 | return value 33 | } 34 | -------------------------------------------------------------------------------- /src/api/web_server.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "HalogenGhostCore/core" 5 | "HalogenGhostCore/core/connectors" 6 | "fmt" 7 | gorilla "github.com/gorilla/mux" 8 | "io" 9 | "log" 10 | "net/http" 11 | "net/url" 12 | "reflect" 13 | "runtime" 14 | "strings" 15 | ) 16 | 17 | type GhostServer struct { 18 | Log core.Logger 19 | Config core.GlobalConfig 20 | } 21 | 22 | var RouteMap = map[string]func(http.ResponseWriter, *http.Request, *core.GlobalConfig){ 23 | "/": Shield, 24 | 25 | // Geometry Dash 26 | "/db/accounts/accountManagement.php": AccountManagement, 27 | "/db/accounts/backupGJAccount.php": AccountBackup, 28 | "/db/accounts/loginGJAccount.php": AccountLogin, 29 | "/db/accounts/registerGJAccount.php": AccountRegister, 30 | "/db/accounts/syncGJAccount.php": AccountSync, 31 | "/db/accounts/syncGJAccount20.php": AccountSync, 32 | 33 | "/db/database/accounts/backupGJAccountNew.php": AccountBackup, 34 | "/db/database/accounts/syncGJAccountNew.php": AccountSync, 35 | 36 | "/db/acceptGJFriendRequest20.php": FriendAcceptRequest, 37 | "/db/blockGJUser20.php": BlockUser, 38 | "/db/deleteGJAccComment20.php": AccountCommentDelete, 39 | "/db/deleteGJComment20.php": CommentDelete, 40 | "/db/deleteGJFriendRequests20.php": FriendRejectRequest, 41 | "/db/deleteGJLevelUser20.php": LevelDelete, 42 | "/db/deleteGJMessages20.php": MessageDelete, 43 | "/db/downloadGJLevel.php": LevelDownload, 44 | "/db/downloadGJLevel19.php": LevelDownload, 45 | "/db/downloadGJLevel20.php": LevelDownload, 46 | "/db/downloadGJLevel21.php": LevelDownload, 47 | "/db/downloadGJLevel22.php": LevelDownload, 48 | "/db/downloadGJMessage20.php": MessageGet, 49 | "/db/getAccountURL.php": GetAccountUrl, 50 | "/db/getGJAccountComments20.php": AccountCommentGet, 51 | "/db/getGJChallenges.php": GetChallenges, 52 | "/db/getGJCommentHistory.php": CommentGetHistory, 53 | "/db/getGJComments.php": CommentGet, 54 | "/db/getGJComments19.php": CommentGet, 55 | "/db/getGJComments20.php": CommentGet, 56 | "/db/getGJComments21.php": CommentGet, 57 | "/db/getGJCreators.php": GetCreators, 58 | "/db/getGJCreators19.php": GetCreators, 59 | "/db/getGJDailyLevel.php": LevelGetDaily, 60 | "/db/getGJFriendRequests20.php": FriendGetRequests, 61 | "/db/getGJGauntlets.php": GetGauntlets, 62 | "/db/getGJGauntlets21.php": GetGauntlets, 63 | "/db/getGJLevels.php": LevelGetLevels, 64 | "/db/getGJLevels19.php": LevelGetLevels, 65 | "/db/getGJLevels20.php": LevelGetLevels, 66 | "/db/getGJLevels21.php": LevelGetLevels, 67 | "/db/getGJLevelScores.php": GetLevelScores, 68 | "/db/getGJLevelScores211.php": GetLevelScores, 69 | "/db/getGJMapPacks.php": GetMapPacks, 70 | "/db/getGJMapPacks20.php": GetMapPacks, 71 | "/db/getGJMapPacks21.php": GetMapPacks, 72 | "/db/getGJMessages20.php": MessageGetAll, 73 | "/db/getGJRewards.php": GetRewards, 74 | "/db/getGJScores.php": GetScores, 75 | "/db/getGJScores19.php": GetScores, 76 | "/db/getGJScores20.php": GetScores, 77 | "/db/getGJSongInfo.php": GetSongInfo, 78 | "/db/getGJTopArtists.php": GetTopArtists, 79 | "/db/getGJUserInfo20.php": GetUserInfo, 80 | "/db/getGJUserList20.php": GetUserList, 81 | "/db/getGJUsers20.php": GetUsers, 82 | "/db/likeGJItem.php": LikeItem, 83 | "/db/likeGJItem19.php": LikeItem, 84 | "/db/likeGJItem20.php": LikeItem, 85 | "/db/likeGJItem21.php": LikeItem, 86 | "/db/likeGJItem211.php": LikeItem, 87 | "/db/rateGJDemon21.php": RateDemon, 88 | "/db/rateGJStars20.php": RateStar, 89 | "/db/rateGJStars211.php": RateStar, 90 | "/db/readGJFriendRequest20.php": FriendReadRequest, 91 | "/db/removeGJFriend20.php": FriendRemove, 92 | "/db/reportGJLevel.php": LevelReport, 93 | "/db/requestUserAccess.php": RequestMod, 94 | "/db/suggestGJStars20.php": SuggestStars, 95 | "/db/unblockGJUser20.php": UnblockUser, 96 | "/db/updateGJAccSettings20.php": UpdateAccountSettings, 97 | "/db/updateGJDesc20.php": LevelUpdateDescription, 98 | "/db/updateGJUserScore.php": UpdateUserScore, 99 | "/db/updateGJUserScore19.php": UpdateUserScore, 100 | "/db/updateGJUserScore20.php": UpdateUserScore, 101 | "/db/updateGJUserScore21.php": UpdateUserScore, 102 | "/db/updateGJUserScore22.php": UpdateUserScore, 103 | "/db/uploadFriendRequest20.php": FriendRequest, 104 | "/db/uploadGJAccComment20.php": AccountCommentUpload, 105 | "/db/uploadGJComment.php": CommentUpload, 106 | "/db/uploadGJComment19.php": CommentUpload, 107 | "/db/uploadGJComment20.php": CommentUpload, 108 | "/db/uploadGJComment21.php": CommentUpload, 109 | "/db/uploadGJLevel.php": LevelUpload, 110 | "/db/uploadGJLevel19.php": LevelUpload, 111 | "/db/uploadGJLevel20.php": LevelUpload, 112 | "/db/uploadGJLevel21.php": LevelUpload, 113 | "/db/uploadGJMessage20.php": MessageUpload, 114 | 115 | "/db/deleteGJLevelList.php": LevelListDelete, 116 | "/db/uploadGJLevelList.php": LevelListUpload, 117 | "/db/getGJLevelLists.php": LevelListSearch, 118 | "/db/getGJLevelScoresPlat.php": GetLevelPlatScores, 119 | 120 | "/db/getCustomContentURL.php": GetContentURL, 121 | "/db/content/sfx/{sfxid}": RelaySFX, 122 | 123 | //"/db/content/sfx/sfxlibrary.dat": GetSFXLibrary, 124 | //"/db/content/sfx/sfxlibrary_version.txt": GetSFXLibraryVersion, 125 | //"/db/content/sfx/s{sfxid}.ogg": GetSFXTrack, 126 | 127 | "/db/content/music/musiclibrary.dat": GetMusicLibrary, 128 | "/db/content/music/musiclibrary_02.dat": GetMusicLibrary, 129 | "/db/content/music/musiclibrary_version.txt": GetMusicLibraryVersion, 130 | "/db/mp3": GetSongInfo, 131 | } 132 | 133 | var RouteIntegraMap = map[string]func(http.ResponseWriter, *http.Request, *core.GlobalConfig){ 134 | // PRIVATE API 135 | "/integra/maintenance": TriggerMaintenance, 136 | //"/integra/killskew": EventAction, 137 | } 138 | 139 | func GetFunctionName(i interface{}) string { 140 | return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name() 141 | } 142 | 143 | func (ghost *GhostServer) StartServer(Host string) { 144 | BallisticsCache = make(map[string]int64) 145 | BadRepIP = make(map[string]int) 146 | clusterFuck = make(map[string][]string) 147 | mux := gorilla.NewRouter() 148 | var nfh NotFoundHandler 149 | mux.NotFoundHandler = nfh 150 | mux.HandleFunc("/", Redirector) 151 | for route := range RouteMap { 152 | mux.HandleFunc("/{gdps:[a-zA-Z0-9][a-zA-Z0-9][a-zA-Z0-9][a-zA-Z0-9]}"+route, 153 | func(resp http.ResponseWriter, req *http.Request) { 154 | vars := gorilla.Vars(req) 155 | pref := strings.Replace(req.URL.Path, "/"+vars["gdps"], "", 1) 156 | handler := RouteMap[pref] 157 | if strings.HasPrefix(pref, "/db/content/sfx/") { 158 | handler = RelaySFX 159 | } 160 | IPAddr := req.Header.Get("CF-Connecting-IP") 161 | if IPAddr == "" { 162 | IPAddr = req.Header.Get("X-Real-IP") 163 | } 164 | if IPAddr == "" { 165 | IPAddr = strings.Split(req.RemoteAddr, ":")[0] 166 | } 167 | log.Println("["+IPAddr+"] "+req.URL.Path, " Got ", GetFunctionName(handler)) 168 | handler(resp, req, &ghost.Config) 169 | }) 170 | } 171 | for route, handler := range RouteIntegraMap { 172 | mux.HandleFunc(route, 173 | func(resp http.ResponseWriter, req *http.Request) { 174 | defer func() { 175 | if err := recover(); err != nil { 176 | log.Println(err) 177 | core.SendMessageDiscord(fmt.Sprintf("Panic: %s", err)) 178 | } 179 | }() 180 | IPAddr := req.Header.Get("CF-Connecting-IP") 181 | if IPAddr == "" { 182 | IPAddr = req.Header.Get("X-Real-IP") 183 | } 184 | if IPAddr == "" { 185 | IPAddr = strings.Split(req.RemoteAddr, ":")[0] 186 | } 187 | log.Println("["+IPAddr+"] "+req.URL.Path, " Got ", GetFunctionName(handler)) 188 | handler(resp, req, &ghost.Config) 189 | }) 190 | } 191 | log.Println("Server is up and running on http://" + Host) 192 | err := http.ListenAndServe(Host, mux) 193 | if err != nil { 194 | ghost.Log.LogErr(ghost, err.Error()) 195 | } 196 | 197 | } 198 | 199 | func ReadPost(req *http.Request) url.Values { 200 | if req.Body == nil { 201 | return url.Values{} 202 | } 203 | body, err := io.ReadAll(req.Body) 204 | if err != nil { 205 | log.Println(err.Error()) 206 | return url.Values{} 207 | } 208 | if len(body) == 0 || strings.Count(string(body), "=") == 0 { 209 | return url.Values{} 210 | } 211 | vals := make(url.Values) 212 | pairs := strings.Split(string(body), "&") 213 | for _, val := range pairs { 214 | if !strings.Contains(val, "=") { 215 | continue 216 | } 217 | m := strings.SplitN(val, "=", 2) 218 | //fmt.Println(m) 219 | rval, _ := url.QueryUnescape(m[1]) 220 | rkey, _ := url.QueryUnescape(m[0]) 221 | vals[rkey] = append(vals[rkey], rval) 222 | } 223 | return vals 224 | } 225 | 226 | func ipOf(req *http.Request) string { 227 | IPAddr := req.Header.Get("CF-Connecting-IP") 228 | if IPAddr == "" { 229 | IPAddr = req.Header.Get("X-Real-IP") 230 | } 231 | if IPAddr == "" { 232 | IPAddr = strings.Split(req.RemoteAddr, ":")[0] 233 | } 234 | return IPAddr 235 | } 236 | 237 | func serverError(connector connectors.Connector) { 238 | connector.Error("-1", "Server Error") 239 | } 240 | -------------------------------------------------------------------------------- /src/api/web_templates.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "encoding/base64" 5 | ) 6 | 7 | func BannedTemplateComment(text string) string { 8 | text=base64.StdEncoding.EncodeToString([]byte(text)) 9 | return "2~"+text+"~3~1~4~0~7~0~10~0~9~1 second~6~1:1~M41dss~9~98~10~35~11~3~14~0~15~2~16~0" 10 | } 11 | 12 | func BannedTemplateUserProfile() string { 13 | uname:="M41dss" 14 | return "1:"+uname+":2:0:3:41:4:0:6:1:7:0:8:41:9:98:10:35:11::13:41:14:0:15:2:16:0:17:41:18:0:19:0:20::21:98:22:SHIP_ID"+ 15 | ":23:BALL_ID:24:UFO_ID:25:WAVE_ID:26:ROBOT_ID:28:1:29:1:30:1:31:0:43:SPIDER_ID:44::45::46:41:48:1:49:2:50:0:38:0:39:0:40:0" 16 | } 17 | 18 | func BannedTemplateUserListItem() string { 19 | uname:="M41dss" 20 | return "1:"+uname+":2:0:9:98:10:35:11:3:14:0:15:2:16:0:18:0:41:1|" 21 | } -------------------------------------------------------------------------------- /src/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | RED='\033[0;31m' 4 | BLUE='\033[0;34m' 5 | GREEN='\033[0;32m' 6 | NC='\033[0m' 7 | GRAY='\033[0;37m' 8 | 9 | PATH=$PATH:~/go/bin 10 | echo -e "${GREEN}Checking for ineffectual assignments...${GRAY}" 11 | ineffassign ./... 12 | echo -e "${GREEN}Building...${GRAY}" 13 | CGO_ENABLED=0 go build -ldflags="-s -w" -trimpath -o HalogenGhostCore . 14 | echo -e "${GREEN}Done.${NC}" -------------------------------------------------------------------------------- /src/core/CAccount_structs.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import "strings" 4 | 5 | type CAccountJSON struct { 6 | Uid int `json:"uid"` 7 | Uname string `json:"uname"` 8 | Email string `json:"email,omitempty"` 9 | ModBadge int `json:"mod_badge,omitempty"` 10 | Role Role `json:"role,omitempty"` 11 | IsBanned bool `json:"is_banned"` 12 | LeaderboardRank int `json:"leaderboard_rank,omitempty"` 13 | 14 | WeAreFriends bool `json:"we_are_friends,omitempty"` 15 | NewMessagesCount int `json:"new_messages_count,omitempty"` 16 | NewFriendRequests int `json:"new_friend_requests_count,omitempty"` 17 | 18 | Stats CAccountStats `json:"stats,omitempty"` 19 | Technical CAccountTechnical `json:"technical,omitempty"` 20 | Social CAccountSocial `json:"social,omitempty"` 21 | Vessels CAccountVessels `json:"vessels,omitempty"` 22 | Chests CAccountChests `json:"chests,omitempty"` 23 | Settings CAccountSettings `json:"settings,omitempty"` 24 | } 25 | 26 | func NewCAccountJSONFromAccount(acc CAccount, role *Role, personal bool) CAccountJSON { 27 | // Basic stuff 28 | aj := CAccountJSON{ 29 | Uid: acc.Uid, 30 | Uname: acc.Uname, 31 | IsBanned: acc.IsBanned > 0, 32 | LeaderboardRank: acc.GetLeaderboardRank() + 1, 33 | Stats: NewCAccountStatsFromAccount(acc), 34 | Vessels: NewCAccountVesselsFromAccount(acc), 35 | Settings: NewCAccountSettingsFromAccount(acc), 36 | } 37 | 38 | // Roles 39 | if role != nil { 40 | aj.ModBadge = role.ModLevel 41 | if personal { 42 | aj.Role = *role 43 | } 44 | } 45 | 46 | // Personal 47 | if personal { 48 | aj.Email = acc.Email 49 | aj.Social = NewCAccountSocialFromAccount(acc) 50 | aj.Technical = NewCAccountTechnicalFromAccount(acc) 51 | aj.Chests = NewCAccountChestsFromAccount(acc) 52 | cf := CFriendship{DB: acc.DB} 53 | cm := CMessage{DB: acc.DB} 54 | aj.NewFriendRequests = cf.CountFriendRequests(acc.Uid, true) 55 | aj.NewMessagesCount = cm.CountMessages(acc.Uid, true) 56 | } 57 | 58 | return aj 59 | } 60 | 61 | func NewCAccountJSONFromAccountLite(acc CAccount) CAccountJSON { 62 | return CAccountJSON{ 63 | Uid: acc.Uid, 64 | Uname: acc.Uname, 65 | IsBanned: acc.IsBanned > 0, 66 | Stats: NewCAccountStatsFromAccount(acc), 67 | Vessels: NewCAccountVesselsFromAccount(acc), 68 | } 69 | } 70 | 71 | type CAccountStats struct { 72 | Stars int `json:"stars"` 73 | Diamonds int `json:"diamonds"` 74 | Coins int `json:"coins"` 75 | UCoins int `json:"ucoins"` 76 | Demons int `json:"demons"` 77 | CPoints int `json:"cpoints"` 78 | Orbs int `json:"orbs"` 79 | Moons int `json:"moons"` 80 | Special int `json:"special"` 81 | LvlsCompleted int `json:"lvls_completed"` 82 | } 83 | 84 | func NewCAccountStatsFromAccount(acc CAccount) CAccountStats { 85 | return CAccountStats{ 86 | Stars: acc.Stars, 87 | Diamonds: acc.Diamonds, 88 | Coins: acc.Coins, 89 | UCoins: acc.UCoins, 90 | Demons: acc.Demons, 91 | CPoints: acc.CPoints, 92 | Orbs: acc.Orbs, 93 | Moons: acc.Moons, 94 | Special: acc.Special, 95 | LvlsCompleted: acc.LvlsCompleted, 96 | } 97 | } 98 | 99 | type CAccountTechnical struct { 100 | RegDate string `json:"reg_date"` 101 | AccessDate string `json:"access_date"` 102 | LastIP string `json:"last_ip"` 103 | GameVer string `json:"game_ver"` 104 | } 105 | 106 | func NewCAccountTechnicalFromAccount(acc CAccount) CAccountTechnical { 107 | return CAccountTechnical{ 108 | RegDate: acc.RegDate, 109 | AccessDate: acc.AccessDate, 110 | LastIP: acc.LastIP, 111 | GameVer: acc.GameVer, 112 | } 113 | } 114 | 115 | type CAccountSocial struct { 116 | BlacklistIds []int `json:"blacklist_ids"` 117 | FriendsCount int `json:"friends_count"` 118 | FriendshipIds []int `json:"friendship_ids"` 119 | } 120 | 121 | func NewCAccountSocialFromAccount(acc CAccount) CAccountSocial { 122 | blacklist := strings.Split(acc.Blacklist, ",") 123 | friendships := strings.Split(acc.FriendshipIds, ",") 124 | return CAccountSocial{ 125 | BlacklistIds: ArrTranslateToInt(blacklist), 126 | FriendsCount: acc.FriendsCount, 127 | FriendshipIds: ArrTranslateToInt(friendships), 128 | } 129 | } 130 | 131 | type CAccountVessels struct { 132 | ShownIcon int `json:"shown_icon"` 133 | IconType int `json:"icon_type"` 134 | ColorPrimary int `json:"color_primary"` 135 | ColorSecondary int `json:"color_secondary"` 136 | ColorGlow int `json:"color_glow"` 137 | Cube int `json:"cube"` 138 | Ship int `json:"ship"` 139 | Ball int `json:"ball"` 140 | Ufo int `json:"ufo"` 141 | Wave int `json:"wave"` 142 | Robot int `json:"robot"` 143 | Spider int `json:"spider"` 144 | Swing int `json:"swing"` 145 | Jetpack int `json:"jetpack"` 146 | Trace int `json:"trace"` 147 | Death int `json:"death"` 148 | } 149 | 150 | func NewCAccountVesselsFromAccount(acc CAccount) CAccountVessels { 151 | return CAccountVessels{ 152 | ShownIcon: acc.GetShownIcon(), 153 | IconType: acc.IconType, 154 | ColorPrimary: acc.ColorPrimary, 155 | ColorSecondary: acc.ColorSecondary, 156 | ColorGlow: acc.ColorGlow, 157 | Cube: acc.Cube, 158 | Ship: acc.Ship, 159 | Ball: acc.Ball, 160 | Ufo: acc.Ufo, 161 | Wave: acc.Wave, 162 | Robot: acc.Robot, 163 | Spider: acc.Spider, 164 | Swing: acc.Swing, 165 | Jetpack: acc.Jetpack, 166 | Trace: acc.Trace, 167 | Death: acc.Death, 168 | } 169 | } 170 | 171 | type CAccountChests struct { 172 | ChestSmallCount int `json:"chest_small_count"` 173 | ChestSmallTime int `json:"chest_small_time_left"` 174 | ChestBigCount int `json:"chest_big_count"` 175 | ChestBigTime int `json:"chest_big_time_left"` 176 | } 177 | 178 | func NewCAccountChestsFromAccount(acc CAccount) CAccountChests { 179 | return CAccountChests{ 180 | ChestSmallCount: acc.ChestSmallCount, 181 | ChestSmallTime: acc.ChestSmallTime, 182 | ChestBigCount: acc.ChestBigCount, 183 | ChestBigTime: acc.ChestBigTime, 184 | } 185 | } 186 | 187 | type CAccountSettings struct { 188 | AllowFriendReq bool `json:"allow_friend_requests"` 189 | AllowComments string `json:"allow_view_comments"` 190 | AllowMessages string `json:"allow_messages"` 191 | Youtube string `json:"youtube"` 192 | Twitch string `json:"twitch"` 193 | Twitter string `json:"twitter"` 194 | } 195 | 196 | func NewCAccountSettingsFromAccount(acc CAccount) CAccountSettings { 197 | s := CAccountSettings{ 198 | Youtube: acc.Youtube, 199 | Twitch: acc.Twitch, 200 | Twitter: acc.Twitter, 201 | AllowFriendReq: true, 202 | AllowComments: "everybody", 203 | AllowMessages: "everybody", 204 | } 205 | allowances := []string{"everybody", "friends", "nobody"} 206 | 207 | if acc.FrS > 0 { 208 | s.AllowFriendReq = false 209 | } 210 | if acc.CS > 0 { 211 | s.AllowComments = allowances[Clamp(acc.CS, 0, 2)] 212 | } 213 | if acc.MS > 0 { 214 | s.AllowMessages = allowances[Clamp(acc.MS, 0, 2)] 215 | } 216 | return s 217 | } 218 | -------------------------------------------------------------------------------- /src/core/CComment.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "strconv" 5 | "time" 6 | ) 7 | 8 | type CComment struct { 9 | Id int `json:"id"` 10 | Uid int `json:"uid"` 11 | Likes int `json:"likes"` 12 | PostedTime string `json:"posted_time"` 13 | Comment string `json:"comment"` 14 | 15 | LvlId int `json:"lvl_id"` 16 | Percent int `json:"percent"` 17 | IsSpam bool `json:"is_spam"` 18 | 19 | DB *MySQLConn `json:"-"` 20 | } 21 | 22 | func (cc *CComment) ExistsLevelComment(id int) bool { 23 | var cnt int 24 | cc.DB.ShouldQueryRow("SELECT count(*) as cnt FROM #DB#.comments WHERE id=?", id).Scan(&cnt) 25 | return cnt > 0 26 | } 27 | 28 | func (cc *CComment) ExistsAccComment(id int) bool { 29 | var cnt int 30 | cc.DB.ShouldQueryRow("SELECT count(*) as cnt FROM #DB#.acccomments WHERE id=?", id).Scan(&cnt) 31 | return cnt > 0 32 | } 33 | 34 | func (cc *CComment) CountAccComments(uid int) int { 35 | var cnt int 36 | pf := "" 37 | if uid > 0 { 38 | pf = " WHERE uid=" + strconv.Itoa(uid) 39 | } 40 | cc.DB.ShouldQueryRow("SELECT count(*) as cnt FROM #DB#.acccomments" + pf).Scan(&cnt) 41 | return cnt 42 | } 43 | 44 | func (cc *CComment) CountLevelComments(lvlId int) int { 45 | var cnt int 46 | pf := "" 47 | if lvlId > 0 { 48 | pf = " WHERE lvl_id=" + strconv.Itoa(lvlId) 49 | } 50 | cc.DB.ShouldQueryRow("SELECT count(*) as cnt FROM #DB#.comments" + pf).Scan(&cnt) 51 | return cnt 52 | } 53 | 54 | func (cc *CComment) CountCommentHistory(uid int) int { 55 | var cnt int 56 | cc.DB.ShouldQueryRow("SELECT count(*) as cnt FROM #DB#.comments WHERE uid=?", uid).Scan(&cnt) 57 | return cnt 58 | } 59 | 60 | func (cc *CComment) LoadAccComment() { 61 | cc.DB.ShouldQueryRow("SELECT uid,comment,postedTime,likes,isSpam FROM #DB#.acccomments WHERE id=?", cc.Id).Scan( 62 | &cc.Uid, &cc.Comment, &cc.PostedTime, &cc.Likes, &cc.IsSpam) 63 | } 64 | 65 | func (cc *CComment) LoadLevelComment() { 66 | cc.DB.ShouldQueryRow("SELECT uid,lvl_id,comment,postedTime,likes,isSpam,percent FROM #DB#.comments WHERE id=?", cc.Id).Scan( 67 | &cc.Uid, &cc.LvlId, &cc.Comment, &cc.PostedTime, &cc.Likes, &cc.IsSpam, &cc.Percent) 68 | } 69 | 70 | func (cc *CComment) GetAllAccComments(uid int, page int) []CComment { 71 | page *= 10 72 | rows := cc.DB.ShouldQuery("SELECT id,comment,postedTime,likes,isSpam FROM #DB#.acccomments WHERE uid=? ORDER BY postedTime DESC LIMIT 10 OFFSET "+strconv.Itoa(page), uid) 73 | defer rows.Close() 74 | var out []CComment 75 | for rows.Next() { 76 | comm := CComment{Uid: uid} 77 | rows.Scan(&comm.Id, &comm.Comment, &comm.PostedTime, &comm.Likes, &comm.IsSpam) 78 | out = append(out, comm) 79 | } 80 | return out 81 | } 82 | 83 | func (cc *CComment) GetAllLevelComments(lvlId int, page int, sortMode bool) []CComment { 84 | filter := "postedTime" 85 | page *= 10 86 | if sortMode { 87 | filter = "likes" 88 | } 89 | rows := cc.DB.ShouldQuery("SELECT id,uid,comment,postedTime,likes,isSpam,percent FROM #DB#.comments WHERE lvl_id=? ORDER BY "+filter+" DESC LIMIT 10 OFFSET "+strconv.Itoa(page), lvlId) 90 | defer rows.Close() 91 | var out []CComment 92 | for rows.Next() { 93 | comm := CComment{LvlId: lvlId, DB: cc.DB} 94 | rows.Scan(&comm.Id, &comm.Uid, &comm.Comment, &comm.PostedTime, &comm.Likes, &comm.IsSpam, &comm.Percent) 95 | out = append(out, comm) 96 | } 97 | return out 98 | } 99 | 100 | func (cc *CComment) GetAllCommentsHistory(uid int, page int, sortMode bool) []CComment { 101 | page *= 10 102 | filter := "postedTime" 103 | if sortMode { 104 | filter = "likes" 105 | } 106 | rows := cc.DB.ShouldQuery("SELECT id,lvl_id,comment,postedTime,likes,isSpam,percent FROM #DB#.comments WHERE uid=? ORDER BY "+filter+" DESC LIMIT 10 OFFSET "+strconv.Itoa(page), uid) 107 | defer rows.Close() 108 | var out []CComment 109 | for rows.Next() { 110 | comm := CComment{Uid: uid} 111 | rows.Scan(&comm.Id, &comm.LvlId, &comm.Comment, &comm.PostedTime, &comm.Likes, &comm.IsSpam, &comm.Percent) 112 | out = append(out, comm) 113 | } 114 | return out 115 | } 116 | 117 | func (cc *CComment) PostAccComment() bool { 118 | if len(cc.Comment) > 128 { 119 | return false 120 | } 121 | cc.DB.ShouldExec("INSERT INTO #DB#.acccomments (uid,comment,postedTime) VALUES (?,?,?)", cc.Uid, cc.Comment, 122 | time.Now().Format("2006-01-02 15:04:05")) 123 | return true 124 | } 125 | 126 | func (cc *CComment) PostLevelComment() bool { 127 | if len(cc.Comment) > 128 { 128 | return false 129 | } 130 | cc.DB.ShouldExec("INSERT INTO #DB#.comments (uid,lvl_id,comment,postedTime,percent) VALUES (?,?,?,?,?)", cc.Uid, 131 | cc.LvlId, cc.Comment, time.Now().Format("2006-01-02 15:04:05"), cc.Percent) 132 | return true 133 | } 134 | 135 | func (cc *CComment) DeleteAccComment(id int, uid int) { 136 | cc.DB.ShouldExec("DELETE FROM #DB#.acccomments WHERE id=? AND uid=?", id, uid) 137 | } 138 | 139 | func (cc *CComment) DeleteLevelComment(id int, uid int) { 140 | cc.DB.ShouldExec("DELETE FROM #DB#.comments WHERE id=? AND uid=?", id, uid) 141 | } 142 | 143 | func (cc *CComment) DeleteOwnerLevelComment(id int, lvlId int) { 144 | cc.DB.ShouldExec("DELETE FROM #DB#.comments WHERE id=? AND lvl_id=?", id, lvlId) 145 | } 146 | 147 | func (cc *CComment) LikeAccComment(id int, uid int, actionLike bool) bool { 148 | if IsLiked(ITEMTYPE_ACCCOMMENT, uid, id, cc.DB) { 149 | return false 150 | } 151 | operator := "-" 152 | actionc := "Dislike" 153 | if actionLike { 154 | operator = "+" 155 | actionc = "Like" 156 | } 157 | cc.DB.ShouldExec("UPDATE #DB#.acccomments SET likes=likes"+operator+"1 WHERE id=?", id) 158 | RegisterAction(ACTION_ACCCOMMENT_LIKE, uid, id, map[string]string{"type": actionc}, cc.DB) 159 | return true 160 | } 161 | 162 | func (cc *CComment) LikeLevelComment(id int, uid int, actionLike bool) bool { 163 | if IsLiked(ITEMTYPE_COMMENT, uid, id, cc.DB) { 164 | return false 165 | } 166 | operator := "-" 167 | actionc := "Dislike" 168 | if actionLike { 169 | operator = "+" 170 | actionc = "Like" 171 | } 172 | cc.DB.ShouldExec("UPDATE #DB#.comments SET likes=likes"+operator+"1 WHERE id=?", id) 173 | RegisterAction(ACTION_COMMENT_LIKE, uid, id, map[string]string{"type": actionc}, cc.DB) 174 | return true 175 | } 176 | -------------------------------------------------------------------------------- /src/core/CFriendship.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "slices" 5 | "strconv" 6 | "strings" 7 | "time" 8 | ) 9 | 10 | type CFriendship struct { 11 | DB *MySQLConn 12 | } 13 | 14 | func (cf *CFriendship) IsAlreadyFriend(uid_dest int, uid int) bool { 15 | var cnt int 16 | cf.DB.MustQueryRow("SELECT count(*) as cnt FROM #DB#.friendships WHERE (uid1=? AND uid2=?) OR (uid2=? AND uid1=?)", 17 | uid, uid_dest, uid, uid_dest).Scan(&cnt) 18 | return cnt > 0 19 | } 20 | 21 | func (cf *CFriendship) IsAlreadySentFriend(uid_dest int, uid int) bool { 22 | var cnt int 23 | cf.DB.MustQueryRow("SELECT count(*) as cnt FROM #DB#.friendreqs WHERE uid_src=? AND uid_dest=?", uid, uid_dest).Scan(&cnt) 24 | return cnt > 0 25 | } 26 | 27 | func (cf *CFriendship) CountFriendRequests(uid int, new bool) int { 28 | var cnt int 29 | q := "SELECT count(*) as cnt FROM #DB#.friendreqs WHERE uid_dest=?" 30 | if new { 31 | q += " AND isNew=1" 32 | } 33 | cf.DB.MustQueryRow(q, uid).Scan(&cnt) 34 | return cnt 35 | } 36 | 37 | func (cf *CFriendship) GetFriendRequests(uid int, page int, sent bool) (int, []map[string]string) { 38 | q := "SELECT id,uid_src,uid_dest,uploadDate,comment,isNew FROM #DB#.friendreqs WHERE " 39 | if sent { 40 | q += "uid_src=?" 41 | } else { 42 | q += "uid_dest=?" 43 | } 44 | q += " LIMIT 10 OFFSET " + strconv.Itoa(page*10) 45 | rows := cf.DB.MustQuery(q, uid) 46 | defer rows.Close() 47 | var users []map[string]string 48 | var cnt int 49 | for rows.Next() { 50 | var ( 51 | id int 52 | src int 53 | dest int 54 | date string 55 | comment string 56 | isNew int 57 | ) 58 | rows.Scan(&id, &src, &dest, &date, &comment, &isNew) 59 | cnt++ 60 | user := make(map[string]string) 61 | user["id"] = strconv.Itoa(id) 62 | user["comment"] = comment 63 | acc := CAccount{DB: cf.DB} 64 | if sent { 65 | acc.Uid = dest 66 | } else { 67 | acc.Uid = src 68 | } 69 | user["uid"] = strconv.Itoa(acc.Uid) 70 | acc.LoadAuth(CAUTH_UID) 71 | acc.LoadStats() 72 | acc.LoadVessels() 73 | user["uname"] = acc.Uname 74 | user["isNew"] = strconv.Itoa(isNew) 75 | user["special"] = strconv.Itoa(acc.Special) 76 | user["iconType"] = strconv.Itoa(acc.IconType) 77 | user["clr_primary"] = strconv.Itoa(acc.ColorPrimary) 78 | user["clr_secondary"] = strconv.Itoa(acc.ColorSecondary) 79 | user["iconId"] = strconv.Itoa(acc.GetShownIcon()) 80 | user["date"] = date 81 | users = append(users, user) 82 | } 83 | return cnt, users 84 | } 85 | 86 | func (cf *CFriendship) GetFriendRequestsCount(uid int, sent bool) int { 87 | var cnt int 88 | q := "SELECT count(*) as cnt FROM #DB#.friendreqs WHERE " 89 | if sent { 90 | q += "uid_src=?" 91 | } else { 92 | q += "uid_dest=?" 93 | } 94 | cf.DB.MustQueryRow(q, uid).Scan(&cnt) 95 | return cnt 96 | } 97 | 98 | func (cf *CFriendship) DeleteFriendship(uid int, uid_dest int) { 99 | id := cf.GetFriendshipId(uid, uid_dest) 100 | if id == 0 { 101 | return 102 | } 103 | cf.DB.ShouldExec("DELETE FROM #DB#.friendships WHERE (uid1=? AND uid2=?) OR (uid2=? AND uid1=?)", uid, uid_dest, uid, uid_dest) 104 | u1 := CAccount{DB: cf.DB} 105 | u2 := CAccount{DB: cf.DB} 106 | u1.Uid = uid 107 | u2.Uid = uid_dest 108 | u1.UpdateFriendships(CFRIENDSHIP_REMOVE, id) 109 | u2.UpdateFriendships(CFRIENDSHIP_REMOVE, id) 110 | } 111 | 112 | func (cf *CFriendship) GetFriendshipId(uid int, uid_dest int) int { 113 | var id int 114 | cf.DB.ShouldQueryRow("SELECT id FROM #DB#.friendships WHERE (uid1=? AND uid2=?) OR (uid2=? AND uid1=?)", uid, uid_dest, uid, uid_dest).Scan(&id) 115 | return id 116 | } 117 | 118 | func (cf *CFriendship) GetFriendByFID(id int) (int, int) { 119 | var ( 120 | uid1 int 121 | uid2 int 122 | ) 123 | cf.DB.ShouldQueryRow("SELECT uid1,uid2 FROM #DB#.friendships WHERE id=?", id).Scan(&uid1, &uid2) 124 | return uid1, uid2 125 | } 126 | 127 | func (cf *CFriendship) GetAccFriends(acc CAccount) []int { 128 | fr := strings.Split(acc.FriendshipIds, ",") 129 | var frlist []int 130 | for _, sfr := range fr { 131 | id, err := strconv.Atoi(sfr) 132 | if err != nil { 133 | continue 134 | } 135 | uid1, uid2 := cf.GetFriendByFID(id) 136 | targetUid := uid1 137 | if uid1 == acc.Uid { 138 | targetUid = uid2 139 | } 140 | frlist = append(frlist, targetUid) 141 | } 142 | return frlist 143 | } 144 | 145 | func (cf *CFriendship) ReadFriendRequest(id int) { 146 | cf.DB.ShouldExec("UPDATE #DB#.friendreqs SET isNew=0 WHERE id=?", id) 147 | } 148 | 149 | func (cf *CFriendship) RequestFriend(uid int, uidDest int, comment string) int { 150 | if uid == uidDest || cf.IsAlreadyFriend(uid, uidDest) || cf.IsAlreadySentFriend(uid, uidDest) || len(comment) > 512 { 151 | return -1 152 | } 153 | acc := CAccount{DB: cf.DB} 154 | acc.Uid = uidDest 155 | acc.LoadSettings() 156 | if acc.FrS > 0 { 157 | return -1 158 | } 159 | acc.LoadSocial() 160 | blacklist := strings.Split(acc.Blacklist, ",") 161 | if slices.Contains(blacklist, strconv.Itoa(uid)) { 162 | return -1 163 | } 164 | acc.DB.ShouldExec("INSERT INTO #DB#.friendreqs (uid_src, uid_dest, uploadDate, comment) VALUES (?,?,?,?)", 165 | uid, uidDest, time.Now().Format("2006-01-02 15:04:05"), comment) 166 | return 1 167 | } 168 | 169 | func (cf *CFriendship) AcceptFriendRequest(id int, uid int) int { 170 | var ( 171 | src int 172 | dest int 173 | ) 174 | cf.DB.ShouldQueryRow("SELECT uid_src,uid_dest FROM #DB#.friendreqs WHERE id=?", id).Scan(&src, &dest) 175 | if src == dest || uid != dest { 176 | return -1 177 | } 178 | req, _ := cf.DB.PrepareExec("INSERT INTO #DB#.friendships (uid1, uid2) VALUES (?,?)", src, dest) 179 | iid, _ := req.LastInsertId() 180 | cf.DB.ShouldExec("DELETE FROM #DB#.friendreqs WHERE id=?", id) 181 | u1 := CAccount{DB: cf.DB} 182 | u2 := CAccount{DB: cf.DB} 183 | u1.Uid = src 184 | u2.Uid = dest 185 | res := u1.UpdateFriendships(CFRIENDSHIP_ADD, int(iid)) 186 | res += u2.UpdateFriendships(CFRIENDSHIP_ADD, int(iid)) 187 | if res != 2 { 188 | return -1 189 | } 190 | return 1 191 | } 192 | 193 | func (cf *CFriendship) RejectFriendRequestById(id int, uid int) int { 194 | var ( 195 | uid1 int 196 | uid2 int 197 | ) 198 | cf.DB.ShouldQueryRow("SELECT uid_src, uid_dest FROM #DB#.friendreqs WHERE id=?", id).Scan(&uid1, &uid2) 199 | if uid1 == uid2 || uid != uid2 { 200 | return -1 201 | } 202 | cf.DB.ShouldExec("DELETE FROM #DB#.friendreqs WHERE id=?", id) 203 | return 1 204 | } 205 | 206 | func (cf *CFriendship) RejectFriendRequestByUid(uid int, uid_dest int, isSender bool) { 207 | var ( 208 | uid1 int 209 | uid2 int 210 | ) 211 | if isSender { 212 | uid1 = uid 213 | uid2 = uid_dest 214 | } else { 215 | uid1 = uid_dest 216 | uid2 = uid 217 | } 218 | cf.DB.ShouldExec("DELETE FROM #DB#.friendreqs WHERE uid_src=? AND uid_dest=?", uid1, uid2) 219 | } 220 | -------------------------------------------------------------------------------- /src/core/CHalogen.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | _ "embed" 5 | "net/http" 6 | "strconv" 7 | ) 8 | 9 | //func PushMusicNotify(db MySQLConn, conf *GlobalConfig, blob ConfigBlob, songID int) { 10 | // plug:= modules.PluginCore{} 11 | // mus:= CMusic{DB: db, Logger: db.logger, Config: conf, ConfBlob: blob} 12 | // 13 | //} 14 | 15 | //Count stuff 16 | 17 | func CountUsers(db *MySQLConn) int { 18 | var cnt int 19 | db.ShouldQueryRow("SELECT COUNT(*) as cnt FROM #DB#.users").Scan(&cnt) 20 | return cnt 21 | } 22 | 23 | func CountLevels(db *MySQLConn) int { 24 | var cnt int 25 | db.ShouldQueryRow("SELECT COUNT(*) as cnt FROM #DB#.levels").Scan(&cnt) 26 | return cnt 27 | } 28 | 29 | func CountPosts(db *MySQLConn) int { 30 | var cnt int 31 | db.ShouldQueryRow("SELECT COUNT(*) as cnt FROM #DB#.acccomments").Scan(&cnt) 32 | return cnt 33 | } 34 | 35 | func CountComments(db *MySQLConn) int { 36 | var cnt int 37 | db.ShouldQueryRow("SELECT COUNT(*) as cnt FROM #DB#.comments").Scan(&cnt) 38 | return cnt 39 | } 40 | 41 | //Trigger stuff 42 | 43 | func OnRegister(db *MySQLConn, config *GlobalConfig, blob ConfigBlob) bool { 44 | cnt := CountUsers(db) 45 | if blob.ServerConfig.MaxUsers == -1 { 46 | http.Get(config.ApiEndpoint + "?srvid=" + blob.ServerConfig.SrvID + "&key=" + blob.ServerConfig.SrvKey + "&action=stats.users&value=" + strconv.Itoa(cnt+1)) 47 | return true 48 | } 49 | if cnt > blob.ServerConfig.MaxUsers { 50 | return false 51 | } 52 | http.Get(config.ApiEndpoint + "?srvid=" + blob.ServerConfig.SrvID + "&key=" + blob.ServerConfig.SrvKey + "&action=stats.users&value=" + strconv.Itoa(cnt+1)) 53 | return true 54 | } 55 | 56 | func OnLevel(db *MySQLConn, config *GlobalConfig, blob ConfigBlob) bool { 57 | cnt := CountLevels(db) 58 | if blob.ServerConfig.MaxLevels == -1 { 59 | http.Get(config.ApiEndpoint + "?srvid=" + blob.ServerConfig.SrvID + "&key=" + blob.ServerConfig.SrvKey + "&action=stats.levels&value=" + strconv.Itoa(cnt+1)) 60 | return true 61 | } 62 | if cnt > blob.ServerConfig.MaxLevels { 63 | return false 64 | } 65 | http.Get(config.ApiEndpoint + "?srvid=" + blob.ServerConfig.SrvID + "&key=" + blob.ServerConfig.SrvKey + "&action=stats.levels&value=" + strconv.Itoa(cnt+1)) 66 | return true 67 | } 68 | 69 | func OnPost(db *MySQLConn, config *GlobalConfig, blob ConfigBlob) bool { 70 | cnt := CountPosts(db) 71 | if blob.ServerConfig.MaxPosts == -1 { 72 | http.Get(config.ApiEndpoint + "?srvid=" + blob.ServerConfig.SrvID + "&key=" + blob.ServerConfig.SrvKey + "&action=stats.posts&value=" + strconv.Itoa(cnt+1)) 73 | return true 74 | } 75 | if cnt > blob.ServerConfig.MaxPosts { 76 | return false 77 | } 78 | http.Get(config.ApiEndpoint + "?srvid=" + blob.ServerConfig.SrvID + "&key=" + blob.ServerConfig.SrvKey + "&action=stats.posts&value=" + strconv.Itoa(cnt+1)) 79 | return true 80 | } 81 | 82 | func OnComment(db *MySQLConn, config *GlobalConfig, blob ConfigBlob) bool { 83 | cnt := CountComments(db) 84 | if blob.ServerConfig.MaxComments == -1 { 85 | http.Get(config.ApiEndpoint + "?srvid=" + blob.ServerConfig.SrvID + "&key=" + blob.ServerConfig.SrvKey + "&action=stats.comments&value=" + strconv.Itoa(cnt+1)) 86 | return true 87 | } 88 | if cnt > blob.ServerConfig.MaxComments { 89 | return false 90 | } 91 | http.Get(config.ApiEndpoint + "?srvid=" + blob.ServerConfig.SrvID + "&key=" + blob.ServerConfig.SrvKey + "&action=stats.comments&value=" + strconv.Itoa(cnt+1)) 92 | return true 93 | } 94 | -------------------------------------------------------------------------------- /src/core/CLevelList.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import "github.com/jmoiron/sqlx" 4 | 5 | // Gauntlets 6 | //1 Fire 7 | //2 Ice 8 | //3 Poison 9 | //4 Shadow 10 | //5 Lava 11 | //6 Bonus 12 | //7 Chaos 13 | //8 Demon 14 | //9 Time 15 | //10 Crystal 16 | //11 Magic 17 | //12 spike 18 | //13 Monster 19 | //14 Doom 20 | //15 Death 21 | //16 Forest 22 | //17 Rune 23 | //18 Force 24 | //19 Spooky 25 | //20 Dragon 26 | //21 Water 27 | //22 Haunted 28 | //23 Acid 29 | //24 Witch 30 | //25 Power 31 | //26 Potion 32 | //27 Snake 33 | //28 Toxic 34 | //29 Halloween 35 | //30 Treasure 36 | //31 Ghost 37 | //32 Gem 38 | //33 Inferno 39 | //34 Portal 40 | //35 Strange 41 | //36 Fantasy 42 | //37 Christmas 43 | //38 Surprise 44 | //39 Mystery 45 | //40 Cursed 46 | //41 Cyborg 47 | //42 Castle 48 | //43 Grave 49 | //44 Temple 50 | 51 | type CLevelList struct { 52 | ID int `json:"id"` 53 | Name string `json:"name"` 54 | Description string `json:"description"` 55 | Version int `json:"version"` 56 | Difficulty int `json:"difficulty"` 57 | Downloads int `json:"downloads"` 58 | Likes int `json:"likes"` 59 | IsFeatured bool `json:"is_featured"` 60 | UID int `json:"uid"` 61 | Levels string `json:"-"` 62 | Diamonds int `json:"diamonds"` 63 | LevelDiamonds int `json:"level_diamonds"` 64 | UploadDate string `json:"upload_date"` 65 | UpdateDate string `json:"update_date"` 66 | Unlisted int `json:"unlisted"` 67 | 68 | SideloadUname *string `json:"sideload_uname,omitempty"` 69 | DecoupledLevels []string `json:"levels"` 70 | 71 | DB *MySQLConn `json:"-"` 72 | } 73 | 74 | func (cll *CLevelList) Load(id int) { 75 | cll.DB.MustQueryRow("SELECT id,name,description,version,difficulty,downloads,likes,isFeatured,isUnlisted,uid,levels,diamonds,lvlDiamonds,uploadDate,updateDate FROM #DB#.lists WHERE id=?", id). 76 | Scan(&cll.ID, &cll.Name, &cll.Description, &cll.Version, &cll.Difficulty, &cll.Downloads, &cll.Likes, 77 | &cll.IsFeatured, &cll.Unlisted, &cll.UID, &cll.Levels, &cll.Diamonds, &cll.LevelDiamonds, &cll.UploadDate, &cll.UpdateDate) 78 | } 79 | 80 | func (cll *CLevelList) Exists(lid int) bool { 81 | var count int 82 | cll.DB.MustQueryRow("SELECT COUNT(*) FROM #DB#.lists WHERE id=?", lid).Scan(&count) 83 | return count > 0 84 | } 85 | 86 | func (cll *CLevelList) UpdateList() int { 87 | if !cll.CheckParams() { 88 | return -1 89 | } 90 | cll.DB.ShouldExec("UPDATE #DB#.lists SET name=?,description=?,version=?,difficulty=?,downloads=?,likes=?,isFeatured=?,isUnlisted=?,uid=?,levels=?,diamonds=?,lvlDiamonds=?,uploadDate=?,updateDate=NOW() WHERE id=?", 91 | cll.Name, cll.Description, cll.Version, cll.Difficulty, cll.Downloads, cll.Likes, cll.IsFeatured, cll.Unlisted, cll.UID, cll.Levels, cll.Diamonds, cll.LevelDiamonds, cll.UploadDate, cll.ID) 92 | 93 | return cll.ID 94 | } 95 | 96 | func (cll *CLevelList) UploadList() int { 97 | if !cll.CheckParams() { 98 | return -1 99 | } 100 | tx := cll.DB.ShouldPrepareExec("INSERT INTO #DB#.lists (name,description,version,difficulty,isUnlisted,uid,levels) VALUES (?,?,?,?,?,?,?)", 101 | cll.Name, cll.Description, cll.Version, cll.Difficulty, cll.Unlisted, cll.UID, cll.Levels) 102 | id, _ := tx.LastInsertId() 103 | cll.ID = int(id) 104 | return cll.ID 105 | } 106 | 107 | func (cll *CLevelList) CheckParams() bool { 108 | if len(cll.Name) > 32 || len(cll.Description) > 256 || len(cll.Levels) == 0 { 109 | return false 110 | } 111 | return true 112 | } 113 | 114 | func (cll *CLevelList) OnDownloadList() { 115 | cll.DB.ShouldExec("UPDATE #DB#.lists SET downloads=downloads+1 WHERE id=?", cll.ID) 116 | } 117 | 118 | func (cll *CLevelList) LikeList(lid int, uid int, action int) bool { 119 | if IsLiked(ITEMTYPE_LEVEL, uid, lid, cll.DB) { 120 | return false 121 | } 122 | actionv := "+" 123 | actions := "Like" 124 | if action == CLEVEL_ACTION_DISLIKE { 125 | actionv = "-" 126 | actions = "Dislike" 127 | } 128 | cll.DB.ShouldExec("UPDATE #DB#.lists SET likes=likes"+actionv+"1 WHERE id=?", lid) 129 | RegisterAction(ACTION_LIST_LIKE, uid, lid, map[string]string{"type": actions}, cll.DB) 130 | return true 131 | } 132 | 133 | func (cll *CLevelList) DeleteList() { 134 | cll.DB.ShouldExec("DELETE FROM #DB#.lists WHERE id=?", cll.ID) 135 | } 136 | 137 | func (cll *CLevelList) IsOwnedBy(uid int) bool { 138 | cll.Load(cll.ID) 139 | if cll.ID == 0 { 140 | return false 141 | } 142 | return uid == cll.UID 143 | } 144 | 145 | func (cll *CLevelList) LoadBulkSearch(ids []int) []CLevelList { 146 | var res []CLevelList 147 | query := "SELECT id,name,description,version,difficulty,downloads,likes,isFeatured,isUnlisted,#DB#.lists.uid,levels,#DB#.lists.diamonds," + 148 | "lvlDiamonds,uploadDate,updateDate, #DB#.users.uname FROM #DB#.lists LEFT JOIN #DB#.users on #DB#.lists.uid=#DB#.users.uid WHERE id IN(?)" 149 | q, args, _ := sqlx.In(query, ids) 150 | rows := cll.DB.MustQuery(q, args...) 151 | defer rows.Close() 152 | for rows.Next() { 153 | levl := CLevelList{DB: cll.DB} 154 | e := rows.Scan(&levl.ID, &levl.Name, &levl.Description, &levl.Version, &levl.Difficulty, &levl.Downloads, &levl.Likes, 155 | &levl.IsFeatured, &levl.Unlisted, &levl.UID, &levl.Levels, &levl.Diamonds, &levl.LevelDiamonds, &levl.UploadDate, &levl.UpdateDate, 156 | &levl.SideloadUname) 157 | if levl.SideloadUname == nil { 158 | s := "[DELETED]" 159 | levl.SideloadUname = &s 160 | } 161 | if e != nil { 162 | SendMessageDiscord(e.Error()) 163 | } 164 | res = append(res, levl) 165 | } 166 | 167 | return res 168 | } 169 | 170 | // !TEMP 171 | func (cll *CLevelList) Preload() { 172 | cll.DB.ShouldExec(` 173 | CREATE TABLE IF NOT EXISTS #DB#.lists 174 | ( 175 | id int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT, 176 | name varchar(32) NOT NULL DEFAULT 'Unnamed', 177 | description varchar(256) NOT NULL DEFAULT '', 178 | uid int(11) NOT NULL DEFAULT 0, 179 | version tinyint NOT NULL DEFAULT 1, 180 | difficulty tinyint NOT NULL DEFAULT -1, 181 | downloads int NOT NULL DEFAULT 0, 182 | likes int NOT NULL DEFAULT 0, 183 | isFeatured tinyint(1) NOT NULL DEFAULT 0, 184 | isUnlisted tinyint(1) NOT NULL DEFAULT 0, 185 | levels mediumtext NOT NULL DEFAULT '', 186 | diamonds int NOT NULL DEFAULT 0, 187 | lvlDiamonds int NOT NULL DEFAULT 0, 188 | uploadDate DATETIME NOT NULL DEFAULT NOW(), 189 | updateDate DATETIME NOT NULL DEFAULT NOW() 190 | ) 191 | `) 192 | } 193 | -------------------------------------------------------------------------------- /src/core/CLevelListFilter.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "math" 5 | "strconv" 6 | "time" 7 | ) 8 | 9 | const ( 10 | CLEVELLISTFILTER_MOSTLIKED int = 800 11 | CLEVELLISTFILTER_MOSTDOWNLOADED int = 801 12 | CLEVELLISTFILTER_TRENDING int = 802 13 | CLEVELLISTFILTER_LATEST int = 803 14 | CLEVELLISTFILTER_MAGIC int = 804 15 | CLEVELLISTFILTER_AWARDED int = 805 16 | CLEVELLISTFILTER_SENT int = 806 17 | ) 18 | 19 | type CLevelListFilter struct { 20 | DB *MySQLConn 21 | Count int 22 | } 23 | 24 | /* 25 | * --- [PARAMS] Object --- 26 | * + (s) sterm - search term (used with other params to specify exact usage) 27 | * + (s) diff - difficulties array. If it doesn't exist then all diffs | Ex: array(10,20,30) 28 | * + (b) star - if not set then all, else star/nostar 29 | * + (s) followList - who to follow 30 | */ 31 | 32 | // GenerateQueryString generates SQL string out of params 33 | func (filter *CLevelListFilter) GenerateQueryString(params map[string]string) string { 34 | whereq := "" 35 | // Difficulty 36 | if diff, ok := params["diff"]; ok { 37 | diff = QuickComma(diff) 38 | whereq += " AND difficulty IN (" + diff + ")" 39 | } 40 | 41 | //Is starred 42 | if star, ok := params["star"]; ok { 43 | whereq += " AND diamonds" 44 | if star == "0" { 45 | whereq += "=" 46 | } else { 47 | whereq += ">" 48 | } 49 | whereq += "0" 50 | } 51 | 52 | return whereq 53 | } 54 | 55 | // SearchLevels searches Levels with filters given 56 | func (filter *CLevelListFilter) SearchLists(page int, params map[string]string, xtype int) []int { 57 | page = int(math.Abs(float64(page))) * 10 58 | suffix := filter.GenerateQueryString(params) 59 | query := " FROM #DB#.lists WHERE 1=1" //placeholder 60 | orderBy := "" 61 | 62 | switch xtype { 63 | case CLEVELLISTFILTER_MOSTLIKED: 64 | orderBy = "likes DESC, downloads DESC" 65 | case CLEVELLISTFILTER_MOSTDOWNLOADED: 66 | orderBy = "downloads DESC, likes DESC" 67 | case CLEVELLISTFILTER_TRENDING: 68 | date := time.Now().AddDate(0, 0, -7).Format("2006-01-02 15:04:05") 69 | query += " AND uploadDate>'" + date + "'" 70 | orderBy = "likes DESC, downloads DESC" 71 | case CLEVELLISTFILTER_LATEST: 72 | orderBy = "uploadDate DESC, downloads DESC" 73 | case CLEVELLISTFILTER_MAGIC: 74 | orderBy = "uploadDate DESC, downloads DESC" 75 | query += "" // robtop sniffed some coke 76 | case CLEVELLISTFILTER_AWARDED: 77 | orderBy = "uploadDate DESC, downloads DESC" 78 | query += " AND isFeatured>0 AND diamonds>0" 79 | case CLEVELLISTFILTER_SENT: 80 | orderBy = "uploadDate DESC, downloads DESC" 81 | query += " AND isFeatured=0 AND diamonds=0" 82 | default: 83 | query += " AND 1=0" //Because I can 84 | } 85 | sortstr := " ORDER BY " + orderBy + " LIMIT 10 OFFSET " + strconv.Itoa(page) 86 | 87 | var lists []int 88 | 89 | //If we actually search for something 90 | if sterm, ok := params["sterm"]; ok { 91 | // If it's an ID 92 | if _, err := strconv.Atoi(sterm); err == nil { 93 | compq := query + " AND id=?" + suffix 94 | rows := filter.DB.ShouldQuery("SELECT id"+compq+sortstr, sterm) 95 | defer rows.Close() 96 | filter.DB.ShouldQueryRow("SELECT count(*) as cnt"+compq, sterm).Scan(&filter.Count) 97 | for rows.Next() { 98 | var lid int 99 | rows.Scan(&lid) 100 | lists = append(lists, lid) 101 | } 102 | } else { 103 | // But if it's just text we search title 104 | //! To support unlisted2 aka friendList maybe we should use isUnlisted<>1 or "isUnlisted=ANY(0"+",2"+") 105 | compq := query + " AND name LIKE ? AND isUnlisted=0" + suffix 106 | rows := filter.DB.ShouldQuery("SELECT id"+compq+sortstr, "%"+sterm+"%") 107 | defer rows.Close() 108 | filter.DB.ShouldQueryRow("SELECT count(*) as cnt"+compq, "%"+sterm+"%").Scan(&filter.Count) 109 | for rows.Next() { 110 | var lid int 111 | rows.Scan(&lid) 112 | lists = append(lists, lid) 113 | } 114 | } 115 | } else { 116 | // Or if we're just wandering and clicking buttons 117 | compq := query + " AND isUnlisted=0" + suffix 118 | rows := filter.DB.ShouldQuery("SELECT id" + compq + sortstr) 119 | defer rows.Close() 120 | filter.DB.ShouldQueryRow("SELECT count(*) as cnt" + compq).Scan(&filter.Count) 121 | for rows.Next() { 122 | var lid int 123 | rows.Scan(&lid) 124 | lists = append(lists, lid) 125 | } 126 | } 127 | 128 | return lists 129 | } 130 | 131 | // SearchUserLevels searches levels of Followed users or by UID 132 | func (filter *CLevelListFilter) SearchUserLists(page int, params map[string]string, followMode bool) []int { 133 | page = int(math.Abs(float64(page))) * 10 134 | suffix := filter.GenerateQueryString(params) 135 | query := " FROM #DB#.lists WHERE 1=1" //placehodler 136 | sortstr := " ORDER BY downloads DESC LIMIT 10 OFFSET " + strconv.Itoa(page) 137 | 138 | var lists []int 139 | 140 | if sterm, ok := params["sterm"]; ok { 141 | if followMode { 142 | if _, err := strconv.Atoi(sterm); err != nil { 143 | query += " AND isUnlisted=0 AND name LIKE ?" 144 | sterm = "%" + sterm + "%" 145 | } else { 146 | query += " AND id=?" 147 | } 148 | compq := query + " AND uid IN (" + QuickComma(params["followList"]) + ")" + suffix 149 | rows := filter.DB.ShouldQuery("SELECT id"+compq+sortstr, sterm) 150 | defer rows.Close() 151 | filter.DB.ShouldQueryRow("SELECT count(*) as cnt"+compq, sterm).Scan(&filter.Count) 152 | for rows.Next() { 153 | var lid int 154 | rows.Scan(&lid) 155 | lists = append(lists, lid) 156 | } 157 | } else { 158 | if stermi, err := strconv.Atoi(sterm); err == nil { 159 | compq := query + " AND uid=?" + suffix 160 | rows := filter.DB.ShouldQuery("SELECT id"+compq+sortstr, stermi) 161 | defer rows.Close() 162 | filter.DB.ShouldQueryRow("SELECT count(*) as cnt"+compq, stermi).Scan(&filter.Count) 163 | for rows.Next() { 164 | var lid int 165 | rows.Scan(&lid) 166 | lists = append(lists, lid) 167 | } 168 | } 169 | } 170 | } else { 171 | if followMode { 172 | compq := query + " AND isUnlisted=0 AND uid IN (" + QuickComma(params["followList"]) + ")" + suffix 173 | rows := filter.DB.ShouldQuery("SELECT id" + compq + sortstr) 174 | defer rows.Close() 175 | filter.DB.ShouldQueryRow("SELECT count(*) as cnt" + compq).Scan(&filter.Count) 176 | for rows.Next() { 177 | var lid int 178 | rows.Scan(&lid) 179 | lists = append(lists, lid) 180 | } 181 | } else { 182 | compq := query + suffix 183 | rows := filter.DB.ShouldQuery("SELECT id" + compq + sortstr) 184 | defer rows.Close() 185 | filter.DB.ShouldQueryRow("SELECT count(*) as cnt" + compq).Scan(&filter.Count) 186 | for rows.Next() { 187 | var lid int 188 | rows.Scan(&lid) 189 | lists = append(lists, lid) 190 | } 191 | } 192 | } 193 | 194 | return lists 195 | } 196 | -------------------------------------------------------------------------------- /src/core/CMessage.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "slices" 5 | "strconv" 6 | "strings" 7 | "time" 8 | ) 9 | 10 | type CMessage struct { 11 | Id int `json:"id"` 12 | UidSrc int `json:"uid_src"` 13 | UidDest int `json:"uid_dest"` 14 | Subject string `json:"subject"` 15 | Message string `json:"message"` 16 | PostedTime string `json:"posted_time"` 17 | IsNew bool `json:"is_new"` 18 | 19 | DB *MySQLConn `json:"-"` 20 | } 21 | 22 | func (cm *CMessage) Exists(id int) bool { 23 | var cnt int 24 | cm.DB.MustQueryRow("SELECT count(*) as cnt FROM #DB#.messages WHERE id=?", id).Scan(&cnt) 25 | return cnt > 0 26 | } 27 | 28 | func (cm *CMessage) CountMessages(uid int, isNew bool) int { 29 | var cnt int 30 | var postfix string 31 | if isNew { 32 | postfix = " AND isNew=1" 33 | } 34 | cm.DB.MustQueryRow("SELECT count(*) as cnt FROM #DB#.messages WHERE uid_dest=?"+postfix, uid).Scan(&cnt) 35 | return cnt 36 | } 37 | 38 | func (cm *CMessage) LoadMessageById(id int) { 39 | if id > 0 { 40 | cm.Id = id 41 | } 42 | if cm.DB.ShouldQueryRow("SELECT uid_src,uid_dest,subject,body,postedTime,isNew FROM #DB#.messages WHERE id=?", cm.Id).Scan( 43 | &cm.UidSrc, &cm.UidDest, &cm.Subject, &cm.Message, &cm.PostedTime, &cm.IsNew) == nil { 44 | cm.DB.ShouldExec("UPDATE #DB#.messages SET isNew=0 WHERE id=?", cm.Id) 45 | } 46 | } 47 | 48 | func (cm *CMessage) DeleteMessage(uid int) { 49 | cm.DB.ShouldExec("DELETE FROM #DB#.messages WHERE id=? AND (uid_src=? OR uid_dest=?)", cm.Id, uid, uid) 50 | } 51 | 52 | func (cm *CMessage) SendMessageObj() bool { 53 | if len(cm.Subject) > 256 || len(cm.Message) > 1024 { 54 | return false 55 | } 56 | acc := CAccount{DB: cm.DB} 57 | acc.Uid = cm.UidDest 58 | acc.LoadSettings() 59 | if acc.MS == 2 { 60 | return false 61 | } 62 | acc.LoadSocial() 63 | blacklist := strings.Split(acc.Blacklist, ",") 64 | if slices.Contains(blacklist, strconv.Itoa(cm.UidSrc)) { 65 | return false 66 | } 67 | if acc.MS == 1 { 68 | cf := CFriendship{DB: cm.DB} 69 | if !cf.IsAlreadyFriend(cm.UidSrc, cm.UidDest) { 70 | return false 71 | } 72 | } 73 | cm.DB.ShouldExec("INSERT INTO #DB#.messages (uid_src,uid_dest,subject,body,postedTime) VALUES(?,?,?,?,?)", 74 | cm.UidSrc, cm.UidDest, cm.Subject, cm.Message, time.Now().Format("2006-01-02 15:04:05")) 75 | return true 76 | } 77 | 78 | func (cm *CMessage) GetMessageForUid(uid int, page int, sent bool) (int, []map[string]string) { 79 | page *= 10 80 | var cnt int 81 | pf := "uid_dest" 82 | if sent { 83 | pf = "uid_src" 84 | } 85 | cm.DB.MustQueryRow("SELECT count(*) as cnt FROM #DB#.messages WHERE "+pf+"=?", uid).Scan(&cnt) 86 | if cnt == 0 { 87 | return 0, []map[string]string{} 88 | } 89 | rows := cm.DB.ShouldQuery("SELECT id,uid_src,uid_dest,subject,body,postedTime,isNew FROM #DB#.messages WHERE "+pf+"=? ORDER BY id limit 10 OFFSET "+strconv.Itoa(page), uid) 90 | defer rows.Close() 91 | var out []map[string]string 92 | for rows.Next() { 93 | msg := CMessage{} 94 | rows.Scan(&msg.Id, &msg.UidSrc, &msg.UidDest, &msg.Subject, &msg.Message, &msg.PostedTime, &msg.IsNew) 95 | blk := map[string]string{ 96 | "id": strconv.Itoa(msg.Id), 97 | "subject": msg.Subject, 98 | "message": msg.Message, 99 | "isOld": strconv.Itoa(ToInt(!msg.IsNew)), 100 | "date": msg.PostedTime, 101 | } 102 | 103 | uid := msg.UidSrc 104 | if sent { 105 | uid = msg.UidDest 106 | } 107 | acc := CAccount{DB: cm.DB, Uid: uid} 108 | if acc.Exists(uid) { 109 | acc.LoadAuth(CAUTH_UID) 110 | blk["uname"] = acc.Uname 111 | } else { 112 | blk["uname"] = "[DELETED]" 113 | } 114 | blk["uid"] = strconv.Itoa(uid) 115 | if msg.IsNew { 116 | cm.DB.ShouldExec("UPDATE #DB#.messages SET isNew=0 WHERE id=?", msg.Id) 117 | } 118 | out = append(out, blk) 119 | } 120 | return cnt, out 121 | } 122 | -------------------------------------------------------------------------------- /src/core/CMusic.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/jmoiron/sqlx" 8 | "io" 9 | "net/http" 10 | "regexp" 11 | "strconv" 12 | "strings" 13 | ) 14 | 15 | type CMusic struct { 16 | Status string `json:"status"` 17 | Id int `json:"id"` 18 | Name string `json:"name"` 19 | Artist string `json:"artist"` 20 | Size float64 `json:"size"` 21 | Url string `json:"url"` 22 | IsBanned bool `json:"is_banned"` 23 | Downloads int `json:"downloads"` 24 | 25 | DB *MySQLConn `json:"-"` 26 | Logger Logger `json:"-"` 27 | Config *GlobalConfig `json:"-"` 28 | ConfBlob ConfigBlob `json:"-"` 29 | } 30 | 31 | func (mus *CMusic) Exists(id int) bool { 32 | var cnt int 33 | mus.DB.MustQueryRow("SELECT count(*) as cnt FROM #DB#.songs WHERE id=?", id).Scan(&cnt) 34 | return cnt > 0 35 | } 36 | 37 | func (mus *CMusic) RequestNGOuter(id int) bool { 38 | mus.Id = id 39 | resp, err := http.Get(mus.Config.ApiEndpoint + "?srvid=" + mus.ConfBlob.ServerConfig.SrvID + "&key=" + 40 | mus.ConfBlob.ServerConfig.SrvKey + "&action=requestSong&id=" + strconv.Itoa(id)) 41 | if err != nil { 42 | return false 43 | } 44 | rsp, _ := io.ReadAll(resp.Body) 45 | rsp = bytes.ReplaceAll(rsp, []byte("#"), []byte("")) 46 | if err = json.Unmarshal(rsp, mus); err != nil { 47 | fmt.Println(err) 48 | } 49 | 50 | return mus.Status == "ok" 51 | } 52 | 53 | func (mus *CMusic) TransformHalResource() bool { 54 | arn := strings.Split(mus.Url, ":") 55 | if !mus.isArnValid(arn) { 56 | return false 57 | } 58 | resp, err := http.Get(mus.Config.ApiEndpoint + "?srvid=" + mus.ConfBlob.ServerConfig.SrvID + "&key=" + 59 | mus.ConfBlob.ServerConfig.SrvKey + "&action=requestSongARN&type=" + arn[1] + "&id=" + arn[2]) 60 | if err != nil { 61 | return false 62 | } 63 | rsp, _ := io.ReadAll(resp.Body) 64 | rsp = bytes.ReplaceAll(rsp, []byte("#"), []byte("")) 65 | bufArtist := mus.Artist 66 | bufTitle := mus.Name 67 | if err = json.Unmarshal(rsp, mus); err != nil { 68 | fmt.Println(err) 69 | } 70 | mus.Artist = bufArtist 71 | mus.Name = bufTitle 72 | return mus.Status == "ok" 73 | } 74 | 75 | func (mus *CMusic) isArnValid(arn []string) bool { 76 | if len(arn) != 3 { 77 | return false 78 | } 79 | switch arn[1] { 80 | case "ng": 81 | if f, _ := regexp.MatchString(`[0-9]`, arn[2]); !f { 82 | return false 83 | } 84 | case "dz": 85 | if f, _ := regexp.MatchString(`[0-9]`, arn[2]); !f { 86 | return false 87 | } 88 | case "sc": 89 | if f, _ := regexp.MatchString(`(?i)([a-z\d\-\_])+[\\\\\/]([a-z\d\-\_])+$`, arn[2]); !f { 90 | return false 91 | } 92 | case "yt": 93 | if f, _ := regexp.MatchString(`(?i)^([a-z\d\-\_])+$`, arn[2]); !f { 94 | return false 95 | } 96 | case "vk": 97 | if f, _ := regexp.MatchString(`^(\d)+\_(\d)+$`, arn[2]); !f { 98 | return false 99 | } 100 | default: 101 | return false 102 | } 103 | return true 104 | } 105 | 106 | func (mus *CMusic) TransformBulkHalResources(links []string) []CMusic { 107 | var musics []CMusic 108 | var linksValid []string 109 | for _, link := range links { 110 | arn := strings.Split(link, ":") 111 | if !mus.isArnValid(arn) { 112 | continue 113 | } 114 | linksValid = append(linksValid, arn[1]+":"+arn[2]) 115 | } 116 | resp, err := http.Get(mus.Config.ApiEndpoint + "?srvid=" + mus.ConfBlob.ServerConfig.SrvID + "&key=" + 117 | mus.ConfBlob.ServerConfig.SrvKey + "&action=requestSongARNList&ids=" + strings.Join(linksValid, ",")) 118 | if err != nil { 119 | return musics 120 | } 121 | rsp, _ := io.ReadAll(resp.Body) 122 | rsp = bytes.ReplaceAll(rsp, []byte("#"), []byte("")) 123 | if err = json.Unmarshal(rsp, &musics); err != nil { 124 | fmt.Println(err) 125 | } 126 | return musics 127 | } 128 | 129 | func (mus *CMusic) GetSong(id int) bool { 130 | if !mus.ConfBlob.ServerConfig.HalMusic { 131 | return mus.RequestNGOuter(id) 132 | } 133 | if !mus.Exists(id) { 134 | return false 135 | } 136 | mus.DB.MustQueryRow("SELECT id,name,artist,size,url,isBanned,downloads FROM #DB#.songs WHERE id=?", id).Scan( 137 | &mus.Id, &mus.Name, &mus.Artist, &mus.Size, &mus.Url, &mus.IsBanned, &mus.Downloads) 138 | if mus.IsBanned { 139 | return false 140 | } 141 | if len(mus.Url) > 4 && mus.Url[0:4] == "hal:" { 142 | return mus.TransformHalResource() 143 | } 144 | return true 145 | } 146 | 147 | func (mus *CMusic) GetBulkSongs(ids []int) []CMusic { 148 | q, args, _ := sqlx.In("SELECT id,name,artist,size,url,isBanned,downloads FROM #DB#.songs WHERE id IN (?)", ids) 149 | rows := mus.DB.ShouldQuery(q, args...) 150 | defer rows.Close() 151 | var musics []CMusic 152 | var queryMusics []CMusic 153 | var externalQueries []string 154 | for rows.Next() { 155 | var xmus CMusic 156 | rows.Scan(&xmus.Id, &xmus.Name, &xmus.Artist, &xmus.Size, &xmus.Url, &xmus.IsBanned, &xmus.Downloads) 157 | if xmus.IsBanned { 158 | continue 159 | } 160 | if len(xmus.Url) > 4 && xmus.Url[0:4] == "hal:" { 161 | externalQueries = append(externalQueries, xmus.Url) 162 | queryMusics = append(queryMusics, xmus) 163 | } else { 164 | musics = append(musics, xmus) 165 | } 166 | } 167 | if len(externalQueries) > 0 { 168 | deltas := mus.TransformBulkHalResources(externalQueries) 169 | //musics = append(musics, deltas...) 170 | for i := 0; i < len(deltas); i++ { 171 | delta := deltas[i] 172 | delta.Id = queryMusics[i].Id 173 | if delta.Status != "ok" { 174 | musics = append(musics, queryMusics[i]) 175 | } else { 176 | musics = append(musics, delta) 177 | } 178 | } 179 | //for _, delta := range deltas { 180 | // for _, muss := range queryMusics { 181 | // log.Println("Checking", muss.Id, "against", delta.Id) 182 | // if delta.Id == muss.Id { 183 | // delta.Name = muss.Name 184 | // delta.Artist = muss.Artist 185 | // musics = append(musics, delta) 186 | // log.Println("Added", delta.Id) 187 | // } 188 | // } 189 | //} 190 | } 191 | return musics 192 | } 193 | 194 | func (mus *CMusic) UploadSong() int { 195 | c := mus.DB.ShouldPrepareExec("INSERT INTO #DB#.songs (name,artist,size,url) VALUES (?,?,?,?)", mus.Name, mus.Artist, mus.Size, mus.Url) 196 | id, _ := c.LastInsertId() 197 | return int(id) 198 | } 199 | 200 | func (mus *CMusic) BanMusic(id int, ban bool) { 201 | mus.DB.ShouldExec("UPDATE #DB#.songs SET isBanned=? WHERE id=?", ban, id) 202 | } 203 | 204 | func (mus *CMusic) CountDownloads() { 205 | req := mus.DB.MustQuery("SELECT id FROM #DB#.songs") 206 | defer req.Close() 207 | for req.Next() { 208 | var id int 209 | req.Scan(&id) 210 | var cnt int 211 | mus.DB.ShouldQueryRow("SELECT SUM(downloads) FROM #DB#.levels WHERE song_id=?", id).Scan(&cnt) 212 | mus.DB.ShouldExec("UPDATE #DB#.songs SET downloads=? WHERE id=?", cnt, strconv.Itoa(id)) 213 | } 214 | } 215 | 216 | // ! Implement normal API 217 | func (mus *CMusic) GetTopArtists() map[string]string { 218 | return map[string]string{ 219 | "Riot [Monstercat]": "Monstercat", 220 | "Noisestorm": "noisestorm", 221 | "Nitro Fun": "NitroFunOfficial", 222 | "Throttle": "officialThrottle", 223 | "Tokyo Machine": "TokyoMachine", 224 | "BadComputer": "BadComputer", 225 | "Liquid Soul": "liquidsouliboga", 226 | "Xtrullor": "xtrullor", 227 | "Creo": "CreoMusic", 228 | "Dirty Paws": "DirtyPaws", 229 | "Have better candidates? Join FruitSpace Discord": "", 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /src/core/CProtect.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "encoding/json" 5 | "math" 6 | "os" 7 | "strconv" 8 | "strings" 9 | "time" 10 | ) 11 | 12 | type CProtect struct { 13 | DB *MySQLConn 14 | LevelModel ProtectModel 15 | Savepath string 16 | DisableProtection bool 17 | } 18 | 19 | type ProtectModel struct { 20 | MaxStars int 21 | MaxLevelUpload int 22 | PeakLevelUpload int 23 | Stats map[string]int 24 | } 25 | 26 | func (protect *CProtect) LoadModel(config *GlobalConfig, blob ConfigBlob) { 27 | model, err := os.ReadFile(protect.Savepath + "/levelModel.json") 28 | if err != nil { 29 | os.Mkdir(protect.Savepath, 0777) 30 | protect.FillLevelModel() 31 | //req, err := http.Get(config.ApiEndpoint + "?id=" + blob.ServerConfig.SrvID + "&key=" + blob.ServerConfig.SrvKey + "&action=getModel") 32 | //data, err := io.ReadAll(req.Body) 33 | //if err != nil || req.StatusCode != 200 { 34 | // protect.DisableProtection = true 35 | // return 36 | //} 37 | //os.WriteFile(protect.Savepath+"/levelModel.json", data, 0755) 38 | //protect.LoadModel(config, blob) 39 | } 40 | json.Unmarshal(model, &protect.LevelModel) 41 | if protect.LevelModel.MaxLevelUpload == 0 { 42 | protect.LevelModel.MaxLevelUpload = 10 43 | } 44 | } 45 | 46 | func (protect *CProtect) FillLevelModel() { 47 | 48 | //Calculate LevelModel 49 | date := time.Now() 50 | stats := make(map[string]int) 51 | total := 0 52 | for i := 0; i < 7; i++ { 53 | current := strings.Split(date.AddDate(0, 0, -1*i).Format("2006-01-02 15:04:05"), " ")[0] 54 | currentIndex := strings.Split(date.AddDate(0, 0, -1*(i+1)).Format("2006-01-02 15:04:05"), " ")[0] 55 | var count int 56 | protect.DB.ShouldQueryRow("SELECT count(*) as cnt FROM #DB#.actions WHERE type=4 AND date AND date>? AND data LIKE '%Upload%'", 57 | current, currentIndex).Scan(&count) 58 | stats[currentIndex] = count 59 | if count > protect.LevelModel.PeakLevelUpload { 60 | protect.LevelModel.PeakLevelUpload = count 61 | } 62 | total += count 63 | } 64 | if total < 10 { 65 | protect.LevelModel.MaxLevelUpload = 10 66 | } else { 67 | protect.LevelModel.MaxLevelUpload = int(math.Round(float64(total/7))) + protect.LevelModel.PeakLevelUpload 68 | } 69 | 70 | //Calculate total stars allowed 71 | var count2, count1 int 72 | protect.DB.ShouldQueryRow("SELECT SUM(starsGot) as stars FROM #DB#.levels").Scan(&count1) 73 | protect.DB.ShouldQueryRow("SELECT SUM(packStars) as stars FROM #DB#.levelpacks").Scan(&count2) 74 | protect.LevelModel.MaxStars = 200 + count1 + count2 75 | 76 | protect.LevelModel.Stats = stats 77 | //Dump 78 | data, err := json.Marshal(protect.LevelModel) 79 | if err != nil { 80 | data = []byte("{}") 81 | } 82 | protect.DB.logger.Must(os.WriteFile(protect.Savepath+"/levelModel.json", data, 0755)) 83 | } 84 | 85 | func (protect *CProtect) ResetUserLimits() { 86 | protect.DB.ShouldExec("UPDATE #DB#.users SET protect_levelsToday=0") 87 | protect.DB.ShouldExec("UPDATE #DB#.users SET protect_todayStars=stars") 88 | } 89 | 90 | func (protect *CProtect) DetectLevelModel(uid int) bool { 91 | //FIXME 92 | if 1 == 1 { 93 | return true 94 | } 95 | if protect.DisableProtection { 96 | return true 97 | } 98 | var lvlCnt int 99 | protect.DB.ShouldQueryRow("SELECT protect_levelsToday as cnt FROM #DB#.users WHERE uid=?", uid).Scan(&lvlCnt) 100 | if lvlCnt >= protect.LevelModel.MaxLevelUpload { 101 | protect.DB.ShouldExec("UPDATE #DB#.users SET isBanned=2 WHERE uid=?", uid) 102 | RegisterAction(ACTION_BAN_BAN, 0, uid, map[string]string{"type": "Ban:LevelAuto"}, protect.DB) 103 | SendMessageDiscord("[" + protect.Savepath + "] User " + strconv.Itoa(uid) + " has been banned for uploading too many levels (" + strconv.Itoa(lvlCnt) + "/" + strconv.Itoa(protect.LevelModel.MaxLevelUpload) + ") in a day.") 104 | return false 105 | } 106 | protect.DB.ShouldExec("UPDATE #DB#.users SET protect_levelsToday=protect_levelsToday+1 WHERE uid=?", uid) 107 | return true 108 | } 109 | 110 | func (protect *CProtect) DetectStats(uid int, stars int, diamonds int, demons int, coins int, ucoins int) bool { 111 | if protect.DisableProtection { 112 | return true 113 | } 114 | if stars < 0 || diamonds < 0 || demons < 0 || coins < 0 || ucoins < 0 { 115 | protect.DB.ShouldExec("UPDATE #DB#.users SET isBanned=2 WHERE uid=?", uid) 116 | protect.DB.ShouldExec("DELETE FROM #DB#.levels WHERE uid=?", uid) 117 | protect.DB.ShouldExec("DELETE FROM #DB#.actions WHERE type=4 AND uid=?", uid) 118 | RegisterAction(ACTION_BAN_BAN, 0, uid, map[string]string{"type": "Ban:StatsNegative"}, protect.DB) 119 | SendMessageDiscord("User " + strconv.Itoa(uid) + " has been banned for having negative stats.") 120 | return false 121 | } 122 | //FIXME 123 | if 1 == 1 { 124 | return true 125 | } 126 | if protect.LevelModel.MaxStars == 0 { 127 | protect.LevelModel.MaxStars = 200 128 | } 129 | var starCnt int 130 | protect.DB.ShouldQueryRow("SELECT protect_todayStars FROM #DB#.users WHERE uid=?", uid).Scan(&starCnt) 131 | if (stars - starCnt) > protect.LevelModel.MaxStars { 132 | protect.DB.ShouldExec("UPDATE #DB#.users SET isBanned=2 WHERE uid=?", uid) 133 | RegisterAction(ACTION_BAN_BAN, 0, uid, map[string]string{"type": "Ban:StarsLimit"}, protect.DB) 134 | SendMessageDiscord("User " + strconv.Itoa(uid) + " has been banned for having too many stars (" + strconv.Itoa(stars) + "+" + strconv.Itoa(starCnt) + "/" + strconv.Itoa(protect.LevelModel.MaxStars) + ").") 135 | return false 136 | } 137 | return true 138 | } 139 | 140 | func (protect *CProtect) GetMeta(uid int) map[string]int { 141 | meta := make(map[string]int) 142 | var sMeta string 143 | protect.DB.ShouldQueryRow("SELECT protect_meta FROM #DB#.users WHERE uid=?", uid).Scan(&sMeta) 144 | json.Unmarshal([]byte(sMeta), &meta) 145 | return meta 146 | } 147 | 148 | func (protect *CProtect) DetectMessages(uid int) bool { 149 | if protect.DisableProtection { 150 | return true 151 | } 152 | meta := protect.GetMeta(uid) 153 | t := int(time.Now().Unix()) 154 | if t-meta["msg_time"] < 60 { 155 | return false 156 | } 157 | meta["msg_time"] = t 158 | data, _ := json.Marshal(meta) 159 | protect.DB.ShouldExec("UPDATE #DB#.users SET protect_meta=? WHERE uid=?", string(data), uid) 160 | return true 161 | } 162 | 163 | func (protect *CProtect) DetectPosts(uid int) bool { 164 | if protect.DisableProtection { 165 | return true 166 | } 167 | meta := protect.GetMeta(uid) 168 | t := int(time.Now().Unix()) 169 | if t-meta["post_time"] < 60 { 170 | return false 171 | } 172 | meta["post_time"] = t 173 | data, _ := json.Marshal(meta) 174 | protect.DB.ShouldExec("UPDATE #DB#.users SET protect_meta=? WHERE uid=?", string(data), uid) 175 | return true 176 | } 177 | 178 | func (protect *CProtect) DetectComments(uid int) bool { 179 | if protect.DisableProtection { 180 | return true 181 | } 182 | meta := protect.GetMeta(uid) 183 | t := int(time.Now().Unix()) 184 | if t-meta["comm_time"] < 30 { 185 | return false 186 | } 187 | meta["comm_time"] = t 188 | data, _ := json.Marshal(meta) 189 | protect.DB.ShouldExec("UPDATE #DB#.users SET protect_meta=? WHERE uid=?", string(data), uid) 190 | return true 191 | } 192 | 193 | func CheckIPBan(IPAddr string, config ConfigBlob) bool { 194 | return InArray(config.SecurityConfig.BannedIPs, IPAddr) 195 | } 196 | -------------------------------------------------------------------------------- /src/core/CQuests.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "strconv" 7 | "strings" 8 | "time" 9 | ) 10 | 11 | const ( 12 | QUEST_TYPE_DAILY = 0 13 | QUEST_TYPE_WEEKLY = 1 14 | QUEST_TYPE_EVENT = -1 15 | QUEST_TYPE_CHALLENGE = 2 16 | ) 17 | 18 | type CQuests struct { 19 | DB *MySQLConn 20 | } 21 | 22 | func (cq *CQuests) Exists(cType int) bool { 23 | switch cType { 24 | case -1: 25 | cType = 0 26 | case -2: 27 | cType = 1 28 | case -3: 29 | cType = -1 30 | } 31 | xType := "=" + strconv.Itoa(cType) 32 | if cType == 2 { 33 | xType = ">1" 34 | } 35 | var cnt int 36 | cq.DB.MustQueryRow("SELECT count(*) as cnt FROM #DB#.quests WHERE type" + xType).Scan(&cnt) 37 | return cnt > 0 38 | } 39 | 40 | func (cq *CQuests) GetDaily() (id int, lvlId int) { 41 | cq.DB.ShouldQueryRow("SELECT id, lvl_id FROM #DB#.quests WHERE type=0 AND timeExpire