steamutils 1.0.44 → 1.0.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/SteamClient.js +144 -74
- package/package.json +1 -1
- package/utils.js +35 -4
package/SteamClient.js
CHANGED
@@ -95,11 +95,67 @@ function SteamClient({
|
|
95
95
|
|
96
96
|
const events = {
|
97
97
|
loggedOn: [],
|
98
|
+
csgoOnline: [],
|
98
99
|
webSession: [],
|
99
100
|
friendMessage: [],
|
100
101
|
friendTyping: [],
|
101
102
|
}
|
102
103
|
|
104
|
+
const gcCallback = {}
|
105
|
+
|
106
|
+
function pushGCCallback(name, cb, timeout) {
|
107
|
+
if (!gcCallback[name]) {
|
108
|
+
gcCallback[name] = {}
|
109
|
+
}
|
110
|
+
let t = null
|
111
|
+
let id = uuidv4()
|
112
|
+
|
113
|
+
function callback(...arg) {
|
114
|
+
if (t) {
|
115
|
+
clearTimeout(t)
|
116
|
+
}
|
117
|
+
delete gcCallback[name][id]
|
118
|
+
cb(arg)
|
119
|
+
}
|
120
|
+
|
121
|
+
if (timeout) {
|
122
|
+
t = setTimeout(callback, timeout)
|
123
|
+
}
|
124
|
+
gcCallback[name][id] = callback
|
125
|
+
}
|
126
|
+
|
127
|
+
function callGCCallback(name, ...arg) {
|
128
|
+
if (gcCallback[name]) {
|
129
|
+
for (let id in gcCallback[name]) {
|
130
|
+
gcCallback[name][id](arg)
|
131
|
+
}
|
132
|
+
}
|
133
|
+
}
|
134
|
+
|
135
|
+
function callEvent(_events, data) {
|
136
|
+
_events.forEach((e) => {
|
137
|
+
e.callback?.(data)
|
138
|
+
e.timeout && clearTimeout(e.timeout)
|
139
|
+
delete e.timeout
|
140
|
+
if (e.once) {
|
141
|
+
delete e.callback
|
142
|
+
}
|
143
|
+
})
|
144
|
+
_.remove(_events, e => e.once)
|
145
|
+
}
|
146
|
+
|
147
|
+
function onEvent(name, callback, once, timeout) {
|
148
|
+
if (!events[name]) {
|
149
|
+
events[name] = []
|
150
|
+
}
|
151
|
+
const t = timeout ? setTimeout(callback, timeout) : null
|
152
|
+
events[name].push({
|
153
|
+
once,
|
154
|
+
callback,
|
155
|
+
timeout: t,
|
156
|
+
})
|
157
|
+
}
|
158
|
+
|
103
159
|
const intervals = {}
|
104
160
|
|
105
161
|
function doSetInterval(cb, timeout, key) {
|
@@ -113,6 +169,7 @@ function SteamClient({
|
|
113
169
|
}
|
114
170
|
}
|
115
171
|
intervals[key] = setInterval(cb, timeout)
|
172
|
+
return key
|
116
173
|
}
|
117
174
|
|
118
175
|
function doClearIntervals() {
|
@@ -126,6 +183,10 @@ function SteamClient({
|
|
126
183
|
return [steamClient?.accountInfo?.name, steamClient?._logOnDetails?.account_name].filter(Boolean).join(" - ")
|
127
184
|
}
|
128
185
|
|
186
|
+
function log(msg) {
|
187
|
+
console.log(`[${getAccountInfoName()}] ${msg}`)
|
188
|
+
}
|
189
|
+
|
129
190
|
async function getPersonas(steamIDs) {
|
130
191
|
steamIDs = steamIDs.map(steamID => steamID instanceof SteamID ? steamID.getSteamID64() : steamID)
|
131
192
|
const notCachesteamIDs = steamIDs.filter(id => !PersonasCache.some(p => p.id == id))
|
@@ -165,13 +226,12 @@ function SteamClient({
|
|
165
226
|
return cachedPersonas
|
166
227
|
}
|
167
228
|
|
168
|
-
|
229
|
+
function sleep(ms) {
|
169
230
|
return new Promise(resolve => {
|
170
231
|
setTimeout(resolve, ms)
|
171
232
|
})
|
172
233
|
}
|
173
234
|
|
174
|
-
const partySearchCallback = []
|
175
235
|
|
176
236
|
/**
|
177
237
|
* Get a list of lobbies (* = Unsure description could be wrong)
|
@@ -199,33 +259,26 @@ function SteamClient({
|
|
199
259
|
launcher: 0,
|
200
260
|
game_type: game_type === 'Competitive' ? 8 : 10
|
201
261
|
}))
|
202
|
-
|
203
|
-
setTimeout(resolve, timeout)
|
262
|
+
pushGCCallback('partySearch', resolve, timeout)
|
204
263
|
})
|
205
264
|
}
|
206
265
|
|
207
266
|
async function sendHello() {
|
208
|
-
console.log('sendHello', getAccountInfoName())
|
209
267
|
steamClient.sendToGC(appid, Protos.csgo.ECsgoGCMsg.k_EMsgGCCStrike15_v2_MatchmakingClient2GCHello, {}, Buffer.alloc(0))
|
210
268
|
steamClient.sendToGC(appid, Protos.csgo.EGCBaseClientMsg.k_EMsgGCClientHello, {}, Buffer.alloc(0))
|
211
|
-
console.log('sendHello done', getAccountInfoName())
|
212
269
|
}
|
213
270
|
|
214
|
-
const requestCoPlaysCallback = []
|
215
|
-
|
216
271
|
async function requestCoPlays() {
|
217
|
-
console.log('sendMessageAccount_RequestCoPlays')
|
218
272
|
return new Promise(resolve => {
|
219
273
|
steamClient.sendToGC(appid, Protos.csgo.ECsgoGCMsg.k_EMsgGCCStrike15_v2_Account_RequestCoPlays, {}, Buffer.alloc(0))
|
220
|
-
|
221
|
-
setTimeout(resolve, 10000)
|
274
|
+
pushGCCallback('RequestCoPlays', resolve, 30000)
|
222
275
|
})
|
223
276
|
}
|
224
277
|
|
225
278
|
|
226
279
|
function bindEvent() {
|
227
280
|
steamClient.on('disconnected', async (eresult, msg) => {
|
228
|
-
|
281
|
+
log('disconnected', eresult, msg)
|
229
282
|
doClearIntervals()
|
230
283
|
})
|
231
284
|
steamClient.on('error', async (e) => {
|
@@ -248,26 +301,26 @@ function SteamClient({
|
|
248
301
|
errorStr = `Unknown: ${e.eresult}`
|
249
302
|
break
|
250
303
|
}
|
251
|
-
|
304
|
+
log('error', e?.message)
|
252
305
|
doClearIntervals()
|
253
306
|
})
|
254
307
|
|
255
308
|
steamClient.on('webSession', (sessionID, cookies) => {
|
256
309
|
const webSession = {sessionID, cookies};
|
257
310
|
steamClient.webSession = webSession
|
258
|
-
events.webSession
|
311
|
+
callEvent(events.webSession, webSession)
|
259
312
|
})
|
260
313
|
|
261
314
|
steamClient.on('receivedFromGC', async (appid, msgType, payload) => {
|
262
315
|
const key = getECsgoGCMsgKey(msgType)
|
263
316
|
switch (msgType) {
|
264
317
|
case Protos.csgo.EMsg.k_EMsgClientChatInvite: {
|
265
|
-
|
318
|
+
log(payload)
|
266
319
|
break
|
267
320
|
}
|
268
321
|
case Protos.csgo.ECsgoGCMsg.k_EMsgClientMMSJoinLobbyResponse: {
|
269
322
|
let msg = protoDecode(Protos.csgo.CMsgClientMMSJoinLobbyResponse, payload)
|
270
|
-
|
323
|
+
log(msg)
|
271
324
|
break
|
272
325
|
}
|
273
326
|
case Protos.csgo.ECsgoGCMsg.k_EMsgGCCStrike15_v2_Party_Invite: {
|
@@ -276,16 +329,16 @@ function SteamClient({
|
|
276
329
|
const personas = await getPersonas([sid64]);
|
277
330
|
const player_name = personas.find(p => p.id == sid64)?.player_name
|
278
331
|
if (player_name === undefined) {
|
279
|
-
|
332
|
+
log(sid64, personas);
|
280
333
|
}
|
281
|
-
|
334
|
+
log(getAccountInfoName(), player_name, `https://steamcommunity.com/profiles/${sid64}`);
|
282
335
|
// joinLobby(msg.lobbyid, msg.accountid)
|
283
336
|
break
|
284
337
|
}
|
285
338
|
case Protos.csgo.ECsgoGCMsg.k_EMsgGCCStrike15_v2_Account_RequestCoPlays: {
|
286
339
|
const msg = protoDecode(Protos.csgo.CMsgGCCStrike15_v2_Account_RequestCoPlays, payload);
|
287
340
|
const personas = msg.players.map(p => SteamID.fromIndividualAccountID(p.accountid));
|
288
|
-
|
341
|
+
callGCCallback('RequestCoPlays', personas)
|
289
342
|
break
|
290
343
|
}
|
291
344
|
case Protos.csgo.EGCBaseClientMsg.k_EMsgGCClientWelcome: {
|
@@ -307,9 +360,13 @@ function SteamClient({
|
|
307
360
|
}
|
308
361
|
break
|
309
362
|
}
|
363
|
+
default: {
|
364
|
+
log("cache_object.type_id", cache_object.type_id);
|
365
|
+
}
|
310
366
|
}
|
311
367
|
}
|
312
368
|
}
|
369
|
+
callEvent(events.csgoOnline)
|
313
370
|
break
|
314
371
|
}
|
315
372
|
case Protos.csgo.ECsgoGCMsg.k_EMsgGCCStrike15_v2_MatchmakingGC2ClientUpdate: {
|
@@ -461,11 +518,7 @@ function SteamClient({
|
|
461
518
|
}
|
462
519
|
}
|
463
520
|
|
464
|
-
|
465
|
-
let _partySearchCallback = null
|
466
|
-
while ((_partySearchCallback = partySearchCallback.shift())) {
|
467
|
-
_partySearchCallback?.(players)
|
468
|
-
}
|
521
|
+
callGCCallback('partySearch', players)
|
469
522
|
break
|
470
523
|
}
|
471
524
|
case Protos.csgo.ECsgoGCMsg.k_EMsgGCCStrike15_v2_PlayersProfile: {
|
@@ -506,16 +559,13 @@ function SteamClient({
|
|
506
559
|
player_cur_xp: 327682846,
|
507
560
|
player_xp_bonus_flags: 0
|
508
561
|
}]
|
509
|
-
console.log(data);
|
510
562
|
|
511
563
|
const accountid = data?.[0]?.account_id
|
512
|
-
|
513
|
-
delete getPlayersProfileCallback[accountid]
|
514
|
-
cb?.(data?.[0])
|
564
|
+
callGCCallback('PlayersProfile' + accountid, data?.[0])
|
515
565
|
break
|
516
566
|
}
|
517
567
|
default:
|
518
|
-
|
568
|
+
log(`receivedFromGC ${msgType} ${key}`)
|
519
569
|
const results = Object.values(Protos.csgo).map(function (p) {
|
520
570
|
try {
|
521
571
|
return protoDecode(p, payload)
|
@@ -524,7 +574,7 @@ function SteamClient({
|
|
524
574
|
}).filter(function (result) {
|
525
575
|
return result && Object.keys(result).length
|
526
576
|
})
|
527
|
-
|
577
|
+
log(key, results)
|
528
578
|
}
|
529
579
|
})
|
530
580
|
|
@@ -572,38 +622,38 @@ function SteamClient({
|
|
572
622
|
await sleep(1000)
|
573
623
|
}
|
574
624
|
|
575
|
-
|
625
|
+
log(`app ${appid} launched`)
|
576
626
|
await sendHello()
|
577
627
|
|
578
|
-
events.loggedOn
|
628
|
+
callEvent(events.loggedOn)
|
579
629
|
})
|
580
630
|
|
581
631
|
steamClient.on('friendMessage', async (user, message) => {
|
582
632
|
const sid64 = user.getSteamID64();
|
583
633
|
const personas = await getPersonas([sid64]);
|
584
634
|
const player_name = personas[sid64]?.player_name || ''
|
585
|
-
|
635
|
+
log('friendMessage', sid64, message);
|
586
636
|
|
587
|
-
if ([`
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
}))
|
595
|
-
}
|
637
|
+
if ([`Inited you to play a game!`, `Đã mời bạn chơi một trò chơi!`].includes(message)) {
|
638
|
+
callEvent(events.friendMessage, {
|
639
|
+
player_name,
|
640
|
+
message,
|
641
|
+
invite: true,
|
642
|
+
sid64,
|
643
|
+
})
|
596
644
|
} else {
|
597
|
-
events.friendMessage
|
645
|
+
callEvent(events.friendMessage, {
|
598
646
|
message,
|
599
647
|
player_name,
|
600
648
|
sid64,
|
601
|
-
})
|
649
|
+
})
|
602
650
|
}
|
603
651
|
});
|
604
652
|
|
605
653
|
steamClient.on('friendTyping', async function (steamID, message) {
|
606
|
-
events.friendTyping
|
654
|
+
callEvent(events.friendTyping, {
|
655
|
+
steamID, message
|
656
|
+
})
|
607
657
|
});
|
608
658
|
}
|
609
659
|
|
@@ -612,19 +662,30 @@ function SteamClient({
|
|
612
662
|
}
|
613
663
|
|
614
664
|
|
615
|
-
const getPlayersProfileCallback = {};
|
616
|
-
|
617
665
|
function getPlayersProfile(accountid) {
|
618
666
|
steamClient.sendToGC(730, Protos.csgo.ECsgoGCMsg.k_EMsgGCCStrike15_v2_ClientRequestPlayersProfile, {}, protoEncode(Protos.csgo.CMsgGCCStrike15_v2_ClientRequestPlayersProfile, {
|
619
667
|
account_id: accountid, // account_id: new SteamID('76561199184696945').accountid,
|
620
668
|
request_level: 32
|
621
669
|
}))
|
622
670
|
return new Promise(resolve => {
|
623
|
-
|
624
|
-
setTimeout(resolve, 30000)
|
671
|
+
pushGCCallback('PlayersProfile' + accountid, resolve, 30000)
|
625
672
|
})
|
626
673
|
}
|
627
674
|
|
675
|
+
async function checkPlayerPrimeStatus(accountid) {
|
676
|
+
const profile = await getPlayersProfile(accountid)
|
677
|
+
if (!profile) return false
|
678
|
+
|
679
|
+
if (profile.ranking?.account_id) {
|
680
|
+
return true
|
681
|
+
}
|
682
|
+
|
683
|
+
if (profile.player_level || profile.player_cur_xp) {
|
684
|
+
return true
|
685
|
+
}
|
686
|
+
return false
|
687
|
+
}
|
688
|
+
|
628
689
|
async function getNewCookie(cookie) {
|
629
690
|
let response = (await axios.request({
|
630
691
|
url: 'https://store.steampowered.com/', headers: {
|
@@ -669,7 +730,7 @@ function SteamClient({
|
|
669
730
|
return cookie
|
670
731
|
} else {
|
671
732
|
if (tryNewCookie) {
|
672
|
-
|
733
|
+
log('You are not logged in', cookie)
|
673
734
|
return null
|
674
735
|
} else {
|
675
736
|
return await loginWithCookie(await getNewCookie(cookie), true)
|
@@ -679,13 +740,13 @@ function SteamClient({
|
|
679
740
|
|
680
741
|
async function login() {
|
681
742
|
if (cookie) {
|
682
|
-
|
743
|
+
log('login with cookie')
|
683
744
|
const newCookie = await loginWithCookie(cookie)
|
684
745
|
if (newCookie) {
|
685
746
|
cookie = newCookie
|
686
747
|
}
|
687
748
|
} else if (username && password) {
|
688
|
-
|
749
|
+
log(`login with username ${username}`)
|
689
750
|
steamClient.logOn({
|
690
751
|
accountName: username, password: password, rememberPassword: true, machineName: 'Natri',
|
691
752
|
})
|
@@ -699,14 +760,16 @@ function SteamClient({
|
|
699
760
|
|
700
761
|
|
701
762
|
function partyRegister() {
|
702
|
-
_partyRegister(prime)
|
703
763
|
if (prime === null) {
|
704
764
|
_partyRegister(true)
|
765
|
+
_partyRegister(false)
|
766
|
+
} else {
|
767
|
+
_partyRegister(prime)
|
705
768
|
}
|
706
769
|
}
|
707
770
|
|
708
771
|
function _partyRegister(prime) {
|
709
|
-
|
772
|
+
log("partyRegister", prime);
|
710
773
|
steamClient.sendToGC(730, Protos.csgo.ECsgoGCMsg.k_EMsgGCCStrike15_v2_Party_Register, {}, protoEncode(Protos.csgo.CMsgGCCStrike15_v2_Party_Register, {
|
711
774
|
// id : 0,
|
712
775
|
ver: CSGO_VER, apr: prime ? 1 : 0,//prime
|
@@ -733,14 +796,16 @@ function SteamClient({
|
|
733
796
|
}
|
734
797
|
}
|
735
798
|
|
736
|
-
async function autoRequestFreeLicense(
|
799
|
+
async function autoRequestFreeLicense(shouldLog = false, max = 10) {
|
737
800
|
const steamUtils = new SteamUtils(getCookie())
|
738
801
|
|
739
|
-
const
|
802
|
+
const ownedApps = (await steamUtils.getDynamicStoreUserData())?.rgOwnedApps || []
|
803
|
+
const ownedAppsCount = shouldLog ? (ownedApps?.length || 0) : 0
|
740
804
|
let recommendedApps = Math.random() > 0.5 ? _.shuffle((await steamUtils.getDynamicStoreUserData())?.rgRecommendedApps || []) : []
|
741
805
|
if (!recommendedApps?.length) {
|
742
806
|
recommendedApps = (await axios.request({url: 'https://raw.githubusercontent.com/5x/easy-steam-free-packages/master/src/script/packages_db.json'}))?.data?.packages || []
|
743
807
|
}
|
808
|
+
_.remove(recommendedApps, app => ownedApps.includes(app))
|
744
809
|
|
745
810
|
if (max) {
|
746
811
|
recommendedApps.length = Math.min(recommendedApps.length, max)
|
@@ -755,16 +820,16 @@ function SteamClient({
|
|
755
820
|
try {
|
756
821
|
await steamClient.requestFreeLicense(recommendedApp)
|
757
822
|
} catch (e) {
|
758
|
-
|
823
|
+
log(e);
|
759
824
|
}
|
760
825
|
}
|
761
826
|
await sleep(2000)
|
762
827
|
}
|
763
|
-
if (
|
828
|
+
if (shouldLog) {
|
764
829
|
await sleep(20000)
|
765
830
|
const ownedAppsCount2 = (await steamUtils.getDynamicStoreUserData())?.rgOwnedApps?.length || 0
|
766
831
|
const increaseNumber = ownedAppsCount2 - ownedAppsCount;
|
767
|
-
|
832
|
+
log(getAccountInfoName(), `OwnedApps length ${ownedAppsCount2}, increase ${increaseNumber}`)
|
768
833
|
}
|
769
834
|
}
|
770
835
|
|
@@ -782,6 +847,22 @@ function SteamClient({
|
|
782
847
|
steamClient.setPersona(SteamUser.EPersonaState.LookingToPlay)
|
783
848
|
}
|
784
849
|
|
850
|
+
async function gamesPlayed(apps) {
|
851
|
+
steamClient.gamesPlayed(apps);
|
852
|
+
await sleep(2000)
|
853
|
+
steamClient.setPersona(SteamUser.EPersonaState.LookingToPlay)
|
854
|
+
sendHello()
|
855
|
+
// await sleep(10000)
|
856
|
+
// self.steamUser.uploadRichPresence(730, {
|
857
|
+
// status: 'bussssss',
|
858
|
+
// 'game:state': 'lobby',
|
859
|
+
// steam_display: '#display_watch',
|
860
|
+
// currentmap: '#gamemap_de_empire',
|
861
|
+
// connect: '+gcconnectG082AA752',
|
862
|
+
// 'game:mode': 'competitive'
|
863
|
+
// })
|
864
|
+
}
|
865
|
+
|
785
866
|
return {
|
786
867
|
init,
|
787
868
|
partySearch,
|
@@ -795,15 +876,7 @@ function SteamClient({
|
|
795
876
|
getLogOnDetails() {
|
796
877
|
return steamClient?._logOnDetails
|
797
878
|
},
|
798
|
-
onEvent
|
799
|
-
if (!events[name]) {
|
800
|
-
events[name] = []
|
801
|
-
}
|
802
|
-
events[name].push(cb)
|
803
|
-
},
|
804
|
-
onEventLoggedOn(cb) {
|
805
|
-
events.loggedOn.push(cb)
|
806
|
-
},
|
879
|
+
onEvent,
|
807
880
|
setPersona(state, name) {
|
808
881
|
steamClient.setPersona(state, name)
|
809
882
|
},
|
@@ -821,12 +894,9 @@ function SteamClient({
|
|
821
894
|
playCSGO,
|
822
895
|
doSetInterval,
|
823
896
|
doClearIntervals,
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
steamClient.setPersona(SteamUser.EPersonaState.LookingToPlay)
|
828
|
-
sendHello()
|
829
|
-
}
|
897
|
+
gamesPlayed,
|
898
|
+
sendHello,
|
899
|
+
checkPlayerPrimeStatus,
|
830
900
|
}
|
831
901
|
}
|
832
902
|
|
package/package.json
CHANGED
package/utils.js
CHANGED
@@ -34,7 +34,7 @@ const minDate = new Date(0),
|
|
34
34
|
maxDate = new Date(parseInt('ffffffff', 16) * 1000)
|
35
35
|
|
36
36
|
export function objectIdFromDate(date) {
|
37
|
-
if(date < minDate || date > maxDate) {
|
37
|
+
if (date < minDate || date > maxDate) {
|
38
38
|
return 'Error: date must be between ' + minDate.getFullYear() + ' and ' + maxDate.getFullYear()
|
39
39
|
}
|
40
40
|
var pad = '00000000'
|
@@ -56,13 +56,13 @@ export function console_log(...args) {
|
|
56
56
|
}
|
57
57
|
|
58
58
|
export function removeSpaceKeys(object) {//mutate object
|
59
|
-
if(!object || Array.isArray(object)) {
|
59
|
+
if (!object || Array.isArray(object)) {
|
60
60
|
return object
|
61
61
|
}
|
62
62
|
|
63
63
|
Object.entries(object).forEach(([key, value]) => {
|
64
64
|
const newKey = key.replaceAll(/[^a-zA-Z0-9]/gi, '_')
|
65
|
-
if(newKey !== key) {
|
65
|
+
if (newKey !== key) {
|
66
66
|
delete object[key]
|
67
67
|
object[newKey] = value
|
68
68
|
}
|
@@ -71,7 +71,7 @@ export function removeSpaceKeys(object) {//mutate object
|
|
71
71
|
}
|
72
72
|
|
73
73
|
export function getCleanObject(object) {//like removeSpaceKeys but not mutate object
|
74
|
-
if(!object || Array.isArray(object)) {
|
74
|
+
if (!object || Array.isArray(object)) {
|
75
75
|
return object
|
76
76
|
}
|
77
77
|
|
@@ -98,3 +98,34 @@ export function JSON_stringify(data) {
|
|
98
98
|
return null
|
99
99
|
}
|
100
100
|
}
|
101
|
+
|
102
|
+
|
103
|
+
export async function throttle(fn, delay) {
|
104
|
+
let canFire = true
|
105
|
+
let queue = []
|
106
|
+
|
107
|
+
async function pop() {
|
108
|
+
if (queue.length < 1) return
|
109
|
+
|
110
|
+
const [that, args] = queue.pop()
|
111
|
+
await fn.apply(that, args)
|
112
|
+
canFire = false
|
113
|
+
setTimeout(async () => {
|
114
|
+
canFire = true
|
115
|
+
await pop()
|
116
|
+
}, delay)
|
117
|
+
}
|
118
|
+
|
119
|
+
async function push() {
|
120
|
+
queue.push([this, arguments])
|
121
|
+
if (canFire) {
|
122
|
+
await pop()
|
123
|
+
}
|
124
|
+
}
|
125
|
+
|
126
|
+
push.cancel = () => {
|
127
|
+
queue = []
|
128
|
+
}
|
129
|
+
|
130
|
+
return push
|
131
|
+
}
|