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