· 8 years ago · Jan 12, 2018, 08:50 PM
1
2local sql = {}
3sql.Connected = false
4sql.Queries = {}
5
6file.CreateDir("autodonate")
7local function log(text, silent)
8 text = text.."\n"
9 if !silent then Msg("[autodonate] "..text) end
10 file.Append(os.date("autodonate/%d-%m-%y.txt"), os.date("[%c] ")..text)
11end
12
13function sql:Log( text )
14 print( "[autodonate MySQL] " .. text )
15end
16
17function sql:Initialize()
18 require( "mysqloo" )
19
20 sql.Database = mysqloo.connect( auth.Host, auth.Username, auth.Password, auth.Database, auth.Port )
21
22 sql.Database.onConnected = function( )
23 self.Connected = true
24
25 self:Query( "CREATE TABLE IF NOT EXISTS ad_pdata ( infoid VARCHAR( 32 ) NOT NULL, value BLOB NULL, PRIMARY KEY ( infoid ) );" )
26 --for _, v in ipairs( sql.Queries ) do
27 while ( #sql.Queries > 0 ) do
28 local query = table.remove( sql.Queries, 1 )
29 self:Query( query[ 1 ], query[ 2 ], unpack( query[ 3 ] ) )
30 end
31
32 self:Log( "Connection succesful!" )
33 end
34
35 sql.Database.onConnectionFailed = function( db, err )
36 sql:Log( "Connection failed: " .. err )
37
38 self.Connected = false
39
40 timer.Simple( 10, function()
41 db:connect()
42 end )
43 end
44
45 self:Log( "Connecting to database..." )
46
47 sql.Database:connect()
48end
49
50function sql:Query( sql, callback, ... )
51 if ( self.Database and self.Connected ) then
52 local args = {}
53 for _, val in ipairs( { ... } ) do
54 val = tostring( val )
55 val = self.Database:escape( val )
56
57 table.insert( args, val )
58 end
59
60 local formatted = string.format( sql, unpack( args ) )
61 local query = self.Database:query( formatted )
62 query.onSuccess = function( _, data )
63 if ( callback ) then
64 callback( data )
65 end
66 end
67
68 query.onError = function( _, err )
69 if ( self.Database:status() == mysqloo.DATABASE_NOT_CONNECTED ) then
70 table.insert( self.Queries, { formatted, callback, {} } )
71 self.Database:connect()
72
73 self.Connected = false
74
75 self:Log( "Lost connection to server, reconnecting..." )
76 else
77 self:Log( "Query failed: " .. err )
78 end
79 end
80
81 query:start()
82 else
83 self:Log( "Query attempted while disconnected." )
84 table.insert( self.Queries, { sql, callback, { ... } } )
85 end
86end
87
88function sql:GetPData(steamid, callback)
89 if !isstring(steamid) then steamid = steamid:SteamID64() end
90 self:Query( "SELECT value FROM ad_pdata WHERE infoid = '%s' LIMIT 1", callback, steamid )
91end
92
93local function retryRemove(steamid)
94 sql:GetPData(steamid, function(a)
95 if a or a[1] or a[1].value then
96 log("БД не удалила значение, пробуем ещё раз через пол Ñекунды", true)
97 timer.Simple(0.5, function()
98 sql:RemovePData(steamid)
99 retryRemove(steamid)
100 end)
101 end
102 end)
103end
104
105function sql:RemovePData( steamid )
106 if !isstring(steamid) then steamid = steamid:SteamID64() end
107 self:Query( "DELETE FROM ad_pdata WHERE infoid = '%s'", nil, steamid )
108 timer.Simple(0.1, function() retryRemove(steamid) end)
109end
110
111local function retrySet(steamid, value)
112 sql:GetPData(steamid, function(a)
113 if !a or !a[1] or !a[1].value or a[1].value != value then
114 timer.Simple(0.5, function()
115 sql:SetPData(steamid, value)
116 retrySet(steamid, value)
117 end)
118 end
119 end)
120end
121
122function sql:SetPData( steamid, value )
123 if !isstring(steamid) then steamid = steamid:SteamID64() end
124 self:Query( "REPLACE INTO ad_pdata ( infoid, value ) VALUES ( '%s', '%s' )", nil, steamid, value )
125 timer.Simple(0.1, function() retrySet(steamid, value) end)
126end
127
128
129sql:Initialize()
130
131ad.sql = sql
132
133------------------------------------------------------------
134
135if !socket then require("socket.core") end
136local md5 = include("ad_md5.lua")
137
138local sock = ad.socket
139if !sock then
140 sock = socket.tcp()
141 assert(sock:bind(ip, port))
142 sock:settimeout(0)
143 sock:listen(max)
144 ad.socket = sock
145end
146
147local red = Color(255, 0, 0)
148local function onError(reason, sid, code)
149 reason = "\n============AUTODONATE ERROR============\n "..reason.."\n========================================\n SteamID: "..sid.." || code: "..code.."\n========================================\n\n"
150 log(reason, true)
151 MsgC(red, reason, color_white)
152end
153
154
155local invKeys = (file.Read("autodonate/invkeys.txt") or ""):Split("\n")
156
157local function processData(statuscode, body, sid, code)
158 if statuscode != 200 then return onError("код ответа равен не 200 ("..statuscode..")", sid, code) end
159 body = util.JSONToTable(body)
160 if !body then return onError("ответ пришел не в JSON формате", sid, code) end
161 if body.retval != "0" then return onError("Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð½Ðµ выполнен!\n код: "..body.retval.."\n опиÑание ошибки: "..body.retdesc, sid, code) end
162 if util.SteamIDFrom64(sid) == "STEAM_0:0:0" then return onError("SteamID неверен!", sid, code) end
163
164 body.inv = tostring(body.inv)
165
166 for k in pairs(invKeys) do
167 if tostring(k) == body.inv then return log("Ignoring duped inv key "..k) end
168 end
169
170 local val = math.floor(tonumber(body.cnt_goods:Replace(",", ".")))
171 if !val then return onError("JOPA ERROR CNT_GOODS IS NIL:\n"..util.TableToJSON(body, true), sid, code) end
172 log(sid.." byed "..val.."p")
173
174 local ply = player.GetBySteamID64(sid)
175 if ply then
176 ad.add(ply, val, true)
177 else
178 log(sid.." is offline, adding "..val.."p to MySQL")
179 sql:GetPData(sid, function(a)
180 local data = util.JSONToTable(a and a[1] and a[1].value or "{\"balance\":0}")
181 data.balance = data.balance+val
182 sql:SetPData(sid, util.TableToJSON(data))
183 end)
184 end
185
186 invKeys[body.inv] = true
187 file.Append("autodonate/invkeys.txt", body.inv.."\n")
188 log("writed inv key "..body.inv)
189end
190
191
192local function checker()
193 local client = sock:accept()
194 if client then
195 local req = client:receive()
196 client:settimeout(2)
197
198 if req and isstring(req) and req:StartWith("GET /?uniquecode=") or req:StartWith("POST /?uniquecode=") then
199 req = req:sub(req:StartWith("GET /?uniquecode=") and 18 or 19):Split("&"..ad.config.param.."=")
200 if req and istable(req) and !req[1] or !req[2] then
201 log("wrong request: "..tostring(req[1]).." sid: "..tostring(req[2]))
202 client:send("HTTP/1.0 301 Moved Permanently\r\nLocation: "..redirect.."\r\n\r\n")
203 client:close()
204 return
205 end
206
207 local code, sid = req[1], req[2]:sub(0, req[2]:find(" ")-1)
208 log("sending POST: code: "..code.." sid: "..sid)
209 HTTP{
210 method = "POST",
211 url = "http://shop.digiseller.ru/xml/check_unique_code.asp",
212 type = "application/json",
213 body = [[{"id_seller":"]]..id_seller..[[","unique_code":"]]..code..[[","sign":"]]..md5.sumhexa(id_seller..":"..code..":"..password)..[["}]],
214 failed = function(reason) onError(reason, sid, code) end,
215 success = function(status, body) processData(status, body, sid, code) end
216 }
217
218 elseif isstring(req) then
219 log("Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð½Ðµ по донату: "..req, true)
220 elseif tostring(req) then
221 log("Ñтранный Ð·Ð°Ð¿Ñ€Ð¾Ñ "..tostring(req), true)
222 end
223
224 client:send("HTTP/1.0 301 Moved Permanently\r\nLocation: "..redirect.."\r\n\r\n")
225 client:close()
226 end
227end
228
229timer.Create("checkKeys", 1, 0, function()
230 local ok, ret = pcall(checker)
231 if !ok then onError(ret, "SOCKET ERROR", "SOCKET ERROR") end
232end)
233
234concommand.Add("ad_retry",function(ply, _, arg)
235 if IsValid(ply) then return end
236 local code, sid = arg[1], arg[2]
237 if !code or !sid then return print("ad_retry wrong arguments") end
238 log("sending POST: code: "..code.." sid: "..sid)
239 HTTP{
240 method = "POST",
241 url = "http://shop.digiseller.ru/xml/check_unique_code.asp",
242 type = "application/json",
243 body = [[{"id_seller":"]]..id_seller..[[","unique_code":"]]..code..[[","sign":"]]..md5.sumhexa(id_seller..":"..code..":"..password)..[["}]],
244 failed = function(reason) onError(reason, sid, code) end,
245 success = function(status, body) processData(status, body, sid, code) end
246 }
247end)
248
249------------------------------------------------------
250local sip = "autodonate_error_what"
251timer.Simple(1, function()
252 sip = game.GetIPAddress()
253 ad.sip = sip
254 if sip == "0.0.0.0:27015" then
255 http.Fetch("https://api.ipify.org", function(ip)
256 sip = ip..":27015"
257 ad.sip = sip
258 end)
259 end
260end)
261
262util.AddNetworkString("autodonate")
263
264local function update(ply)
265 local tbl = {balance = ply.ad.balance}
266 if ply.ad[sip] then
267 tbl.group = ply.ad[sip].group
268 tbl.group_expires = ply.ad[sip].group_expires
269 end
270 net.Start("autodonate")
271 net.WriteTable(tbl)
272 net.WriteBool(false)
273 net.Send(ply)
274end
275
276function ad.save(ply, upd)
277 sql:SetPData(ply, util.TableToJSON(ply.ad))
278 if upd then update(ply) end
279end
280
281function ad.set(ply, value)
282 ply = isentity(ply) and ply or false
283 assert(ply, "игрок не на Ñервере!")
284 log("изменение Ñчёта "..ply:SteamID64()..", было "..ply.ad.balance.."p, Ñтало: "..value, true)
285 ply.ad.balance = value
286 ad.save(ply, true)
287end
288
289
290function ad.add(ply, amount, msg)
291 ply = isentity(ply) and ply or false
292 assert(ply, "игрок не на Ñервере!")
293 ad.set(ply, ply.ad.balance+amount)
294 log(ply:SteamID64().." пополнил Ñчет на "..amount.."Ñ€, Ñчет равен "..ply.ad.balance.."p", true)
295 if msg then ad.msg(ply, "Ð’Ñ‹ пополнили Ñчет на "..amount.."Ñ€") end
296end
297
298function ad.msg(ply, msg, err)
299 local tbl = {balance = ply.ad.balance}
300 if ply.ad[sip] then
301 tbl.group = ply.ad[sip].group
302 tbl.group_expires = ply.ad[sip].group_expires
303 end
304 net.Start("autodonate")
305 net.WriteTable(tbl)
306 net.WriteBool(true)
307 net.WriteString(msg)
308 net.WriteBool(err)
309 net.Send(ply)
310end
311
312function ad.removeGroup(sid)
313 if !isstring(sid) then sid = sid:SteamID64() end
314 sql:GetPData(sid, function(a)
315 local data = util.JSONToTable(a and a[1] and a[1].value or "{\"balance\":0}")
316 data[sip] = nil
317 sql:SetPData(sid, util.TableToJSON(data))
318 end)
319end
320
321local function timedGroup(ply, group)
322 ulx.adduser(NULL, ply, group)
323 ply.ad[sip] = {}
324 ply.ad[sip].group = group
325 ply.ad[sip].group_expires = os.time()+2591999
326end
327
328
329hook.Add("PlayerInitialSpawn", "autodonate", function(ply)
330 if !ply:IsBot() then
331 sql:GetPData(ply, function(a)
332 ply.ad = util.JSONToTable(a and a[1] and a[1].value or "{\"balance\":0}")
333 update(ply)
334 end)
335 end
336end)
337
338timer.Create("ad.checkGroups", 1, 0, function()
339 for _, ply in pairs(player.GetAll()) do
340 if !ply:IsBot() and ply.ad and ply.ad[sip] and ply.ad[sip].group:gsub("_inf", "") != ply:GetUserGroup() then
341 ply.ad[sip] = nil
342 update(ply)
343 elseif !ply:IsBot() and ply.ad and ply.ad[sip] and ply.ad[sip].group_expires and ply.ad[sip].group_expires-os.time() <= 0 then
344 if ply.ad[sip].group == ply:GetUserGroup() then
345 log(ply:SteamID64().." был ÑнÑÑ‚ Ñ Ð¿Ñ€Ð¸Ð²ÐµÐ»ÐµÐ½Ð¸Ð¸ "..ply.ad[sip].group, true)
346 ulx.adduser(NULL, ply, "user")
347 ad.msg(ply, "МеÑÑц прошёл - донат группа ÑнÑта")
348 end
349 ply.ad[sip] = nil
350 if ply.ad.balance == 0 then
351 sql:RemovePData(ply)
352 else ad.save(ply) end
353 update(ply)
354 end
355 end
356end)
357
358concommand.Add("autodonate_buy", function(ply, _, arg)
359 arg = arg[1]
360 local try = ad.config.items[arg]
361 if !try or ply.ad.balance < try.p or (ply.ad[sip] and ply.ad[sip].group == try) then return ply:Kick("siebalsa") end
362 if os.time()-(ply.ad_lastByed or 0) < 5 then return ad.msg(ply, "Подождите 5 Ñекунд перед покупкой Ñледующего предмета", true) end
363
364 log(ply:SteamID64().." купил "..arg.." за "..try.p.."p", true)
365 if try.func then
366 local err, msg = try.func(ply)
367 ad.msg(ply, msg)
368 if err then
369 ply.ad_lastByed = os.time()
370 return
371 end
372 else
373 timedGroup(ply, arg)
374 ad.msg(ply, "Ð’Ñ‹ уÑпешно купили привилегию "..try[1].." за "..try.p.."Ñ€")
375 end
376 ad.set(ply, ply.ad.balance-try.p)
377 ply.ad_lastByed = os.time()
378end)