steamutils 1.0.0

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/index.js ADDED
@@ -0,0 +1,4065 @@
1
+ import cheerio from 'cheerio'
2
+ import {randomBytes} from 'crypto'
3
+ import sha256 from 'crypto-js/sha256.js'
4
+ import _ from 'lodash'
5
+ import moment from 'moment'
6
+ import {hex2b64, Key as RSA} from 'node-bignumber'
7
+ import SteamID from 'steamid'
8
+ import URL from 'url'
9
+ import Url from 'url-parse'
10
+ import xml2js from 'xml2js'
11
+ import {JSON_parse, JSON_stringify, console_log, getCleanObject, removeSpaceKeys, sleep} from './utils.js'
12
+ import './string.js'
13
+ import {Header, request} from './axios.js'
14
+ import {getTableHasHeaders, table2json} from './cheerio.js'
15
+ import {getJSObjectFronXML} from './xml2json.js'
16
+
17
+ const MAX_RETRY = 10
18
+ export const MatchHistoryType = {
19
+ matchhistoryscrimmage: 'matchhistoryscrimmage',
20
+ matchhistorycompetitive: 'matchhistorycompetitive',
21
+ }
22
+
23
+ export const MatchWinLose = {
24
+ Win: 'Win',
25
+ Lose: 'Lose',
26
+ Tie: 'Tie',
27
+ }
28
+ export const AppID_CSGO = 730
29
+ const SteamcommunityURL = 'https://steamcommunity.com'
30
+
31
+ const FRIEND_CODE_REPLACEMENTS = ['b', 'c', 'd', 'f', 'g', 'h', 'j', 'k', 'm', 'n', 'p', 'q', 'r', 't', 'v', 'w']
32
+
33
+ const EPrivacyState = {
34
+ Private: 'Private',
35
+ FriendsOnly: 'FriendsOnly',
36
+ Public: 'Public'
37
+ }
38
+ const EActivityType = Object.freeze({
39
+ newFriend: 'newFriend',
40
+ playedFirstTime: 'playedFirstTime',
41
+ achieved: 'achieved',
42
+ added2wishlist: 'added2wishlist',
43
+ following: 'following',
44
+ joined: 'joined',
45
+ own: 'own',
46
+ })
47
+
48
+ const PrivacySettings = {
49
+ profile: 'Public',
50
+ inventory: 'Public',
51
+ inventoryGifts: 'Private',
52
+ gameDetails: 'Public',
53
+ playtime: 'Public',
54
+ friendsList: 'Public',
55
+ comment: 'Private'
56
+ }
57
+
58
+ const EFriendRelationship = {
59
+ None: 0,
60
+ Blocked: 1,
61
+ RequestRecipient: 2,
62
+ Friend: 3,
63
+ RequestInitiator: 4,
64
+ Ignored: 5,
65
+ IgnoredFriend: 6,
66
+ SuggestedFriend: 7, // removed "was used by the original implementation of the facebook linking feature; but now unused."
67
+
68
+ // Value-to-name mapping for convenience
69
+ 0: 'None',
70
+ 1: 'Blocked',
71
+ 2: 'RequestRecipient',
72
+ 3: 'Friend',
73
+ 4: 'RequestInitiator',
74
+ 5: 'Ignored',
75
+ 6: 'IgnoredFriend',
76
+ 7: 'SuggestedFriend',
77
+ }
78
+
79
+ const EGroupRank = Object.freeze({
80
+ Owner: ['Group Owner', 'Ιδιοκτήτης ομάδας', 'Groepseigenaar', 'Gruppeeier', 'Gruppeejer', 'Gruppenbesitzer', 'Владелец группы', 'Proprietarul grupului', 'Chủ sở hữu nhóm', 'Собственик на групата', 'Gruppägare', 'Propietario del grupo', 'Власник групи', 'Proprietario del gruppo', 'グループのオーナー', '组所有者', '群組擁有者', 'Vlastník skupiny', 'เจ้าของกลุ่ม', 'Grubun Sahibi', 'Dono do grupo', 'Proprietário do grupo', 'Właściciel grupy', 'Propriétaire du groupe', 'Omistaja', '그룹 소유자', 'Csoporttulajdonos'],
81
+ Officer: ['Group Officer', 'Διαχειριστής ομάδας', 'Groepsbeheerder', 'Gruppeoffiser', 'Gruppeofficer', 'Gruppenadministrator', 'Офицер группы', 'Ofițer de grup', 'Ủy viên nhóm', 'Офицер на групата', 'Gruppofficer', 'Oficial del grupo', 'Адміністратор групи', 'Amministratore del gruppo', 'グループの上級メンバー', '组官员', '群組幹部', 'Správce skupiny', 'เจ้าหน้าที่กลุ่ม', 'Grup Yetkilisi', 'Administrador do grupo', 'Oficial de Grupo', 'Oficer grupy', 'Responsable du groupe', 'Ylläpitäjä', '그룹 임원', 'Csoport-elöljáró'],
82
+ Moderator: ['Group Moderator', 'Συντονιστής ομάδας', 'Groepsmoderator', 'Gruppemoderator', 'Gruppenmoderator', 'Модератор группы', 'Moderator de grup', 'Điều hành viên nhóm', 'Модератор на групата', 'Gruppmoderator', 'Moderador del grupo', 'Модератор групи', 'Moderatore del gruppo', 'グループのモデレーター', '组版主', '群組板務', 'Moderátor skupiny', 'ผู้ช่วยดูแลกลุ่ม', 'Grup Moderatörü', 'Moderador do grupo', 'Moderador de Grupo', 'Moderator grupy', 'Modération du groupe', 'Valvoja', '그룹 모더레이터', 'Csoportmoderátor'],
83
+ })
84
+
85
+ const ELanguage = Object.freeze({
86
+ greek: 'greek',
87
+ dutch: 'dutch',
88
+ norwegian: 'norwegian',
89
+ danish: 'danish',
90
+ german: 'german',
91
+ russian: 'russian',
92
+ romanian: 'romanian',
93
+ vietnamese: 'vietnamese',
94
+ bulgarian: 'bulgarian',
95
+ swedish: 'swedish',
96
+ spanish: 'spanish',
97
+ latam: 'latam',
98
+ english: 'english',
99
+ ukrainian: 'ukrainian',
100
+ italian: 'italian',
101
+ japanese: 'japanese',
102
+ schinese: 'schinese',
103
+ tchinese: 'tchinese',
104
+ czech: 'czech',
105
+ thai: 'thai',
106
+ turkish: 'turkish',
107
+ brazilian: 'brazilian',
108
+ portuguese: 'portuguese',
109
+ polish: 'polish',
110
+ french: 'french',
111
+ finnish: 'finnish',
112
+ koreana: 'koreana',
113
+ hungarian: 'hungarian'
114
+ })
115
+
116
+ export const NotYetSetupProfileTextList = Object.freeze(['This user has not yet set up their Steam Community profile.If you know this person, encourage them to set up their profile and join in the gaming!', 'Αυτός ο χρήστης δεν έχει ρυθμίσει το προφίλ του στην Κοινότητα του Steam.Αν τον γνωρίζετε, ενθαρρύνετέ τον να στήσει το προφίλ του και να συμμετάσχει στο παιχνίδι!', 'Deze gebruiker heeft nog geen Steam-communityprofiel.Als je deze persoon kent, moedig hem of haar dan aan om een profiel te maken en deel te nemen aan de gamingcommunity!', 'Denne brukeren har ikke satt opp Steam-samfunnsprofilen.Hvis du kjenner denne personen, kan du oppmuntre vedkommende til å sette opp profilen og bli med på spillingen!', 'Denne bruger har endnu ikke tilpasset sin profil på Steam-fællesskabet.Hvis du kender vedkommende, så få dem til at tilpasse deres profil og deltage i spilfællesskabet!', 'Diese Person hat ihr Steam-Communityprofil noch nicht eingerichtet.Wenn Sie diese Person kennen, fordern Sie sie doch auf, ihr Profil einzurichten und an der Spielecommunity teilzunehmen!', 'Этот пользователь ещё не настроил свой профиль в сообществе Steam.Если вы знакомы, посоветуйте ему настроить профиль, чтобы играть вместе!', 'Acest utilizator nu și-a configurat încă profilul din comunitatea Steam.Dacă cunoști această persoană, încurajeaz-o să-și configureze un profil și să se alăture comunității!', 'Người dùng này vẫn chưa thiết lập hồ sơ cộng đồng Steam.Nếu là người bạn quen, hãy khuyến khích người ấy thiết lập hồ sơ và cùng chơi!', 'Този потребител все още не е настроил своя профил за Steam общността.Ако познавате това лице, насърчете го да установи своя профил и да се присъедини към игралното преживяване!', 'Den här användaren har inte lagt upp sin gemenskapsprofil på Steam.Uppmuntra hen att lägga upp en profil och börja spela, om du känner personen!', 'Este/a usuario/a aún no ha configurado su perfil de la Comunidad Steam.Si conoces a esta persona, anímala a configurar su perfil para unirse a la fiesta.', 'Este usuario aún no ha configurado su perfil de la Comunidad Steam.Si lo conoces, anímale a configurar su perfil para unirse a la fiesta.', 'This user has not yet set up their Steam Community profile.If you know this person, encourage them to set up their profile and join in the gaming!', 'Цей користувач ще не налаштував свій профіль спільноти Steam. Якщо ви знайомі, то порадьте йому/їй налаштувати свій профіль і почати грати!', 'Questo utente non ha ancora configurato il suo profilo della Comunità di Steam.Se lo conosci, invitalo a configurare il suo profilo e unirsi al divertimento!', 'このユーザーはまだSteamコミュニティのプロフィールを作成していません。このユーザーを知っている場合は、プロフィールを作成してゲーマーとして参加するよう伝えてみましょう。', '此用户尚未设置自己的 Steam 社区个人资料。如果您认识此人,请鼓励对方设置个人资料并加入到游戏当中!', '這位使用者尚未設定 Steam 社群個人檔案。如果您認識對方,請鼓勵他設定個人檔案,並加入遊戲世界!', 'Tento uživatel si zatím nenastavil svůj profil v komunitě služby Steam.Pokud tohoto uživatele znáte, napište mu, aby si nastavil svůj profil a stal se tak opravdovým členem komunity!', 'ผู้ใช้นี้ยังไม่ได้ตั้งค่าโปรไฟล์ชุมชน Steamหากคุณรู้จักผู้ใช้นี้ บอกให้เขาตั้งค่าโปรไฟล์ แล้วมาเล่นเกมด้วยกัน!', 'Bu kullanıcı Steam Topluluğu profilini henüz oluşturmadı.Eğer bu kullanıcıyı tanıyorsanız, profilini oluşturmasını ve oyun dünyasına katılmasını sağlayın!', 'Esse(a) usuário(a) ainda não criou um perfil da Comunidade Steam.Caso o(a) conheça, encoraje-o(a) a criá-lo e jogar com você!', 'Esta pessoa ainda não configurou o seu perfil na Comunidade Steam.Se conheces esta pessoa, encoraja-a a configurar um perfil e a jogar contigo!', 'Ten użytkownik jeszcze nie uzupełnił swojego profilu Społeczności Steam.Jeżeli to twój znajomy, zachęć go, żeby to zrobił.', 'Cette personne n’a pas encore de profil dans la communauté Steam.Si vous la connaissez, encouragez-la à créer un profil et à rejoindre des parties !', 'Käyttäjä ei ole vielä luonut Steam-yhteisön profiilia.Jos tunnet hänet, kehota häntä luomaan profiili ja liittymään peliyhteisöön.', '이 사용자는 아직 Steam 커뮤니티 프로필을 설정하지 않았습니다.이 사용자를 알고 계시면 프로필을 설정하고 게임에 참가하도록 권해 주십시오!', 'Ez a felhasználó még nem állította be Steam közösségi profilját.Ha az ismerősöd, bátorítsd, hogy állítsa be profilját, és vegyen részt a játékban!'])
117
+ export const PrivateProfileTextList = Object.freeze(['This profile is private.', 'Αυτό το προφίλ είναι ιδιωτικό.', 'Dit is een privéprofiel', 'Denne profilen er privat.', 'Denne profil er privat.', 'Dieses Profil ist privat.', 'Профиль скрыт', 'Acest profil este privat.', 'Hồ sơ này không công khai.', 'Този профил е личен.', 'Den här profilen är privat.', 'Este perfil es privado.', 'Este perfil es privado.', 'This profile is private.', 'Профіль приховано', 'Questo profilo è privato.', 'プロフィールは非公開に設定されています。', '此个人资料是私密的。', '此個人檔案未公開。', 'Tento profil je soukromý.', 'โปรไฟล์นี้เป็นโปรไฟล์ส่วนตัว', 'Bu profil gizlidir.', 'Este perfil é privado.', 'Este perfil é privado.', 'Ten profil jest prywatny.', 'Ce profil est privé.', 'Tämä profiili on yksityinen.', '이 프로필은 비공개입니다.', 'Privát profil.'])
118
+ const ErrorProcessingRequest = Object.freeze(['An error was encountered while processing your request:', 'Παρουσιάστηκε σφάλμα κατά την επεξεργασία του αιτήματός σας:', 'Er is een fout opgetreden bij het verwerken van je verzoek.', 'Det oppstod en feil under behandling av forespørselen:', 'Der skete en fejl ved behandling af din forespørgsel:', 'Bei der Verarbeitung Ihrer Anfrage ist ein Fehler aufgetreten:', 'При обработке вашего запроса произошла ошибка:', 'A apărut o eroare în timpul procesării:', 'Đã có lỗi xảy ra trong quá trình xử lí yêu cầu của bạn:', 'Възникна грешка, докато обработвахме заявката Ви:', 'Ett fel uppstod när din begäran behandlades:', 'Se ha producido un error mientras se procesaba la solicitud:', 'Se ha producido un error mientras se procesaba la solicitud:', 'An error was encountered while processing your request:', 'Під час обробки вашого запиту сталася помилка:', 'Si è verificato un errore durante l\'elaborazione della tua richiesta:', 'リクエストの処理中にエラーが発生しました。', '处理您的请求时遇到错误:', '處理您的要求時發生錯誤:', 'Při zpracovávání Vašeho požadavku došlo k chybě:', 'ตรวจพบข้อผิดพลาดขณะกำลังประมวลผลคำร้องขอของคุณ:', 'İşleminiz sırasında bir hata meydana geldi:', 'Ocorreu um erro ao processar a sua solicitação:', 'Foi encontrado um erro ao processar o pedido', 'Wystąpił błąd przetwarzania żądania użytkownika:', 'Une erreur est survenue lors du traitement de votre requête :', 'Pyyntösi käsittelyssä tapahtui virhe:', '요청을 처리하는 동안 오류가 발생했습니다.', 'Hiba lépett fel a kérésed feldolgozása közben:'])
119
+ export const EmptyProfileSummary = Object.freeze(['No information given.', 'Δεν έχουν δοθεί πληροφορίες.', 'Geen informatie gegeven.', 'Ingen informasjon oppgitt.', 'Ingen oplysninger.', 'Keine Informationen angegeben.', 'Информация отсутствует.', 'Nicio informație oferită.', 'Không có thông tin nào được cấp.', 'Няма предоставена информация.', 'Information saknas.', 'No se ha proporcionado información.', 'No se ha proporcionado información.', 'No information given.', 'Не надано жодної інформації.', 'Nessuna informazione.', '情報が指定されていません。', '未提供信息。', '未提供任何資訊。', 'Nebyly zadány žádné informace.', 'ไม่ระบุข้อมูล', 'Herhangi bir bilgi verilmedi.', 'Nada informado.', 'Sem informações.', 'Nie podano informacji.', 'Aucune information disponible.', 'Ei tietoja.', '관련 정보가 없습니다.', 'Nincs információ.'])
120
+ const ECurrentlyTradeBanned = Object.freeze(['Currently trade banned', 'Αποκλεισμένος από ανταλλαγές', 'Heeft momenteel ruilban', 'For øyeblikket utestengt fra byttehandel', 'I øjeblikket udelukket fra at bytte', 'Zurzeit vom Handel ausgeschlossen', 'Заблокирован в системе обмена', 'În prezent, abilitate de schimb este banată', 'Hiện đang bị cấm trao đổi', 'Понастоящем със забрана за търгуване', 'Har för närvarande bytesförbud', 'Intercambio actualmente bloqueado', 'Intercambio actualmente bloqueado', 'Currently trade banned', 'Наразі обмін заблоковано', 'Attualmente bandito dagli scambi', '現在トレード禁止', '当前已禁止交易', '目前已遭交易封鎖', 'Momentálně má zakázáno obchodovat', 'ถูกแบนการแลกเปลี่ยนอยู่ในขณะนี้', 'Şu anda takas yasaklı', 'Banido de trocar', 'Banido do sistema de trocas', 'Wymiana obecnie zablokowana', 'Actuellement interdit d\'échange', 'Vaihtokiellossa', '거래 차단 상태', 'Jelenleg kitiltva a cseréből'])
121
+ const E1VACBanOnRecord = Object.freeze(['1 VAC ban on record | Info', '1 αποκλεισμός VAC στο αρχείο | Πληροφορίες', '1 vastgelegde VAC-verbanning | Info', '1 VAC-utestengelse registrert | Info', '1 VAC-udelukkelse registreret | Info', '1 VAC-Ausschluss | Informationen', '1 блокировка VAC | Подробнее', '1 banare VAC înregistrată | Informații', '1 lệnh cấm VAC được ghi nhận | Thông tin', '1 вписана VAC забрана | Информация', '1 registrerad VAC-avstängning | Info', '1 bloqueo por VAC registrado | Detalles', '1 bloqueo por VAC registrado | Detalles', '1 VAC ban on record | Info', '1 зареєстроване блокування VAC | Інформація', '1 ban VAC registrato | Informazioni', '1 件の VAC 検出記録 | 情報', '1 个记录在案的 VAC 封禁 | 信息', '1 個 VAC 封鎖紀錄 | 資訊', '1 ban ochrany VAC | Informace', 'VAC แบน 1 ครั้ง ในบันทึก | ข้อมูล', 'Kayıtlarda 1 VAC yasaklanması | Bilgi', '1 banimento VAC em registro | Informações', '1 banimento do VAC em registo | Informações', '1 zarejestrowana blokada VAC | Informacje', '1 bannissement VAC enregistré | Infos', '1 VAC-kielto merkitty | Tietoa', 'VAC 차단 기록 1건 | 정보', '1 feljegyzett VAC-kitiltás | Információ'])
122
+ const EMultipleVACBansOnRecord = Object.freeze(['Multiple VAC bans on record | Info', 'Πολλαπλοί αποκλεισμοί VAC στο αρχείο | Πληροφορίες', 'Meerdere vastgelegde VAC-bans | Info', 'Flere VAC-utestengelser registrert | Info', 'Adskillige VAC-udelukkelser registreret | Info', 'Mehrere VAC-Ausschlüsse | Informationen', 'Несколько блокировок VAC | Подробнее', 'Multiple banări VAC înregistrate | Informații', 'Nhiều lệnh cấm VAC được ghi nhận | Thông tin', 'Множество VAC забрани | Информация', 'Flera registrerade VAC-avstängningar | Info', 'Varios bloqueos por VAC registrados | Detalles', 'Varios bloqueos por VAC registrados | Detalles', 'Multiple VAC bans on record | Info', 'Декілька зареєстрованих блокувань VAC | Інформація', 'Più ban VAC registrati | Informazioni', '複数の VAC 検出記録 | 情報', '多个记录在案的 VAC 封禁 | 信息', '多個 VAC 封鎖紀錄 | 資訊', 'Více banů ochrany VAC | Informace', 'VAC แบนหลายครั้ง ในบันทึก | ข้อมูล', 'Kayıtlarda birden fazla VAC yasaklanması | Bilgi', 'Vários banimentos VAC em registro | Informações', 'Vários banimentos do VAC em registo | Informações', 'Wiele zarejestrowanych blokad VAC | Informacje', 'Plusieurs bannissements VAC enregistrés | Infos', 'Useita VAC-kieltoja merkitty | Tietoa', '다수의 VAC 차단 기록 | 정보', 'Több feljegyzett VAC-kitiltás | Információ'])
123
+ const E1GameBanOnRecord = Object.freeze(['1 game ban on record | Info', '1 αποκλεισμός παιχνιδιού στο αρχείο | Πληροφορίες', '1 vastgelegde spelverbanning | Info', '1 spillutestengelse registrert | Info', '1 spiludelukkelse registreret | Info', '1 Spielausschluss | Informationen', '1 игровая блокировка | Подробнее', '1 banare de joc înregistrată | Informații', '1 lệnh cấm trò chơi được ghi nhận | Thông tin', '1 игрална забрана | Информация', '1 registrerad spelavstängning | Info', '1 bloqueo en juego registrado | Detalles', '1 bloqueo en juego registrado | Detalles', '1 game ban on record | Info', '1 зареєстроване блокування у грі | Інформація', '1 ban di gioco registrato | Informazioni', '1 件のゲーム禁止記録 | 情報', '1 个记录在案的游戏封禁 | 信息', '1 個遊戲封鎖紀錄 | 資訊', '1 herní ban | Informace', 'เกมแบน 1 ครั้ง ในบันทึก | ข้อมูล', 'Kayıtlarda 1 oyun yasaklanması | Bilgi', '1 banimento de jogo em registro | Informações', '1 banimento de jogo em registo | Informações', '1 zarejestrowana blokada na grę | Informacje', '1 bannissement en jeu enregistré | Infos', '1 pelikielto merkitty | Tietoa', '게임 차단 기록 1건 | 정보', '1 feljegyzett játékkitiltás | Információ'])
124
+ const EMultipleGameBansOnRecord = Object.freeze(['Multiple game bans on record | Info', 'Πολλαπλοί αποκλεισμοί παιχνιδιών στο αρχείο | Πληροφορίες', 'Meerdere vastgelegde spelbans | Info', 'Flere spillutestengelser registrert | Info', 'Adskillige spiludelukkelser registreret | Info', 'Mehrere Spielausschlüsse | Informationen', 'Несколько игровых блокировок | Подробнее', 'Multiple banări de joc înregistrate | Informații', 'Nhiều lệnh cấm trò chơi được ghi nhận | Thông tin', 'Множество игрални забрани | Информация', 'Flera registrerade spelavstängningar | Info', 'Varios bloqueos en juegos registrados | Detalles', 'Varios bloqueos en juegos registrados | Detalles', 'Multiple game bans on record | Info', 'Декілька зареєстрованих блокувань в іграх | Інформація', 'Più ban di gioco registrati | Informazioni', '複数のゲーム禁止記録 | 情報', '多个记录在案的游戏封禁 | 信息', '多個遊戲封鎖紀錄 | 資訊', 'Více herních banů | Informace', 'เกมแบนหลายครั้ง ในบันทึก | ข้อมูล', 'Kayıtlarda birden fazla oyun yasaklanması | Bilgi', 'Vários banimentos de jogos em registro | Informações', 'Vários banimentos de jogos em registo | Informações', 'Wiele zarejestrowanych blokad na gry | Informacje', 'Plusieurs bannissements en jeu enregistrés | Infos', 'Useita pelikieltoja merkitty | Tietoa', '다수의 게임 차단 기록 | 정보', 'Több feljegyzett játékkitiltás | Információ'])
125
+ const EdaySinceLastBanRegExp = Object.freeze(['(\\d+) day\\(s\\) since last ban', '(\\d+) μέρες από τον τελευταίο αποκλεισμό', '(\\d+) dag\\(en\\) sinds vorige ban', '(\\d+) dag\\(er\\) siden siste utestengelse', '(\\d+) dag\\(e\\) siden sidste udelukkelse.', '(\\d+) Tag\\(e\\) seit dem letzten Ausschluss', 'Дней с последней блокировки: (\\d+)', '(\\d+) zi\\(le\\) de la ultima banare', '(\\d+) ngày từ lần cấm cuối', '(\\d+) ден\\(дни\\) от последна забрана', '(\\d+) dag\\(ar\\) sedan senaste avstängning', '(\\d+) día\\(s\\) desde su último bloqueo', '(\\d+) día\\(s\\) desde su último bloqueo', '(\\d+) day\\(s\\) since last ban', 'Днів з моменту останнього блокування: (\\d+)', '(\\d+) giorno/i dall\'ultimo ban', '最後の接続禁止から (\\d+) 日', '上次封禁于 (\\d+) 天前', '距離上次封鎖共 (\\d+) 天', 'Poslední ban byl uvalen před (\\d+) dny', '(\\d+) วัน นับตั้งแต่วันที่ถูกแบนครั้งล่าสุด', 'En son (\\d+) gün önce yasaklandı', '(\\d+) dia\\(s\\) desde o último banimento', '(\\d+) dia\\(s\\) desde o último ban', 'Dni od ostatniej blokady: (\\d+)', '(\\d+) jour\\(s\\) depuis le dernier bannissement', '(\\d+) päivä\\(ä\\) viime kiellosta', '마지막 차단 이후 (\\d+)일 경과', '(\\d+) nap az utolsó kitiltás óta'])
126
+ const SteamImageCDN = ['community.cloudflare.steamstatic.com', 'community.akamai.steamstatic.com'].map(cdn => `https://${cdn}`)
127
+
128
+ class SteamUser {
129
+ Steam_Language = ELanguage.english
130
+
131
+ static _EPrivacyState = Object.freeze({
132
+ Private: 1,
133
+ FriendsOnly: 2,
134
+ Public: 3
135
+ })
136
+
137
+ static _ECommentPrivacyState = Object.freeze({
138
+ Private: 2,
139
+ FriendsOnly: 0,
140
+ Public: 1
141
+ })
142
+
143
+ constructor(cookies, steamMachineAuth) {
144
+ this.setCookies(cookies, steamMachineAuth)
145
+ }
146
+
147
+ setSteamLanguage(language) {
148
+ language = language?.toLowerCase()
149
+ if(!Object.hasOwn(ELanguage, language)) {
150
+ return
151
+ }
152
+ this.Steam_Language = language
153
+ this._cookies = SteamUser.generateCookie({
154
+ ...SteamUser.parseCookie(this._cookies),
155
+ Steam_Language: this.Steam_Language,
156
+ })
157
+ }
158
+
159
+ setCookies(cookies, _steamMachineAuth) {
160
+ if(!cookies) {
161
+ return
162
+ }
163
+ cookies = Array.isArray(cookies) ? cookies.join(';') : cookies
164
+
165
+ let {
166
+ steamMachineAuth,
167
+ steamLoginSecure,
168
+ steamRememberLogin,
169
+ steamID,
170
+ miniprofile,
171
+ sessionid,
172
+ } = SteamUser.parseCookie(cookies)
173
+
174
+ if(!steamMachineAuth && _steamMachineAuth) {
175
+ steamMachineAuth = _steamMachineAuth
176
+ }
177
+ if(!sessionid) {
178
+ sessionid = SteamUser.generateSessionID()
179
+ }
180
+
181
+ this._cookies = SteamUser.generateCookie({
182
+ steamMachineAuth,
183
+ steamLoginSecure,
184
+ steamRememberLogin,
185
+ steamID,
186
+ miniprofile,
187
+ sessionid,
188
+ Steam_Language: this.Steam_Language,
189
+ })
190
+
191
+ this._sessionid = sessionid
192
+ this._steamid_user = steamID
193
+ this._miniprofile_user = miniprofile
194
+ this._steamMachineAuth = steamMachineAuth
195
+ this._steamLoginSecure = steamLoginSecure
196
+ this._steamRememberLogin = steamRememberLogin
197
+ }
198
+
199
+ static parseCookie(cookies) {
200
+ if(!Array.isArray(cookies)) {
201
+ cookies = cookies.split(';')
202
+ }
203
+ cookies = cookies.map(c => decodeURIComponent(c.trim()))
204
+
205
+
206
+ const sessionid = cookies.find(c => c.startsWith('sessionid'))?.substringAfter('=')
207
+ const Steam_Language = cookies.find(c => c.startsWith('Steam_Language'))?.substringAfter('=')
208
+
209
+ let steamID = cookies.find(c => c.startsWith('steamRememberLogin='))?.substringBetween('=', '||')
210
+ if(!steamID) {
211
+ //multiple steamMachineAuth for multiple steamID
212
+ //steamID = cookies.find(c => c.startsWith('steamMachineAuth'))?.substringBetween('steamMachineAuth', '=')
213
+ if(!steamID) {
214
+ steamID = cookies.find(c => c.startsWith('steamLoginSecure='))?.substringBetween('=', '||')
215
+ }
216
+ }
217
+ const miniprofile = SteamUser.steamID642Miniprofile(steamID)
218
+ const steamMachineAuth = cookies.find(c => c.startsWith('steamMachineAuth' + steamID))?.substringAfter('=')
219
+ const steamLoginSecure = cookies.find(c => c.startsWith('steamLoginSecure'))?.substringAfter('||')
220
+ const steamRememberLogin = cookies.find(c => c.startsWith('steamRememberLogin'))?.substringAfter('||')
221
+
222
+ return {
223
+ steamMachineAuth,
224
+ steamLoginSecure,
225
+ steamRememberLogin,
226
+ steamID,
227
+ miniprofile,
228
+ sessionid,
229
+ Steam_Language,
230
+ }
231
+ }
232
+
233
+ static generateCookie({
234
+ steamMachineAuth,
235
+ steamLoginSecure,
236
+ steamRememberLogin,
237
+ steamID,
238
+ sessionid,
239
+ Steam_Language,
240
+ }) {
241
+ return [steamLoginSecure && `steamLoginSecure=${steamID}||${steamLoginSecure}`, steamMachineAuth && `steamMachineAuth${steamID}=${steamMachineAuth}`, steamRememberLogin !== undefined ? `steamRememberLogin=${steamID}||${steamRememberLogin}` : steamRememberLogin, sessionid && `sessionid=${sessionid}`, Steam_Language && `Steam_Language=${Steam_Language}`,].filter(Boolean).join(';')
242
+ }
243
+
244
+ getCookies() {
245
+ return this._cookies
246
+ }
247
+
248
+ getSteamidUser() {
249
+ return this._steamid_user
250
+ }
251
+
252
+
253
+ getSessionid() {
254
+ return this._sessionid
255
+ }
256
+
257
+ getSteamMachineAuth() {
258
+ return this._steamMachineAuth
259
+ }
260
+
261
+ getSteamUserProfileURL(steamID = this.getSteamidUser()) {
262
+ return `profiles/${steamID}`
263
+ }
264
+
265
+ getMySteamUserProfileURL() {
266
+ return this.getSteamUserProfileURL()
267
+ }
268
+
269
+ /**
270
+ *
271
+ * start static functions
272
+ *
273
+ * */
274
+
275
+
276
+ async getUserSummary(steamID = this.getSteamidUser(), parts = []) {
277
+ return await SteamUser.getUserSummary(steamID, parts, this.getCookies())
278
+ }
279
+
280
+ static async getUserSummary(steamID, parts = [], cookie = null) {
281
+ const xmlMap = {
282
+ name: null,
283
+ realname: null,
284
+ steamID: null,
285
+ onlineState: null,
286
+
287
+ stateMessageFull: null,
288
+ stateMessage: null,
289
+ stateMessageGame: null,
290
+ stateMessage_NonSteamGame: null,
291
+
292
+ privacyState: null,
293
+ visibilityState: null,
294
+ avatarHash: null,
295
+ vacBanned: null,
296
+ tradeBanState: null,
297
+ isLimitedAccount: null,
298
+ customURL: null,
299
+ memberSince: null,
300
+ steamRating: null,
301
+ location: null,
302
+ summary: null,
303
+ privacyMessage: null,
304
+ notYetSetup: null,
305
+ }
306
+
307
+ const profileMap = {
308
+ name: null,
309
+ realname: null,
310
+ steamID: null,
311
+ avatarHash: null,
312
+ avatarFrame: null,
313
+ customURL: null,
314
+ location: null,
315
+ summary: null,
316
+ notYetSetup: null,
317
+ profile_private_info: null,
318
+ lobbyLink: null,
319
+ addFriendEnable: null,
320
+ isPrivate: null,
321
+ url: null,
322
+ nickname: null,
323
+ level: null,
324
+ dayLastBan: null,
325
+ gameBanFull: null,
326
+ isVACBan: null,
327
+ isGameBan: null,
328
+ isTradeBan: null,
329
+ daysSinceLastBan: null,
330
+ }
331
+
332
+ const mustFetchFromURL = parts.length === 0 || parts.some(key => !Object.hasOwn(xmlMap, key))//because xml not have some parts
333
+ let mustFetchFromXML = parts.length === 0 || parts.some(key => !Object.hasOwn(profileMap, key))//because profile not have some parts
334
+
335
+ if(!mustFetchFromURL && !mustFetchFromXML) {
336
+ mustFetchFromXML = true
337
+ }
338
+
339
+ const queue = []
340
+ if(mustFetchFromURL) {
341
+ queue.push((async function () {
342
+ try {
343
+ const result = (await request({
344
+ url: 'https://steamcommunity.com/profiles/' + steamID,
345
+ headers: {...cookie && {cookie}},
346
+ })).data
347
+ return SteamUser._parseUserProfile(result) || 'Error'
348
+ } catch (e) {
349
+ }
350
+ })())
351
+ }
352
+ if(mustFetchFromXML) {
353
+ queue.push((async function () {
354
+ try {
355
+ const resultXml = await SteamUser._httpRequestXML(`https://steamcommunity.com/profiles/${steamID}/?xml=1`)
356
+ return await SteamUser._parseSummaryFromXML(resultXml)
357
+ } catch (e) {
358
+ }
359
+ })())
360
+ }
361
+
362
+ const results = await Promise.all(queue)
363
+ return results.reduce(function (previousValue, currentValue) {
364
+ if(previousValue === 'Invalid' || currentValue === 'Invalid') {
365
+ return 'Invalid'
366
+ }
367
+ if(previousValue === 'Error' || currentValue === 'Error') {
368
+ return 'Error'
369
+ }
370
+
371
+ //order : undefined, null, '', Boolean
372
+ for (const key in currentValue) {
373
+ let previousValueOrder = 0
374
+ if(previousValue[key] === undefined) {
375
+ previousValueOrder = 0
376
+ }
377
+ else if(previousValue[key] === null) {
378
+ previousValueOrder = 1
379
+ }
380
+ else if(previousValue[key] === '') {
381
+ previousValueOrder = 2
382
+ }
383
+ else {
384
+ previousValueOrder = 3
385
+ }
386
+
387
+ let currentValueOrder = 0
388
+ if(currentValue[key] === undefined) {
389
+ currentValueOrder = 0
390
+ }
391
+ else if(currentValue[key] === null) {
392
+ currentValueOrder = 1
393
+ }
394
+ else if(currentValue[key] === '') {
395
+ currentValueOrder = 2
396
+ }
397
+ else {
398
+ currentValueOrder = 3
399
+ }
400
+
401
+ if(currentValueOrder > previousValueOrder) {
402
+ previousValue[key] = currentValue[key]
403
+ }
404
+ }
405
+
406
+ return previousValue
407
+ }, {})
408
+ }
409
+
410
+ static async getUserSummaryFromXML(steamID) {
411
+ const resultXml = await SteamUser._httpRequestXML(`https://steamcommunity.com/profiles/${steamID}/?xml=1`)
412
+ return await SteamUser._parseSummaryFromXML(resultXml)
413
+ }
414
+
415
+ static async _parseSummaryFromXML(resultXml) {
416
+ if(!resultXml) {
417
+ return
418
+ }
419
+
420
+ let json
421
+ try {
422
+ json = await xml2js.parseStringPromise(resultXml)
423
+ } catch (e) {
424
+ // console.error(e)
425
+ return
426
+ }
427
+
428
+ if(json?.response?.error?.[0] === 'The specified profile could not be found.') {
429
+ return 'Invalid'
430
+ }
431
+
432
+ if(!json?.profile) {
433
+ return
434
+ }
435
+ for (let key in json.profile) {
436
+ if(Array.isArray(json.profile[key]) && json.profile[key].length === 1) {
437
+ json.profile[key] = json.profile[key][0]
438
+ }
439
+ }
440
+
441
+ const _profile = json.profile
442
+ if(!_profile) {
443
+ return
444
+ }
445
+
446
+
447
+ let memberSince
448
+ if((memberSince = moment.utc(`November 3, 2016`, ['MMMM DD, YYYY', 'MMMM D, YYYY', 'MMMM DD', 'MMMM D'], true)).isValid()) {
449
+ memberSince = memberSince.valueOf()
450
+ }
451
+ else {
452
+ memberSince = _profile.memberSince
453
+ }
454
+
455
+ const avatarHash = SteamUser.GetAvatarHashFromMultipleURL([_profile.avatarIcon, _profile.avatarFull])
456
+ const privacyMessage = _profile.privacyMessage?._
457
+
458
+ //parse stateMessage
459
+ let stateMessage = '',
460
+ stateMessageGame = '',
461
+ stateMessage_NonSteamGame = ''
462
+ if(!_profile.stateMessage) {
463
+ }
464
+ else if(['Online', 'Online using Big Picture', 'Online using VR'].includes(_profile.stateMessage)) {
465
+ stateMessage = 'online'
466
+ _profile.stateMessage = ''
467
+ }
468
+ else if(_profile.stateMessage === 'Offline') {
469
+ stateMessage = 'offline'
470
+ _profile.stateMessage = ''
471
+ }
472
+ else if(_profile.stateMessage.startsWith('In non-Steam game<br/>')) {
473
+ stateMessage = 'in-game'
474
+ stateMessage_NonSteamGame = _profile.stateMessage.substringAfter('<br/>')
475
+ _profile.stateMessage = ''
476
+ }
477
+ else if(_profile.stateMessage.startsWith('In-Game<br/>')) {
478
+ stateMessage = 'in-game'
479
+ stateMessageGame = _profile.stateMessage.substringAfter('<br/>')
480
+ _profile.stateMessage = ''
481
+ }
482
+ else {
483
+ //error
484
+ }
485
+
486
+
487
+ return {
488
+ name: _profile.steamID,
489
+ realname: _profile.realname || '',
490
+ steamID: _profile.steamID64,
491
+ onlineState: _profile.onlineState,//offline in-game online
492
+
493
+ stateMessageFull: _profile.stateMessage,// 'Offline' Online In-Game<br/>16bit Trader In non-Steam game<br/>
494
+ stateMessage,
495
+ stateMessageGame,
496
+ stateMessage_NonSteamGame,
497
+
498
+ privacyState: _profile.privacyState,// 'public', private
499
+ visibilityState: !isNaN(parseInt(_profile.visibilityState)) ? parseInt(_profile.visibilityState) : undefined,//1, 2, 3
500
+ avatarHash,
501
+ vacBanned: !isNaN(parseInt(_profile.vacBanned)) ? parseInt(_profile.vacBanned) : undefined,//0, 1, 2
502
+ tradeBanState: _profile.tradeBanState,//'None'
503
+ isLimitedAccount: !isNaN(parseInt(_profile.isLimitedAccount)) ? parseInt(_profile.isLimitedAccount) : undefined,//0 or 1
504
+ customURL: _profile.customURL,
505
+ memberSince,//'August 19, 2018', April 24, September 6, 2019
506
+ steamRating: _profile.steamRating,
507
+ location: _profile.location,
508
+ summary: SteamUser._formatSummary(_profile.summary),
509
+ privacyMessage: NotYetSetupProfileTextList.includes(privacyMessage) ? '___NotYetSetupProfile___' : privacyMessage,//'This user has not yet set up their Steam Community profile.If you know this person, encourage them to set up their profile and join in the gaming!'
510
+ notYetSetup: NotYetSetupProfileTextList.includes(privacyMessage),
511
+ }
512
+ }
513
+
514
+ async getUserSummaryFromProfile(steamID = this.getSteamidUser()) {
515
+ return await SteamUser.getUserSummaryFromProfile(steamID, this.getCookies())
516
+ }
517
+
518
+ static async getUserSummaryFromProfile(steamID, cookie) {
519
+ const result = (await request({
520
+ url: 'https://steamcommunity.com/profiles/' + steamID,
521
+ headers: {...cookie && {cookie}},
522
+ }))?.data
523
+ return SteamUser._parseUserProfile(result)
524
+ }
525
+
526
+ static _formatSummary(summary) {
527
+ summary = summary || ''
528
+ for (let i = 1; i < SteamImageCDN.length; i++) {
529
+ summary = summary.replaceAll(SteamImageCDN[i], SteamImageCDN[0])
530
+ }
531
+ return EmptyProfileSummary.includes(summary) ? null : summary
532
+ }
533
+
534
+ static _parseUserProfile(html) {
535
+ if(!html) {
536
+ return
537
+ }
538
+ const $ = cheerio.load(html)
539
+
540
+ const sectionText = $('#message .sectionText').text()?.trim()
541
+ if(ErrorProcessingRequest.includes(sectionText)) {
542
+ return
543
+ }
544
+ const profile_private_info = $('.profile_header_summary .profile_private_info').text()?.trim()
545
+ const addFriendEnable = !!$('#btn_add_friend').length
546
+ const name = $('.persona_name .actual_persona_name').text().trim()
547
+ const $personaLevel = $('.persona_level .friendPlayerLevelNum')
548
+ const level = $personaLevel.length ? parseInt($personaLevel.text()) : null
549
+
550
+ const g_rgProfileDataExample = {
551
+ url: 'https://steamcommunity.com/id/natri99/',
552
+ steamid: '76561199040402348',
553
+ personaname: 'Natri',
554
+ summary: 'Sub: <a class="bb_link" href="https://steamcommunity.com/id/Natri2099/" target="_blank" rel="noreferrer" >https://steamcommunity.com/id/Natri2099/</a> '
555
+ }
556
+ const g_rgProfileData = JSON_parse(html.substringBetweenOrNull('g_rgProfileData = ', '"};') + '"}')
557
+ if(!g_rgProfileData) {
558
+ return
559
+ }
560
+
561
+ const lobbyLink = $(`.profile_in_game_joingame a[href*='steam://joinlobby/']`).attr('href')
562
+ const nickname = $('.persona_name .nickname').text()
563
+
564
+ const profileBanStatusEl = $('.profile_ban_status').clone()
565
+
566
+ const gameBanFull = [...($(profileBanStatusEl.children()))]
567
+ .map(line => {
568
+ const text = $(line).text()
569
+ $(line).remove()
570
+ return text
571
+ }).concat(profileBanStatusEl.text()).map(text => text?.replaceAll(/[\t\n\r]/gi, ' ')?.replaceAll(/\s+/gi, ' ')?.trim()).filter(Boolean)
572
+ const GameBan = SteamUser.parseGameBanType(gameBanFull)
573
+ let dayLastBan = null
574
+ if(GameBan && GameBan.daysSinceLastBan !== null) {
575
+ dayLastBan = moment()
576
+ .startOf('day')
577
+ .subtract(GameBan.daysSinceLastBan, 'days')
578
+ .valueOf()
579
+ }
580
+
581
+ const url = g_rgProfileData.url ? (g_rgProfileData.url.endsWith('/') ? g_rgProfileData.url.substringBeforeLast('/') : g_rgProfileData.url) : ''
582
+ const customURL = SteamUser.getCustomURL_from_ProfileURL(url) || ''
583
+
584
+ const avatarFrameEl = $('.profile_header .playerAvatar .profile_avatar_frame')
585
+ const avatarFrame = avatarFrameEl.find('img').attr('src')
586
+ avatarFrameEl.remove()
587
+
588
+ // const avatarFull = $(`.profile_header .playerAvatar img`).attr('src')
589
+ const avatarFulls = [$(`head > link[rel="image_src"][href$="_full.jpg"]`).attr('href'), $(`head > meta[name="twitter:image"][content$="_full.jpg"]`).attr('content'), $(`head > meta[property="og:image"][content$="_full.jpg"]`).attr('content'), $(`.profile_header .playerAvatar img`).attr('src'),].filter(Boolean)
590
+ const avatarHash = SteamUser.GetAvatarHashFromMultipleURL(avatarFulls)
591
+ const location = $('img.profile_flag').attr('src')?.substringBetweenOrNull('/images/countryflags/', '.')?.toUpperCase() || ''
592
+
593
+ const playerAvatarClass = $('.profile_header .playerAvatar').attr('class')
594
+ const onlineState = ['online', 'offline', 'in-game'].find(_onlineState => playerAvatarClass.includes(_onlineState))
595
+ const gamename = $('.profile_in_game .profile_in_game_name').text()?.trim() || ''
596
+ let stateMessageGame = ''
597
+ if(onlineState === 'in-game') {
598
+ stateMessageGame = gamename
599
+ }
600
+
601
+ return {
602
+ name: g_rgProfileData.personaname,
603
+ realname: $('.header_real_name bdi').text()?.trim() || '',
604
+ onlineState,
605
+ steamID: g_rgProfileData.steamid,
606
+ avatarHash,
607
+ avatarFrame,
608
+ customURL,
609
+ location,
610
+ summary: SteamUser._formatSummary(g_rgProfileData.summary),
611
+ notYetSetup: NotYetSetupProfileTextList.includes(profile_private_info),
612
+ profile_private_info: profile_private_info ? ((NotYetSetupProfileTextList.includes(profile_private_info) ? '___NotYetSetupProfile___' : (PrivateProfileTextList.includes(profile_private_info) ? '___PrivateProfile___' : profile_private_info))) : null,
613
+ lobbyLink,
614
+ addFriendEnable,
615
+ isPrivate: PrivateProfileTextList.includes(profile_private_info),
616
+ url,
617
+ nickname,
618
+ level,
619
+ dayLastBan,
620
+ gameBanFull: gameBanFull.length ? gameBanFull : null,
621
+ isVACBan: GameBan?.isVACBan,
622
+ isGameBan: GameBan?.isGameBan,
623
+ isTradeBan: GameBan?.isTradeBan,
624
+ daysSinceLastBan: GameBan?.daysSinceLastBan,
625
+ stateMessageGame,
626
+ sectionText,
627
+ }
628
+ }
629
+
630
+ static async QueryLocations() {
631
+ let response = await request(`https://steamcommunity.com//actions/QueryLocations/`)
632
+ return response?.data || []
633
+ const resultExample = [{
634
+ countrycode: 'US',
635
+ hasstates: 1,
636
+ countryname: 'United States'
637
+ }, {
638
+ countrycode: 'CA',
639
+ hasstates: 1,
640
+ countryname: 'Canada'
641
+ }]
642
+ }
643
+
644
+ static async queryAppList(term) {
645
+ let response = await request(`https://store.steampowered.com/search/results/?query&start=0&count=50&dynamic_data=&sort_by=_ASC&term=${encodeURIComponent(term)}&infinite=1`)
646
+ if(!response?.data?.results_html) {
647
+ return []
648
+ }
649
+ const $ = cheerio.load(response
650
+ ?.data
651
+ ?.results_html
652
+ ?.replaceAll(/[\t\n\r]/gi, '')
653
+ .trim())
654
+ const results = []
655
+ $(`a.search_result_row`).each(function () {
656
+ const el = $(this)
657
+ const appid = parseInt(el.attr('data-ds-appid'))
658
+ const name = el.find('.search_name .title').text().trim()
659
+ const img = el.find('.search_capsule img').attr('src').trim()
660
+ const price = el.find('.search_price').text().trim()
661
+ results.push({
662
+ appid,
663
+ name,
664
+ img,
665
+ price,
666
+ })
667
+ })
668
+ return results
669
+
670
+ }
671
+
672
+ static async suggestAppList(term) {
673
+ let response = await request(`https://store.steampowered.com/search/suggest?term=${encodeURIComponent(term)}&f=games&cc=VN&realm=1&l=english&excluded_content_descriptors%5B%5D=3&excluded_content_descriptors%5B%5D=4&use_store_query=1`)
674
+ if(!response?.data) {
675
+ return []
676
+ }
677
+ const $ = response._$()
678
+ const results = []
679
+ $(`a.match`).each(function () {
680
+ const el = $(this)
681
+ const appid = parseInt(el.attr('data-ds-appid'))
682
+ const name = el.find('.match_name').text().trim()
683
+ const img = el.find('.match_img img').attr('src').trim()
684
+ const price = el.find('.match_price').text().trim()
685
+ results.push({
686
+ appid,
687
+ name,
688
+ img,
689
+ price,
690
+ })
691
+ })
692
+ return results
693
+ }
694
+
695
+ static steamID642Miniprofile(steamID) {
696
+ return parseInt((new SteamID(steamID)).getSteam3RenderedID().split(':').pop().split(']')[0])
697
+ }
698
+
699
+ static miniprofile2SteamID64(miniprofile) {
700
+ const accountid = parseInt(miniprofile)
701
+ if(isNaN(accountid)) {
702
+ //debug
703
+ console_log('debug')
704
+ return
705
+ }
706
+ return (SteamID.fromIndividualAccountID(accountid)).getSteamID64()
707
+ }
708
+
709
+ static groupminiid2SteamID64(miniid) {
710
+ const accountid = parseInt(miniid)
711
+ if(isNaN(accountid)) {
712
+ //debug
713
+ console_log('debug')
714
+ return
715
+ }
716
+
717
+ const sid = new SteamID()
718
+ sid.accountid = accountid
719
+ sid.type = SteamID.Type.CLAN
720
+ sid.instance = SteamID.Instance.ALL
721
+ sid.universe = SteamID.Universe.PUBLIC
722
+
723
+ return sid.getSteamID64()
724
+ }
725
+
726
+ static getCustomURL_from_ProfileURL(profileURL) {
727
+ const split = 'steamcommunity.com/id/'
728
+ if(profileURL.includes(split)) {
729
+ const customURL = profileURL.substringAfter(split)
730
+ return customURL.includes('/') ? customURL.substringBefore('/') : customURL
731
+ }
732
+ return null
733
+ }
734
+
735
+ static getProfileURL_from_CustomURL(customURL) {
736
+ if(typeof customURL !== 'string' || !customURL) {
737
+ return null
738
+ }
739
+ return `https://steamcommunity.com/id/${customURL.trim()}`
740
+ }
741
+
742
+ static async GetAppList() {
743
+ let result = await request(`http://api.steampowered.com/ISteamApps/GetAppList/v0002/?format=json`)
744
+ return result?.data
745
+ const resultExample = {
746
+ applist: {
747
+ apps: [{
748
+ appid: 1829051,
749
+ name: 'XXXXX'
750
+ },]
751
+ }
752
+ }
753
+ }
754
+
755
+ static async GetCurrentVersion(appID) {
756
+ let result = await request(`https://api.steampowered.com/ISteamApps/UpToDateCheck/v1/?format=json&appid=${appID}&version=0`)
757
+ return result?.data?.response?.required_version
758
+ }
759
+
760
+ static generateSessionID() {
761
+ return randomBytes(12).toString('hex')
762
+ }
763
+
764
+ static async communityLogin({
765
+ username,
766
+ password,
767
+ emailauth,
768
+ cookie,
769
+ steamMachineAuth
770
+ }) {
771
+ if(!username || !password) {
772
+ return
773
+ }
774
+ username = username?.toLowerCase()
775
+ const rsakey = await this.getrsakey(username)
776
+ if(!rsakey || !rsakey.success) {
777
+ return
778
+ }
779
+
780
+ const key = new RSA()
781
+ key.setPublic(rsakey.publickey_mod, rsakey.publickey_exp)
782
+ const encryptedPassword = hex2b64(key.encrypt(password))
783
+
784
+ if(cookie) {
785
+ const cookieParts = SteamUser.parseCookie(cookie)
786
+ if(!cookieParts.steamMachineAuth && steamMachineAuth) {
787
+ cookieParts.steamMachineAuth = steamMachineAuth
788
+ }
789
+ cookie = SteamUser.generateCookie(cookieParts)
790
+ }
791
+
792
+
793
+ const postData = {
794
+ captcha_text: '',
795
+ captchagid: -1,
796
+ emailauth: emailauth,
797
+ emailsteamid: '',
798
+ password: encryptedPassword,
799
+ remember_login: 'true',
800
+ rsatimestamp: rsakey.timestamp,
801
+ twofactorcode: '',
802
+ username,
803
+ loginfriendlyname: 'Playerhubs.com',
804
+ donotcache: Date.now(),
805
+ tokentype: -1,
806
+ }
807
+
808
+ let result = await request({
809
+ url: `https://steamcommunity.com/login/dologin/`,
810
+ method: 'POST',
811
+ headers: {
812
+ Connection: 'keep-alive',
813
+ accept: '*/*',
814
+ 'accept-language': 'en-US,en;q=0.9',
815
+ 'cache-control': 'no-cache',
816
+ 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
817
+ pragma: 'no-cache',
818
+ 'sec-ch-ua': '"Chromium";v="104", " Not A;Brand";v="99", "Google Chrome";v="104"',
819
+ 'sec-ch-ua-mobile': '?0',
820
+ 'sec-ch-ua-platform': '"Windows"',
821
+ 'sec-fetch-dest': 'empty',
822
+ 'sec-fetch-mode': 'cors',
823
+ 'sec-fetch-site': 'same-origin',
824
+ 'x-kl-ajax-request': 'Ajax_Request',
825
+ 'x-requested-with': 'XMLHttpRequest',
826
+ Referer: 'https://steamcommunity.com/login/home/',
827
+ 'Referrer-Policy': 'strict-origin-when-cross-origin',
828
+ Cookie: cookie || rsakey.cookie.join(';'),
829
+ },
830
+ data: Object.keys(postData).map(function (key) {
831
+ let value = postData[key]
832
+ if(typeof value === 'undefined') {
833
+ value = ''
834
+ }
835
+ return `${key}=${encodeURIComponent(value.toString())}`
836
+ }).join('&'),
837
+ })
838
+
839
+ if(result?.data) {
840
+ const jsonResponse = {
841
+ ...result.data,
842
+ timestamp: moment().unix()
843
+ }
844
+ if(jsonResponse.success) {
845
+ const resultExample = {
846
+ success: true,
847
+ requires_twofactor: false,
848
+ login_complete: true,
849
+ transfer_urls: ['https://store.steampowered.com/login/transfer', 'https://help.steampowered.com/login/transfer'],
850
+ transfer_parameters: {
851
+ steamid: '76561199277189971',
852
+ token_secure: 'AE985344A09FABA46E8095E1AD6B49AC0B64C8FA',
853
+ auth: 'be54a809d47fe7d61082c806474b9583',
854
+ remember_login: true,
855
+ webcookie: '323B473A2023BE5868F2445AF41D814283A7CD7E'
856
+ }
857
+ }
858
+ let cookies = result.headers['set-cookie']?.map(c => decodeURI(c.substringBefore(';')).trim()) //array
859
+ let expires
860
+ try {
861
+ expires = result.headers['set-cookie'].find(c => c.toLowerCase().startsWith('steamRememberLogin'.toLowerCase())).split(';').map(k => k.trim()).find(k => k.toLowerCase().startsWith('Expires='.toLowerCase())).substringAfter('Expires=')
862
+ } catch (e) {
863
+ }
864
+
865
+ if(!cookies.some(c => c.startsWith('sessionid='))) {
866
+ const sessionid = SteamUser.generateSessionID()
867
+ cookies.push(`sessionid=${sessionid}`)
868
+ }
869
+
870
+ return {
871
+ ...jsonResponse,
872
+ expiresStr: expires,
873
+ expires: expires ? moment(expires).valueOf() : null,
874
+ cookie: cookies.join(';')
875
+ }
876
+ }
877
+ else {
878
+ if(jsonResponse.emailauth_needed) {
879
+ const resultExample = {
880
+ success: false,
881
+ requires_twofactor: false,
882
+ message: '',
883
+ emailauth_needed: true,
884
+ emaildomain: 'gmail.com',
885
+ emailsteamid: '76561199277189971'
886
+ }
887
+ }
888
+ else if(jsonResponse.message) {
889
+ const resultExample = {
890
+ success: false,
891
+ requires_twofactor: false,
892
+ message: 'There have been too many login failures from your network in a short time period. Please wait and try again later.',
893
+ captcha_needed: false,
894
+ captcha_gid: -1
895
+ }
896
+ }
897
+
898
+ return jsonResponse
899
+ }
900
+ }
901
+
902
+ /*if (result.statusText === "Unauthorized") {
903
+ console_log(result.statusText);
904
+ } else {
905
+ console_log(result.data);
906
+ }*/
907
+ // return result
908
+ }
909
+
910
+ static async getrsakey(username) {
911
+ if(!username) {
912
+ return
913
+ }
914
+ username = username?.toLowerCase()
915
+ let response = await request({
916
+ url: `https://steamcommunity.com/login/getrsakey/`,
917
+ method: 'POST',
918
+ headers: {
919
+ accept: '*/*',
920
+ 'accept-language': 'en-US,en;q=0.9',
921
+ 'cache-control': 'no-cache',
922
+ 'content-type': 'multipart/form-data',
923
+ pragma: 'no-cache',
924
+ 'sec-ch-ua': '"Chromium";v="104", " Not A;Brand";v="99", "Google Chrome";v="104"',
925
+ 'sec-ch-ua-mobile': '?0',
926
+ 'sec-ch-ua-platform': '"Windows"',
927
+ 'sec-fetch-dest': 'empty',
928
+ 'sec-fetch-mode': 'cors',
929
+ 'sec-fetch-site': 'same-origin',
930
+ 'x-kl-ajax-request': 'Ajax_Request',
931
+ 'x-requested-with': 'XMLHttpRequest',
932
+ Referer: 'https://steamcommunity.com/login/home/',
933
+ 'Referrer-Policy': 'strict-origin-when-cross-origin'
934
+ },
935
+ data: {
936
+ username,
937
+ donotcache: Date.now(),
938
+ },
939
+ })
940
+
941
+ return response?.data ? {
942
+ ...response?.data,
943
+ cookie: response.headers['set-cookie']?.map(c => decodeURI(c.substringBefore(';'))) || []
944
+ } : null
945
+
946
+ const resultExample = {
947
+ success: true,
948
+ publickey_mod: 'ca0cfb7ffc3b6f8926f3f822c535da4ee4c6c83ed3e318b7f72c1d714ae92c02dae6ec19a011b3a8c5d10399dd58e3df9d12d7d0dc3b20856d62b2860c89fb65140ccbc11f36d3cce1373c6067ba8bcc9de2a1d0188d0ae6c23e9fdeca11dd59ae13f7fd3f750b8f6f921d62279edef90b9726e421c98a511a9c62c3f65a8587285bed3be8dda32eee74d9fecbb1243833f0ddf8c19eaa22847aa736675e32560d4a59695bf90dc22eb2f2879364dbc6ac86ae3aee1cd7fb0a444471735d45a00250f96ec5fe60fea257ad7b1699153e790dc3a102d1c0cb5b2f3e1844dbc1409f60ff7f29be771afacd22e3aeb879ed0e0e9e9c2fb9ab0d9a2142c6462d9e61',
949
+ publickey_exp: '010001',
950
+ timestamp: '403345600000',
951
+ token_gid: '2be211c91a7dd170'
952
+ }
953
+ }
954
+
955
+ static async oAuthLogin(steamguard, access_token) {
956
+ steamguard = steamguard.split('||')
957
+ const steamID = (new SteamID(steamguard[0])).getSteamID64()
958
+ const sessionID = this.generateSessionID()
959
+ let result = await request({
960
+ url: `https://api.steampowered.com/IMobileAuthService/GetWGToken/v1/`,
961
+ method: 'POST',
962
+ headers: {
963
+ 'Content-Type': 'multipart/form-data'
964
+ },
965
+ data: {
966
+ access_token,
967
+ },
968
+ })
969
+
970
+ if(result?.data?.response?.token) {
971
+ const cookies = ['steamLogin=' + encodeURIComponent(steamID + '||' + result?.data?.response?.token), 'steamLoginSecure=' + encodeURIComponent(steamID + '||' + result?.data?.response?.token_secure), 'steamMachineAuth' + steamID + '=' + steamguard[1], 'sessionid=' + sessionID]
972
+ return {
973
+ sessionID,
974
+ cookies,
975
+ steamID: steamID,
976
+ }
977
+ }
978
+ else {
979
+ return false
980
+ }
981
+ }
982
+
983
+ /**
984
+ *
985
+ * end static functions
986
+ *
987
+ * */
988
+
989
+
990
+ _formatHttpRequest(params) {
991
+ if(typeof params === 'string') {
992
+ params = {
993
+ url: params,
994
+ method: 'GET',
995
+ }
996
+ }
997
+
998
+ if(!(params.headers instanceof Header)) {
999
+ params.headers = new Header(params.headers)
1000
+ }
1001
+
1002
+ params.headers.set({
1003
+ Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
1004
+ 'Accept-Language': 'en-US,en;q=0.9',
1005
+ 'Cache-Control': 'no-cache',
1006
+ Connection: 'keep-alive',
1007
+ Pragma: 'no-cache',
1008
+ 'Sec-Fetch-Dest': 'document',
1009
+ 'Sec-Fetch-Mode': 'navigate',
1010
+ 'Sec-Fetch-Site': 'none',
1011
+ 'Sec-Fetch-User': '?1',
1012
+ 'Upgrade-Insecure-Requests': '1',
1013
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36',
1014
+ 'sec-ch-ua': '".Not/A)Brand";v="99", "Google Chrome";v="103", "Chromium";v="103"',
1015
+ 'sec-ch-ua-mobile': '?0',
1016
+ 'sec-ch-ua-platform': '"Windows"',
1017
+ Cookie: this._cookies,
1018
+ })
1019
+
1020
+ let referer = params.headers.get('referer')
1021
+ if(!referer) {
1022
+ referer = SteamcommunityURL
1023
+ const URLReferer = URL.parse(params.url)
1024
+ if(URLReferer.protocol && URLReferer.host) {
1025
+ referer = URLReferer.protocol + '//' + URLReferer.host
1026
+ }
1027
+ }
1028
+ params.headers.set({
1029
+ Referer: referer,
1030
+ })
1031
+
1032
+ if(!params.data) {
1033
+ params.data = {}
1034
+ }
1035
+
1036
+ params.referrerPolicy = 'strict-origin-when-cross-origin'
1037
+
1038
+ if(!params.method) {
1039
+ params.method = 'GET'
1040
+ }
1041
+ params.method = params.method.toUpperCase()
1042
+
1043
+ return params
1044
+ }
1045
+
1046
+ static _isPlsLoginFirst(data) {
1047
+ try {
1048
+ if(data?.includes(`<p>Please login first.</p>`)) {
1049
+ return true
1050
+ }
1051
+ } catch (e) {
1052
+ }
1053
+ return false
1054
+ }
1055
+
1056
+ async _httpRequest(params) {
1057
+ let result = null,
1058
+ i = 0
1059
+ params = this._formatHttpRequest(params)
1060
+
1061
+ if(!this._cookies) {
1062
+ throw new Error('You jave not set cookie yet')
1063
+ }
1064
+
1065
+ if(params.method.toUpperCase() === 'POST') {
1066
+ if(params.data instanceof URLSearchParams) {
1067
+ params.data.append('sessionid', this._sessionid)
1068
+ params.data.append('sessionID', this._sessionid)
1069
+ }
1070
+ else if(typeof params.data === 'string') {
1071
+ params.data += '&sessionid=' + this._sessionid
1072
+ params.data += '&sessionID=' + this._sessionid
1073
+ }
1074
+ else {
1075
+ params.data.sessionid = params.data.sessionID = this._sessionid
1076
+
1077
+ // if(!params.headers.get('content-type')) {
1078
+ // const formData = new URLSearchParams()
1079
+ // for (let key in params.data) {
1080
+ // formData.append(key, params.data[key])
1081
+ // }
1082
+ // params.data = formData
1083
+ // }
1084
+ }
1085
+
1086
+ if(params.data instanceof URLSearchParams) {
1087
+ params.headers.set('content-type', 'application/x-www-form-urlencoded')
1088
+ }
1089
+
1090
+ }
1091
+
1092
+ while (!result && i++ < MAX_RETRY) {
1093
+ const config = {
1094
+ baseURL: SteamcommunityURL, ...params,
1095
+ data: typeof params.data === 'object' && !Object.keys(params.data).length ? undefined : params.data,
1096
+ headers: params.headers.get(),
1097
+ beforeRedirect: (options, {headers}) => {
1098
+ if(options.hostname === 'steamcommunity.com') {
1099
+ options.headers.cookie = this._cookies
1100
+ }
1101
+ if(headers.location.startsWith('https://login.steampowered.com/jwt/refresh')) {
1102
+ //should login first
1103
+ }
1104
+ },
1105
+ }
1106
+
1107
+ let plsLoginFirst = false
1108
+
1109
+ result = await request(config)
1110
+ if(result.status === 429) {
1111
+ console.log('Too Many Requests')
1112
+ await sleep(60000)
1113
+ result = await request(config)
1114
+ }
1115
+ if(result?.error?.code === 'ERR_FR_TOO_MANY_REDIRECTS') {
1116
+ //Maximum number of redirects exceeded
1117
+ plsLoginFirst = true
1118
+ }
1119
+
1120
+ if(SteamUser._isPlsLoginFirst(result?.data?.data)) {
1121
+ plsLoginFirst = true
1122
+ }
1123
+
1124
+ if(plsLoginFirst) {
1125
+ console.error(params.url, `Please login first`)
1126
+ return {
1127
+ data: null,
1128
+ headers: config.headers
1129
+ }
1130
+ }
1131
+ }
1132
+
1133
+ return result
1134
+ }
1135
+
1136
+ async _httpRequestAjax(params) {
1137
+ params = this._formatHttpRequest(params)
1138
+ const headers = params.headers instanceof Header ? params.headers : new Header(_.cloneDeep(params.headers))
1139
+ params.headers = headers.set({
1140
+ 'X-KL-Ajax-Request': 'Ajax_Request',
1141
+ 'X-Prototype-Version': '1.7',
1142
+ 'X-Requested-With': 'XMLHttpRequest',
1143
+ }).get()
1144
+ return await this._httpRequest(params)
1145
+ }
1146
+
1147
+ static RequestXML_TooManyRequests = 0
1148
+
1149
+ static async _httpRequestXML(link) {
1150
+ if(SteamUser.RequestXML_TooManyRequests && (new Date()).getTime() - SteamUser.RequestXML_TooManyRequests < 60000) {
1151
+ return
1152
+ }
1153
+
1154
+ const resultXml = (await request(link))?.data
1155
+ if(resultXml?.includes(`An error was encountered while processing your request:`) && resultXml?.includes(`You've made too many requests recently. Please wait and try your request again later.`)) {
1156
+ //log
1157
+ SteamUser.RequestXML_TooManyRequests = (new Date()).getTime()
1158
+ }
1159
+ else {
1160
+ SteamUser.RequestXML_TooManyRequests = 0
1161
+ }
1162
+ return resultXml
1163
+ }
1164
+
1165
+
1166
+ async getQuickInviteData() {
1167
+ const {data} = await this._httpRequestAjax({
1168
+ url: `invites/ajaxcreate`,
1169
+ data: {
1170
+ steamid_user: this.getSteamidUser(),
1171
+ duration: 2592000, //30 days
1172
+ },
1173
+ headers: {
1174
+ 'content-type': 'multipart/form-data',
1175
+ },
1176
+ method: 'POST',
1177
+ })
1178
+ return data?.success === 1 ? {
1179
+ ...data.invite,
1180
+ steamid_user: this.getSteamidUser()
1181
+ } : null
1182
+
1183
+ const resultExample = {
1184
+ success: 1,
1185
+ token: 'MQNWBHQV',
1186
+ invite: {
1187
+ invite_token: 'MQNWBHQV',
1188
+ invite_limit: '1',
1189
+ invite_duration: '2592000',
1190
+ time_created: 1659976435,
1191
+ valid: 1,
1192
+ success: 1,
1193
+ rwgrsn: -2,
1194
+ steamid_user: '76561199040402348',
1195
+ }
1196
+ }
1197
+ const errorExample = {
1198
+ success: 'false',
1199
+ error: 15
1200
+ }
1201
+ }
1202
+
1203
+ //add friend link
1204
+ async getQuickInviteLink() {
1205
+ const short_url = `https://s.team/p/${SteamUser.createFriendCode(this.getSteamidUser())}`
1206
+ const quickInviteData = await this.getQuickInviteData()
1207
+ return quickInviteData?.invite_token ? `${short_url}/${quickInviteData?.invite_token}` : null
1208
+ }
1209
+
1210
+ async getCurrentQuickInviteTokens() {
1211
+ return (await this._httpRequest(`invites/ajaxgetall?sessionid=${this._sessionid}`))?.data
1212
+ const successExample = {
1213
+ success: 1,
1214
+ tokens: [{
1215
+ invite_token: 'hrcchjjm',
1216
+ invite_limit: '1',
1217
+ invite_duration: '2591695',
1218
+ time_created: 1662777551,
1219
+ valid: 1
1220
+ }, {
1221
+ invite_token: 'kwmppbvm',
1222
+ invite_limit: '1',
1223
+ invite_duration: '2591702',
1224
+ time_created: 1662777558,
1225
+ valid: 1
1226
+ }, {
1227
+ invite_token: 'vpghcgjp',
1228
+ invite_limit: '1',
1229
+ invite_duration: '2584709',
1230
+ time_created: 1662770565,
1231
+ valid: 1
1232
+ }]
1233
+ }
1234
+ }
1235
+
1236
+
1237
+ //{ success: 1 }
1238
+ //{ success: 91 }
1239
+ //{ success: 8 }//link expires
1240
+ async acceptQuickInviteData(quickInviteData) {
1241
+ return (await this._httpRequest(`invites/ajaxredeem?sessionid=${this._sessionid}&steamid_user=${quickInviteData.steamid_user}&invite_token=${quickInviteData.invite_token}`))?.data
1242
+ }
1243
+
1244
+ async acceptQuickInviteLink(quickInviteLink) {
1245
+ const invite_token = quickInviteLink.removeSuffix('/').substringAfterLast('/')
1246
+ const result = await this._httpRequest(quickInviteLink)
1247
+ if(!result.data) {
1248
+ return
1249
+ }
1250
+ let g_rgProfileData = JSON_parse(result.data.substringBetweenOrNull('g_rgProfileData = ', '"};') + '"}')
1251
+ if(!g_rgProfileData) {
1252
+ return
1253
+ }
1254
+ const quickInviteData = {
1255
+ invite_token,
1256
+ steamid_user: g_rgProfileData.steamid,
1257
+ }
1258
+ return await this.acceptQuickInviteData(quickInviteData)
1259
+ }
1260
+
1261
+ _parseComments(html) {
1262
+ const $ = cheerio.load(`<div>${html}</div>`)
1263
+ const comments = []
1264
+ $('.commentthread_comment').each(function () {
1265
+ const el = $(this)
1266
+ const content = el.find('.commentthread_comment_text').text().trim()
1267
+ const id = el.attr('id')//comment_3452590876711274713
1268
+ const commentID = id.substringAfter('comment_')
1269
+ const avatar = el.find('.commentthread_comment_avatar > a > img').attr('src')
1270
+ const avatarHash = SteamUser.GetAvatarHashFromURL(avatar)
1271
+ const commentthread_comment_author = el.find('.commentthread_comment_author > a')
1272
+ const profileURL = commentthread_comment_author.attr('href')
1273
+ const miniprofile = parseInt(commentthread_comment_author.attr('data-miniprofile'))
1274
+ const name = commentthread_comment_author.text()?.trim()
1275
+ const timestamp = parseInt(el.find('.commentthread_comment_timestamp').attr('data-timestamp'))
1276
+ const steamID = SteamUser.miniprofile2SteamID64(miniprofile)
1277
+
1278
+ comments.push({
1279
+ id: commentID,
1280
+ content,
1281
+ timestamp,
1282
+ author: {
1283
+ steamID,
1284
+ profileURL,
1285
+ customURL: SteamUser.getCustomURL_from_ProfileURL(profileURL),
1286
+ miniprofile,
1287
+ name,
1288
+ avatar,
1289
+ avatarHash,
1290
+ }
1291
+ })
1292
+ })
1293
+ return comments
1294
+ }
1295
+
1296
+ async getMyComments() {
1297
+ return await this.getUserComments()
1298
+ }
1299
+
1300
+ async getUserComments(steamID = this.getSteamidUser()) {
1301
+ const result = await this._httpRequest(`comment/Profile/render/${steamID}/-1/`)
1302
+ if(!result.data) {
1303
+ return []
1304
+ }
1305
+ const {
1306
+ total_count,
1307
+ pagesize,
1308
+ comments_html
1309
+ } = result.data
1310
+
1311
+ let comments = this._parseComments(comments_html)
1312
+ if(comments.length < total_count) {
1313
+ let i = comments.length
1314
+ const queue = []
1315
+ while (i <= total_count) {
1316
+ queue.push(this._httpRequest({
1317
+ url: `comment/Profile/render/${steamID}/-1/`,
1318
+ data: {
1319
+ start: Math.min(i, total_count),
1320
+ totalcount: total_count,
1321
+ count: pagesize,
1322
+ feature2: -1,
1323
+ },
1324
+ method: 'POST'
1325
+ }))
1326
+ i += pagesize
1327
+ }
1328
+ const results = await Promise.all(queue)
1329
+ comments = comments.concat(this._parseComments(results
1330
+ .filter(result => result?.data?.comments_html)
1331
+ .map(result => result.data.comments_html.trim())
1332
+ .join('')))
1333
+ }
1334
+
1335
+ return comments
1336
+ }
1337
+
1338
+ static _formatGroupLink({
1339
+ link,
1340
+ gid,
1341
+ groupURL,
1342
+ page,
1343
+ content_only,
1344
+ searchKey,
1345
+ pathname = '',
1346
+ }) {
1347
+ searchKey = searchKey?.trim?.()
1348
+ if(!link) {
1349
+ link = gid ? `gid/${gid}/?p=${page}` : `groups/${groupURL}/?p=${page}`
1350
+ }
1351
+ link = new Url(link)
1352
+ link.query = '?' + [...link.query.removePrefix('?')
1353
+ .split('&')
1354
+ .map(q => q.split('='))
1355
+ .filter(q => !['searchKey', 'content_only'].includes(q[0]))
1356
+ .map(q => q.join('=')), !!content_only && `content_only=true`, !!searchKey && `searchKey=${encodeURIComponent(searchKey)}`]
1357
+ .filter(Boolean).join('&')
1358
+
1359
+ if(pathname) {
1360
+ let _pathname = link.pathname.removeSuffix('/')
1361
+ if(!_pathname.endsWith(pathname)) {
1362
+ link.pathname = _pathname + '/' + pathname + '/'
1363
+ }
1364
+ }
1365
+
1366
+ Object.assign(link, _.pick((new Url(SteamcommunityURL)), ['slashes', 'host', 'hostname', 'origin', 'protocol']))
1367
+ if(link.pathname && !link.pathname.startsWith('/')) {
1368
+ link.pathname = '/' + link.pathname
1369
+ }
1370
+
1371
+ return link.toString()
1372
+ }
1373
+
1374
+ static _parseGroupInfo(html) {
1375
+ if(!html) {
1376
+ return null
1377
+ }
1378
+
1379
+ let nextPage = null
1380
+ let prevPage = null
1381
+ let nextPageLink = null
1382
+ let totalPages = -1
1383
+ const $ = cheerio.load(html)
1384
+ let gid
1385
+ if(html.includes('InitializeCommentThread') && html.includes('https://steamcommunity.com/comment/Clan')) {
1386
+ gid = html.substringBetweenOrNull(`InitializeCommentThread( "Clan", "Clan_`, `",`)
1387
+ }
1388
+ if(!gid) {
1389
+ gid = $('#reportAbuseModalContents form input[name="abuseID"]').val()
1390
+ }
1391
+ if(!gid) {
1392
+ gid = $('.content .joinchat_bg').attr('onclick')?.substringBetweenOrNull(`'`, `'`)
1393
+ }
1394
+
1395
+ $('.grouppage_header_abbrev').remove()
1396
+ const groupName = html.substringBetweenOrNull(`g_strGroupName = "`, `";`) || $('.grouppage_header_name').text()?.trim()
1397
+
1398
+ let _groupURL = html.substringBetweenOrNull(`InitGroupPage( 'https://steamcommunity.com/groups/`, `',`) || $('#group_tab_overview').attr('href')?.substringAfter('https://steamcommunity.com/groups/') || $('#searchEditForm[action]').attr('action')?.substringAfter('https://steamcommunity.com/groups/')?.substringBefore('/')
1399
+ _groupURL = _groupURL?.removeSuffix('/')
1400
+ const headline = $('.maincontent .group_summary > h1').text()?.trim()
1401
+
1402
+ let summary = $('.maincontent .group_summary .formatted_group_summary').html()?.replaceAll(/[\t\n\r]/gi, '')
1403
+ for (let i = 1; i < SteamImageCDN.length; i++) {
1404
+ summary = summary.replaceAll(SteamImageCDN[i], SteamImageCDN[0])
1405
+ }
1406
+
1407
+ const avatarHash = SteamUser.GetAvatarHashFromURL($('.grouppage_logo img[src]').attr('src') || $('.grouppage_resp_logo img[src]').attr('src'))
1408
+ let memberCount = [...$('.group_paging')]
1409
+ .map(g => $(g).text()?.trim())
1410
+ .find(text => text?.endsWith('Members'))
1411
+ ?.substringBeforeLast('Members')
1412
+ ?.trim()
1413
+ if(memberCount) {
1414
+ if(memberCount.includes('of')) {
1415
+ memberCount = memberCount.substringAfterLast('of')
1416
+ }
1417
+ memberCount = parseInt(memberCount.trim().replaceAll(',', ''))
1418
+ }
1419
+
1420
+ const membersInChat = SteamUser._formatString2Int($($('.joinchat_bg .joinchat_membercount .count')[0]).text())
1421
+
1422
+ const groupmemberstat = [...$('.membercount')].map(groupstat => ({
1423
+ label: $(groupstat).find('.label').text()?.trim(),
1424
+ className: $(groupstat).attr('class')?.split(' ') || [],
1425
+ count: SteamUser._formatString2Int($(groupstat).find('.count').text()),
1426
+ }))
1427
+
1428
+ const memberDetailCount = groupmemberstat.find(({className}) => className.includes('members'))?.count
1429
+ const membersInGame = groupmemberstat.find(({className}) => className.includes('ingame'))?.count
1430
+ const membersOnline = groupmemberstat.find(({className}) => className.includes('online'))?.count
1431
+
1432
+ const startingMember = -1
1433
+ const members = []
1434
+ const groupstat = [...$('.groupstat')].map(groupstat => ({
1435
+ label: $(groupstat).find('.label').text()?.trim(),
1436
+ data: $(groupstat).find('.data').text()?.trim(),
1437
+ }))
1438
+ const foundedStr = groupstat.find(({label}) => label?.toLowerCase() === 'Founded'.toLowerCase())?.data
1439
+ let founded
1440
+ if((founded = moment(foundedStr, 'DD MMMM, YYYY')).isValid()) {
1441
+ founded = founded.valueOf()
1442
+ }
1443
+ else {
1444
+ founded = null
1445
+ }
1446
+ const language = groupstat.find(({label}) => label?.toLowerCase() === 'Language'.toLowerCase())?.data
1447
+ const location = groupstat.find(({label}) => label?.toLowerCase() === 'Location'.toLowerCase())?.data
1448
+
1449
+ $(`#memberList > .member_block`).each(function () {
1450
+ const el = $(this)
1451
+ let linkFriend = el.find(`a.linkFriend`)
1452
+ let avatarHash = SteamUser.GetAvatarHashFromURL(el.find(`a > img`).attr('src'))
1453
+ const link = linkFriend.attr('href')
1454
+ const name = linkFriend.text()
1455
+ const miniprofile = parseInt(el.attr('data-miniprofile'))
1456
+ const steamID = SteamUser.miniprofile2SteamID64(miniprofile)
1457
+
1458
+ const rank_icon = el.find('> .rank_icon[title]').attr('title')?.trim()
1459
+ // const rank_icon_src = el.find('> .rank_icon img').attr('src')?.toLowerCase()?.substringBetweenOrNull(`/images/skin_1/`, `.`)
1460
+ const rank = rank_icon && (Object.entries(EGroupRank)
1461
+ .find(([key, ranks]) => ranks.some(rank => rank.toLowerCase() === rank_icon.toLowerCase())))?.[0]
1462
+
1463
+
1464
+ members.push({
1465
+ link,
1466
+ steamID,
1467
+ miniprofile,
1468
+ name,
1469
+ avatarHash,
1470
+ customURL: SteamUser.getCustomURL_from_ProfileURL(link),
1471
+ rank_icon,
1472
+ rank, // rank_icon_src,
1473
+ })
1474
+ })
1475
+
1476
+ $('.pagebtn[href]').each(function () {
1477
+ const el = $(this)
1478
+ const text = el.text()?.trim()
1479
+ const _page = parseInt(el.attr('href').substringAfter('p=')) || -1
1480
+
1481
+ if(_page) {
1482
+ totalPages = Math.max(totalPages, _page)
1483
+ }
1484
+
1485
+ if(text === '>') {
1486
+ const _nextPageLink = el.attr('href')?.replace(`#members`, `/members`)
1487
+ const _nextPage = parseInt(_nextPageLink.substringAfter('p='))
1488
+ if(!isNaN(_nextPage)) {
1489
+ nextPage = _nextPage
1490
+ nextPageLink = _nextPageLink
1491
+ }
1492
+ }
1493
+ else if(text === '<') {
1494
+ const _prevPage = parseInt(el.attr('href').substringAfter('p='))
1495
+ if(!isNaN(_prevPage)) {
1496
+ prevPage = _prevPage
1497
+ }
1498
+ }
1499
+ })
1500
+
1501
+ const g_strLanguage = html.substringBetweenOrNull(`g_strLanguage = "`, `";`)
1502
+
1503
+ return {
1504
+ id: gid,
1505
+ name: groupName,
1506
+ url: _groupURL,
1507
+ headline: headline,
1508
+ summary: summary,
1509
+ avatarHash: avatarHash,
1510
+ memberCount: memberCount,
1511
+ memberDetailCount: memberDetailCount,
1512
+ membersInChat: membersInChat,
1513
+ membersInGame: membersInGame,
1514
+ membersOnline: membersOnline,
1515
+ totalPages: totalPages,
1516
+ currentPage: nextPage - 1,
1517
+ nextPage: nextPage,
1518
+ nextPageLink: nextPageLink,
1519
+ startingMember: startingMember,
1520
+ members: members,
1521
+ founded: founded,
1522
+ foundedStr: foundedStr,
1523
+ language: language,
1524
+ location: location,
1525
+ g_strLanguage,
1526
+ }
1527
+ }
1528
+
1529
+ async getGroupOverview({
1530
+ groupURL,
1531
+ gid,
1532
+ page = 1,
1533
+ content_only,
1534
+ searchKey,
1535
+ }) {
1536
+ return await SteamUser.getGroupOverview({
1537
+ groupURL,
1538
+ gid,
1539
+ page,
1540
+ cookie: this.getCookies(),
1541
+ content_only,
1542
+ searchKey,
1543
+ })
1544
+ }
1545
+
1546
+ static async getGroupOverview({
1547
+ groupURL,
1548
+ gid,
1549
+ page = 1,
1550
+ cookie,
1551
+ link,
1552
+ content_only,
1553
+ searchKey,
1554
+ }) {
1555
+ link = SteamUser._formatGroupLink({
1556
+ link,
1557
+ gid,
1558
+ groupURL,
1559
+ page,
1560
+ content_only,
1561
+ searchKey,
1562
+ })
1563
+ const result = await request({
1564
+ baseURL: SteamcommunityURL,
1565
+ url: link,
1566
+ headers: {...cookie && {cookie}},
1567
+ })
1568
+ return SteamUser._parseGroupInfo(result?.data)
1569
+ }
1570
+
1571
+ async getGroupMembers({
1572
+ groupURL,
1573
+ gid,
1574
+ page = 1,
1575
+ link,
1576
+ content_only,
1577
+ searchKey,
1578
+ }) {
1579
+ return await SteamUser.getGroupMembers({
1580
+ groupURL,
1581
+ gid,
1582
+ page,
1583
+ cookie: this.getCookies(),
1584
+ link,
1585
+ content_only,
1586
+ searchKey,
1587
+ })
1588
+ }
1589
+
1590
+ static async getGroupMembers({
1591
+ groupURL,
1592
+ gid,
1593
+ page = 1,
1594
+ cookie,
1595
+ link,
1596
+ content_only,
1597
+ searchKey,
1598
+ }) {
1599
+ link = SteamUser._formatGroupLink({
1600
+ link,
1601
+ gid,
1602
+ groupURL,
1603
+ page,
1604
+ content_only,
1605
+ searchKey,
1606
+ pathname: 'members'
1607
+ })
1608
+
1609
+ const result = await request({
1610
+ baseURL: SteamcommunityURL,
1611
+ url: link,
1612
+ headers: {...cookie && {cookie}},
1613
+ })
1614
+ return SteamUser._parseGroupInfo(result?.data)
1615
+ }
1616
+
1617
+ async getGroupMembersFull({
1618
+ groupURL,
1619
+ gid,
1620
+ cookie,
1621
+ cbOnMember,
1622
+ cbOnMembers,
1623
+ content_only,
1624
+ searchKey,
1625
+ }) {
1626
+ return await SteamUser.getGroupMembersFull({
1627
+ groupURL,
1628
+ gid,
1629
+ cookie: this.getCookies(),
1630
+ cbOnMember,
1631
+ cbOnMembers,
1632
+ content_only,
1633
+ searchKey,
1634
+ })
1635
+ }
1636
+
1637
+ static async getGroupMembersFull({
1638
+ groupURL,
1639
+ gid,
1640
+ cookie,
1641
+ cbOnMember,
1642
+ cbOnMembers,
1643
+ content_only,
1644
+ searchKey,
1645
+ }) {
1646
+ let group
1647
+ do {
1648
+ group = await SteamUser.getGroupMembers({
1649
+ ...(group?.nextPageLink ? {link: group.nextPageLink} : {
1650
+ gid: gid,
1651
+ groupURL: groupURL
1652
+ }),
1653
+ cookie,
1654
+ content_only,
1655
+ searchKey,
1656
+ })
1657
+ if(Array.isArray(group?.members)) {
1658
+ typeof cbOnMembers === 'function' && cbOnMembers(group.members)
1659
+ typeof cbOnMember === 'function' && group.members.forEach(cbOnMember)
1660
+ }
1661
+ } while (group?.nextPageLink)
1662
+ }
1663
+
1664
+ static async getGroupInfoXML({
1665
+ groupURL,
1666
+ gid,
1667
+ page = 1,
1668
+ link,
1669
+ }) {
1670
+ if(!link) {
1671
+ link = gid ? `/gid/${gid}/memberslistxml/?xml=1&p=${page}` : `/groups/${groupURL}/memberslistxml/?xml=1&p=${page}`
1672
+ }
1673
+ const resultXml = await request({
1674
+ baseURL: SteamcommunityURL,
1675
+ url: link,
1676
+ })
1677
+ if(!resultXml?.data) {
1678
+ return
1679
+ }
1680
+ if(resultXml.status === 503) {
1681
+ console.log('Service Unavailable')
1682
+ return
1683
+ }
1684
+ if(resultXml.status === 429) {
1685
+ console.log('Too Many Requests')
1686
+ return
1687
+ }
1688
+ const result = getJSObjectFronXML(resultXml.data)?.memberList
1689
+ if(!result) {
1690
+ return
1691
+ }
1692
+ return {
1693
+ id: result.groupID64,
1694
+ name: result.groupDetails.groupName,
1695
+ url: result.groupDetails.groupURL,
1696
+ headline: result.groupDetails.headline,
1697
+ summary: result.groupDetails.summary,
1698
+ avatarHash: SteamUser.GetAvatarHashFromMultipleURL([result.groupDetails.avatarIcon, result.groupDetails.avatarMedium, result.groupDetails.avatarFull]),
1699
+ memberCount: SteamUser._formatString2Int(result.memberCount),
1700
+ memberDetailCount: SteamUser._formatString2Int(result.groupDetails.memberCount),
1701
+ membersInChat: SteamUser._formatString2Int(result.groupDetails.membersInChat),
1702
+ membersInGame: SteamUser._formatString2Int(result.groupDetails.membersInGame),
1703
+ membersOnline: SteamUser._formatString2Int(result.groupDetails.membersOnline),
1704
+ totalPages: SteamUser._formatString2Int(result.totalPages),
1705
+ currentPage: SteamUser._formatString2Int(result.currentPage),
1706
+ startingMember: SteamUser._formatString2Int(result.startingMember),
1707
+ nextPageLink: result.nextPageLink,
1708
+ members: result.members.steamID64,//['76561198072779605', '76561198017622621']
1709
+ }
1710
+ }
1711
+
1712
+ static async getGroupInfoXMLFull({
1713
+ groupURL,
1714
+ gid,
1715
+ cbOnMembers,
1716
+ }) {
1717
+ let group = null,
1718
+ members = [],
1719
+ groupInfo = null
1720
+ do {
1721
+ group = await SteamUser.getGroupInfoXML({
1722
+ ...(group?.nextPageLink ? {link: group.nextPageLink} : {
1723
+ gid: gid,
1724
+ groupURL: groupURL,
1725
+ }),
1726
+ })
1727
+ if(group && !groupInfo) {
1728
+ groupInfo = group
1729
+ }
1730
+ if(Array.isArray(group?.members)) {
1731
+ members = members.concat(group?.members)
1732
+ typeof cbOnMembers === 'function' && cbOnMembers(group.members)//list of steam id
1733
+ }
1734
+ } while (group?.nextPageLink)
1735
+ return {
1736
+ ...groupInfo,
1737
+ nextPageLink: undefined,
1738
+ members,
1739
+ }
1740
+ }
1741
+
1742
+ async _parseFriendList(result) {
1743
+ const $ = cheerio.load(result?.data || result || '')
1744
+ const results = []
1745
+ const friendList = $(`.persona[data-miniprofile]`)
1746
+ if(!friendList || !friendList.length) {
1747
+ return []
1748
+ }
1749
+
1750
+ friendList.each(function () {
1751
+ const el = $(this)
1752
+ const profileURL = el.find('a.selectable_overlay[data-container]').attr('href') || el.find('a.friendBlockLinkOverlay').attr('href')
1753
+ let friendBlockContent = el.find(`.friend_block_content, .friendBlockContent`)
1754
+ const playerAvatar = el.find(`.player_avatar, .playerAvatar`).find('img')
1755
+ const avatarHash = SteamUser.GetAvatarHashFromURL(playerAvatar.attr('src'))
1756
+ const avatar = SteamUser.GetAvatarURLFromHash(avatarHash, 'full')
1757
+
1758
+ const isNickname = !!friendBlockContent.find('.player_nickname_hint').length
1759
+
1760
+ const game = friendBlockContent.find(`.friend_game_link, .linkFriend_in-game`)
1761
+ .text()
1762
+ .replaceAll(/\s+/g, ' ')
1763
+ .trim()
1764
+ .removePrefix('In-Game')
1765
+ .trim()
1766
+ friendBlockContent.find(`.friend_small_text, .friendSmallText`).remove()
1767
+
1768
+ const lastOnline = friendBlockContent.find(`.friend_last_online_text`).text().replaceAll(/\s+/g, ' ').trim()
1769
+ friendBlockContent.find(`.friend_last_online_text`).remove()
1770
+
1771
+ let username = friendBlockContent.text().trim()
1772
+ if(username === '[deleted]') {
1773
+ return
1774
+ }
1775
+
1776
+ const miniprofile = parseInt(el.attr('data-miniprofile'))
1777
+ const steamId = SteamUser.miniprofile2SteamID64(miniprofile)
1778
+ let score = ''
1779
+
1780
+ let onlineStatus = ''
1781
+ if(el.hasClass('in-game')) {
1782
+ onlineStatus = 'ingame'
1783
+ }
1784
+ else if(el.hasClass('online')) {
1785
+ onlineStatus = 'online'
1786
+ }
1787
+ else if(el.hasClass('offline')) {
1788
+ onlineStatus = 'offline'
1789
+ }
1790
+
1791
+ results.push({
1792
+ username,
1793
+ steamId,
1794
+ game,
1795
+ onlineStatus,
1796
+ lastOnline,
1797
+ miniprofile,
1798
+ score,
1799
+ isNickname,
1800
+ avatar,
1801
+ avatarHash,
1802
+ profileURL,
1803
+ customURL: SteamUser.getCustomURL_from_ProfileURL(profileURL),
1804
+ })
1805
+ })
1806
+
1807
+ return results
1808
+ }
1809
+
1810
+ async getFollowingPlayersList(steamID = this.getSteamidUser()) {
1811
+ return (await this._parseFriendList((await this._httpRequest(`${this.getSteamUserProfileURL(steamID)}/following/`)).data))
1812
+ }
1813
+
1814
+ async getFriendsList(steamID = this.getSteamidUser()) {
1815
+ return (await this._parseFriendList((await this._httpRequest(`${this.getSteamUserProfileURL(steamID)}/friends/`)).data))
1816
+ }
1817
+
1818
+ async getMyFriendsList() {
1819
+ return await this.getFriendsList()
1820
+ }
1821
+
1822
+ async getMyFriendsIDList() {
1823
+ const {data} = await this._httpRequestAjax(`textfilter/ajaxgetfriendslist`)
1824
+ if(!data) {
1825
+ return []
1826
+ }
1827
+ return data.friendslist.friends.filter(function (friend) {
1828
+ return EFriendRelationship.Friend === friend.efriendrelationship
1829
+ }).map(function (friend) {
1830
+ return friend.ulfriendid
1831
+ })
1832
+ }
1833
+
1834
+ async getMyFollowingPlayersList() {
1835
+ const steamID = this.getSteamidUser()
1836
+ return await this.getFollowingPlayersList(steamID)
1837
+ }
1838
+
1839
+ async getMatchHistory_initial(matchHistoryType) {
1840
+ if(!matchHistoryType) {
1841
+ return
1842
+ }
1843
+ let result = await this._httpRequest(`${this.getMySteamUserProfileURL()}/gcpd/${AppID_CSGO}/?tab=${matchHistoryType}`)
1844
+ if(result?.data) {
1845
+ const matches = this._parseMatchHistory(result?.data)
1846
+ const continue_token = result.data.substringBetweenOrNull('var g_sGcContinueToken =', ';').trim().removeSurrounding('\'').trim()
1847
+ const continue_text = result.data.substringBetweenOrNull('load_more_button_continue_text" class="returnLink">', '</div>')
1848
+ return {
1849
+ continue_token,
1850
+ continue_text,
1851
+ matches
1852
+ }
1853
+ }
1854
+ else {
1855
+ return {
1856
+ continue_token: '',
1857
+ continue_text: '',
1858
+ matches: []
1859
+ }
1860
+ }
1861
+ }
1862
+
1863
+ async getClientJsToken() {
1864
+ let result = (await this._httpRequest('chat/clientjstoken')).data
1865
+
1866
+ const dataExample = {
1867
+ logged_in: true,
1868
+ steamid: '76561199040402348',
1869
+ accountid: 1080136620,
1870
+ account_name: 'henry22230',
1871
+ token: '1o7xYpbcmTsAAAAAAAAAAAAAAAAAAAAAAwCf6fVEGxM9H7Tnk5oW5QPI'
1872
+ }
1873
+
1874
+ return Object.assign(result, {
1875
+ steamID: new SteamID(result.steamid),
1876
+ accountName: result.account_name,
1877
+ webLogonToken: result.token
1878
+ })
1879
+ }
1880
+
1881
+ getClientLogonToken = this.getClientJsToken
1882
+
1883
+ async _getHistoryMatches(matchHistoryType, token) {
1884
+ const _self = this
1885
+ // console_log(`fetchIt ${token} ${matchHistoryType}`)
1886
+ let result,
1887
+ i = MAX_RETRY
1888
+ while (result?.data?.success !== true && i--) {
1889
+ result = await _self._httpRequest(`${this.getMySteamUserProfileURL()}/gcpd/${AppID_CSGO}?ajax=1&tab=${matchHistoryType}&continue_token=${token}&sessionid=${_self._sessionid}`)
1890
+ }
1891
+ if(!result.data) {
1892
+ return null
1893
+ }
1894
+ const {
1895
+ continue_token,//3568892337192960000
1896
+ continue_text,//2022-08-29
1897
+ html,
1898
+ success,//true, false
1899
+ } = result.data
1900
+
1901
+ const matches = _self._parseMatchHistory(html)
1902
+
1903
+ return {
1904
+ matches,
1905
+ continue_token,
1906
+ continue_text,
1907
+ }
1908
+ }
1909
+
1910
+ _parseMatchHistory(html) {
1911
+ return SteamUser._parseMatchHistory(html, this._miniprofile_user)
1912
+ }
1913
+
1914
+ static _parseMatchHistory(html, myMiniProfile) {
1915
+ const matches = []
1916
+ if(!html) {
1917
+ return matches
1918
+ }
1919
+
1920
+ const $ = cheerio.load(html
1921
+ .replaceAll(/[\t\n\r]/gi, '')
1922
+ .trim())
1923
+
1924
+ $('table.csgo_scoreboard_inner_right').each(function () {
1925
+ const table = $(this)
1926
+
1927
+ const matchesEl = table.parents('tr')
1928
+ const matchInfo = {}
1929
+
1930
+ matchesEl.find('td.val_left table.csgo_scoreboard_inner_left tr > td').each(function (index, tdEl) {
1931
+ tdEl = $(tdEl)
1932
+ const text = tdEl.text().trim()
1933
+
1934
+ if(text.startsWith('Competitive ')) {
1935
+ matchInfo.map = text.substringAfter('Competitive ')
1936
+ }
1937
+ else if(text.endsWith(' GMT')) {
1938
+ matchInfo.time = text
1939
+ }
1940
+ else if(text.startsWith('Wait Time: ')) {
1941
+ matchInfo.waitTime = text.substringAfter('Wait Time: ')
1942
+ }
1943
+ else if(text.startsWith('Match Duration: ')) {
1944
+ matchInfo.duration = text.substringAfter('Match Duration: ')
1945
+ }
1946
+ else if(text === 'Download GOTV Replay') {
1947
+ matchInfo.GOTV_Replay = tdEl.find('a').attr('href')
1948
+ }
1949
+ else if(text.startsWith('Viewers: ')) {
1950
+ matchInfo.viewers = parseInt(text.substringAfter('Viewers: '))
1951
+ }
1952
+ else if(text.startsWith('Ranked: Yes')) {
1953
+ matchInfo.ranked = true
1954
+ }
1955
+ else {
1956
+ console_log(text)
1957
+ }
1958
+ })
1959
+ matchInfo.ranked = !!matchInfo.ranked
1960
+
1961
+ const historyTable = table2json($, table, function ($, el, isHeader) {
1962
+ el = $(el)
1963
+ if(!isHeader && el.attr('class') === 'inner_name') {
1964
+ const link = el.find('a.linkTitle').attr('href')
1965
+ return {
1966
+ avatarHash: SteamUser.GetAvatarHashFromURL(el.find('.playerAvatar a > img[src]').attr('src')),
1967
+ name: el.text().trim(),
1968
+ link,
1969
+ miniprofile: el.find('a.linkTitle').data('miniprofile'),
1970
+ customURL: SteamUser.getCustomURL_from_ProfileURL(link),
1971
+ }
1972
+ }
1973
+ return el.text().trim()
1974
+ })
1975
+
1976
+ if(historyTable.length !== 11) {
1977
+ return
1978
+ }
1979
+ //historyTable should be array of 11 elements
1980
+ const scoreboard_score = historyTable[5]['Player Name'].split(':').map(c => parseInt(c.trim()))
1981
+ const ctScore = scoreboard_score[0]
1982
+ const tScore = scoreboard_score[1]
1983
+ const winningTeam = ctScore > tScore ? 'ct' : (ctScore < tScore ? 't' : null)
1984
+ const players = []
1985
+
1986
+ for (let i = 0; i < 11; i++) {
1987
+ if(i === 5) {
1988
+ continue
1989
+ }
1990
+ const player = historyTable[i]
1991
+ player.team = i < 5 ? 'ct' : 't'
1992
+ player.isWin = winningTeam === player.team
1993
+ player.isTie = winningTeam === null
1994
+ player.isLose = !player.isWin && !player.isTie
1995
+ player.MatchWinLose = player.isWin ? MatchWinLose.Win : (player.isTie ? MatchWinLose.Tie : MatchWinLose.Lose)
1996
+
1997
+ for (const key of ['Ping', 'K', 'A', 'D', 'Score']) {
1998
+ if(!isNaN(parseInt(player[key]))) {
1999
+ player[key] = parseInt(player[key])
2000
+ }
2001
+ }
2002
+
2003
+ const MVP = player['★']
2004
+ delete player['★']
2005
+ if(MVP === '') {
2006
+ player.MVP = 0
2007
+ }
2008
+ else if(MVP === '★') {
2009
+ player.MVP = 1
2010
+ }
2011
+ else if(MVP.startsWith('★')) {
2012
+ player.MVP = parseInt(MVP.replaceAll('★', ''))
2013
+ }
2014
+ else {
2015
+ //error
2016
+ console.log(MVP)
2017
+ }
2018
+
2019
+ player.HSP = player.HSP === '' ? null : parseInt(player.HSP.replaceAll('%', ''))
2020
+
2021
+ const _player = {
2022
+ ...player,
2023
+ name: player['Player Name'].name,
2024
+ link: player['Player Name'].link,
2025
+ customURL: player['Player Name'].customURL,
2026
+ steamID: SteamUser.miniprofile2SteamID64(player['Player Name'].miniprofile),
2027
+ miniprofile: player['Player Name'].miniprofile,
2028
+ isMe: player['Player Name'].miniprofile === myMiniProfile,
2029
+ avatarHash: player['Player Name'].avatarHash,
2030
+ }
2031
+ delete _player['Player Name']
2032
+
2033
+ players.push(_player)
2034
+ }
2035
+
2036
+ const myTeam = players.find(p => p.isMe)?.team
2037
+ myTeam && players.forEach(p => p.isTeammate = p.team === myTeam && !p.isMe)
2038
+
2039
+ const matchIDs = [matchInfo.map, matchInfo.time, matchInfo.waitTime, matchInfo.duration]
2040
+ const nonHashMatchID = matchIDs.join('').replaceAll(/[^a-zA-Z0-9]/gi, '')
2041
+ const matchID = sha256(nonHashMatchID).toString()
2042
+
2043
+ matches.push({
2044
+ id: matchID,
2045
+ matchInfo,
2046
+ players,
2047
+ })
2048
+ })
2049
+
2050
+ return matches
2051
+ }
2052
+
2053
+ async getFullHistoryMatches({
2054
+ matchHistoryTypes = [],
2055
+ cbOnMatch = null,
2056
+ cbOnMatches = null,
2057
+ maxPage = null,
2058
+ Started_playing_CS_GO = null,
2059
+ shouldStop = function ({
2060
+ maxPage,
2061
+ currentPage,
2062
+ continue_token,
2063
+ continue_text,
2064
+ matchHistoryType,
2065
+ }) {
2066
+ if(maxPage === null) {
2067
+ return false
2068
+ }
2069
+ else {
2070
+ return currentPage > maxPage
2071
+ }
2072
+ }
2073
+ }) {
2074
+
2075
+ if(!Array.isArray(matchHistoryTypes)) {
2076
+ matchHistoryTypes = [matchHistoryTypes]
2077
+ }
2078
+
2079
+ const results = {}
2080
+ if(maxPage === null && Started_playing_CS_GO === null) {
2081
+ Started_playing_CS_GO = (await this.getPersonalGameDataAccountInformation())?.Started_playing_CS_GO
2082
+ }
2083
+ if(!Started_playing_CS_GO || !(Started_playing_CS_GO = moment(Started_playing_CS_GO.replace('GMT', '+00'), `YYYY-MM-DD HH:mm:ss Z`)).isValid()) {
2084
+ Started_playing_CS_GO = null
2085
+ }
2086
+ for (const matchHistoryType of matchHistoryTypes) {
2087
+ results[matchHistoryType] = await this._getFullHistoryMatches({
2088
+ matchHistoryType,
2089
+ cbOnMatch,
2090
+ cbOnMatches,
2091
+ maxPage,
2092
+ Started_playing_CS_GO,
2093
+ shouldStop
2094
+ })
2095
+ }
2096
+ return results
2097
+ }
2098
+
2099
+ async _getFullHistoryMatches({
2100
+ matchHistoryType = '',
2101
+ cbOnMatch,
2102
+ cbOnMatches,
2103
+ maxPage,
2104
+ Started_playing_CS_GO,
2105
+ shouldStop
2106
+ }) {
2107
+
2108
+ let result = null,
2109
+ page = 0,
2110
+ stop = false
2111
+ const matches = []
2112
+
2113
+ do {
2114
+ result = await (page++ ? this._getHistoryMatches(matchHistoryType, result?.continue_token) : this.getMatchHistory_initial(matchHistoryType))
2115
+ stop = shouldStop({
2116
+ maxPage,
2117
+ currentPage: page,
2118
+ continue_token: result.continue_token,
2119
+ continue_text: result.continue_text,
2120
+ matchHistoryType,
2121
+ })
2122
+
2123
+ if(result.continue_text && Started_playing_CS_GO && moment(result.continue_text, 'YYYY-MM-DD').isBefore(Started_playing_CS_GO)) {
2124
+ stop = true
2125
+ }
2126
+
2127
+ if(!result.continue_token) {
2128
+ stop = true
2129
+ }
2130
+
2131
+ Array.isArray(result.matches) && result.matches.length && typeof cbOnMatches === 'function' && await cbOnMatches(result.matches)
2132
+
2133
+ for (const match of result.matches) {
2134
+ if(match.players.length) {
2135
+ matches.push(match)
2136
+ typeof cbOnMatch === 'function' && await cbOnMatch(match)
2137
+ }
2138
+ }
2139
+ } while (!stop)
2140
+
2141
+ return matches
2142
+ }
2143
+
2144
+ //{ success: 1 } means success
2145
+ //{ success: 2 }
2146
+ async followUser(steamID) {
2147
+ const {data} = await this._httpRequest({
2148
+ url: `${this.getSteamUserProfileURL(steamID)}/followuser/`,
2149
+ method: 'POST',
2150
+ })
2151
+
2152
+ return data
2153
+ }
2154
+
2155
+ //from profile
2156
+ async unfollowUser(steamID) {
2157
+ const {data} = await this._httpRequest({
2158
+ url: `${this.getSteamUserProfileURL(steamID)}/unfollowuser/`,
2159
+ method: 'POST',
2160
+ })
2161
+
2162
+ return data
2163
+ }
2164
+
2165
+ //from following manager
2166
+ async unfollowUsers(steamIDs) {
2167
+ const formData = new URLSearchParams()
2168
+ formData.append('steamid', this.getSteamidUser())
2169
+ formData.append('ajax', '1')
2170
+ formData.append('action', 'unfollow')
2171
+ steamIDs.forEach(steamID => {
2172
+ formData.append('steamids[]', steamID.toString())
2173
+ })
2174
+
2175
+ const {data} = await this._httpRequestAjax({
2176
+ url: `${this.getMySteamUserProfileURL()}/friends/action`,
2177
+ method: 'POST',
2178
+ data: formData,
2179
+ })
2180
+
2181
+ return data
2182
+ }
2183
+
2184
+ //from following manager
2185
+ async unfollowAllFollowUsers() {
2186
+ const friends = await this.getMyFollowingPlayersList()
2187
+ return await this.unfollowUsers(friends.map(r => r.steamId))
2188
+ }
2189
+
2190
+ //true, false boolean
2191
+ async blockCommunicationUser(steamID) {
2192
+ const {data} = await this._httpRequest({
2193
+ url: `actions/BlockUserAjax`,
2194
+ data: {
2195
+ steamid: steamID,
2196
+ block: 1,
2197
+ },
2198
+ method: 'POST',
2199
+ })
2200
+
2201
+ return data
2202
+ }
2203
+
2204
+ //true, false boolean
2205
+ async unblockCommunicationUser(steamID) {
2206
+ const {data} = await this._httpRequest({
2207
+ url: `actions/BlockUserAjax`,
2208
+ data: {
2209
+ steamid: steamID,
2210
+ block: 0,
2211
+ },
2212
+ method: 'POST',
2213
+ })
2214
+
2215
+ return data
2216
+ }
2217
+
2218
+
2219
+ //{ invited: [ '76561199277912057' ], success: 1 }
2220
+ async addFriendUser(steamID) {
2221
+ const {data} = await this._httpRequest({
2222
+ url: `actions/AddFriendAjax`,
2223
+ data: {
2224
+ steamid: steamID,
2225
+ accept_invite: 0,
2226
+ },
2227
+ method: 'POST',
2228
+ })
2229
+
2230
+ return data
2231
+ }
2232
+
2233
+
2234
+ //true, false boolean
2235
+ async removeFriend(steamID) {
2236
+ const {data} = await this._httpRequest({
2237
+ url: `actions/RemoveFriendAjax`,
2238
+ data: {
2239
+ steamid: steamID
2240
+ },
2241
+ method: 'POST',
2242
+ })
2243
+ return data
2244
+ }
2245
+
2246
+ async acceptFriendRequest(steamID) {
2247
+ const {data} = await this._httpRequest({
2248
+ url: `${this.getMySteamUserProfileURL()}/friends/action`,
2249
+ data: {
2250
+ steamid: this._steamid_user,
2251
+ ajax: 1,
2252
+ action: 'accept',
2253
+ 'steamids[]': steamID,
2254
+ },
2255
+ method: 'POST',
2256
+ })
2257
+ return data
2258
+ const resultExample = {
2259
+ success: 2,
2260
+ rgCounts: {
2261
+ cFriendsPending: 11,
2262
+ cFriendsBlocked: 1,
2263
+ cFollowing: 179,
2264
+ cGroupsPending: 0,
2265
+ cFriends: 183,
2266
+ cGroups: 1,
2267
+ success: 1
2268
+ }
2269
+ }
2270
+ }
2271
+
2272
+ async cancelAddFriendUser(steamID) {
2273
+ return await this.removeFriend(steamID)
2274
+ }
2275
+
2276
+ async ignoreFriendRequest(steamID) {
2277
+ const {data} = await this._httpRequestAjax({
2278
+ url: `actions/IgnoreFriendInviteAjax`,
2279
+ data: {
2280
+ steamid: steamID,
2281
+ },
2282
+ method: 'POST',
2283
+ })
2284
+ return data
2285
+ }
2286
+
2287
+ //{"success":1,"nickname":"new nickname"}
2288
+ async setNickname(steamID, nickname) {
2289
+ const {data} = await this._httpRequestAjax({
2290
+ url: `${this.getSteamUserProfileURL(steamID)}/ajaxsetnickname/`,
2291
+ data: {
2292
+ nickname: nickname,
2293
+ },
2294
+ method: 'POST',
2295
+ })
2296
+ return data
2297
+ }
2298
+
2299
+ //{ success: 1, nickname: '' }
2300
+ async removeNickname(steamID) {
2301
+ return await this.setNickname(steamID, '')
2302
+ }
2303
+
2304
+
2305
+ async getNameHistory(steamID = this.getSteamidUser()) {
2306
+ const {data} = await this._httpRequestAjax({
2307
+ url: `${this.getSteamUserProfileURL(steamID)}/ajaxaliases/`,
2308
+ method: 'POST',
2309
+ })
2310
+ return data
2311
+ const resultExample = [{
2312
+ newname: 'Natri',
2313
+ timechanged: '9 Aug @ 6:26am'
2314
+ }, {
2315
+ newname: 'Natri 1',
2316
+ timechanged: '9 Aug @ 6:26am'
2317
+ }]
2318
+ }
2319
+
2320
+ getPreviousAliases = this.getNameHistory
2321
+
2322
+ async clearPreviousAliases() {
2323
+ const {data} = await this._httpRequestAjax({
2324
+ url: `${this.getMySteamUserProfileURL()}/ajaxclearaliashistory/`,
2325
+ method: 'POST',
2326
+ })
2327
+ return data
2328
+ }
2329
+
2330
+ async setupProfile() {
2331
+ const profile = await this._httpRequest(`/edit?welcomed=1`)
2332
+ if(!profile.data) {
2333
+ return false
2334
+ }
2335
+ const $ = profile._$()
2336
+ return $('title').text() == 'Steam Community :: Edit Profile'
2337
+ }
2338
+
2339
+ async editMyProfile({
2340
+ personaName = null,
2341
+ realName = null,
2342
+ customURL = null,
2343
+ country = null,
2344
+ summary = null,
2345
+ hide_profile_awards = null
2346
+ }) {
2347
+ const existProfile = await this._httpRequest(`/my/edit/info`)
2348
+ if(!existProfile.data) {
2349
+ return
2350
+ }
2351
+ const $ = existProfile._$()
2352
+ const profileEdit = $('#profile_edit_config').data('profile-edit')
2353
+ if(!profileEdit) {
2354
+ return
2355
+ }
2356
+
2357
+ const {data} = await this._httpRequestAjax({
2358
+ url: `${this.getMySteamUserProfileURL()}/edit/`,
2359
+ method: 'POST',
2360
+ data: {
2361
+ type: 'profileSave',
2362
+ weblink_1_title: '',
2363
+ weblink_1_url: '',
2364
+ weblink_2_title: '',
2365
+ weblink_2_url: '',
2366
+ weblink_3_title: '',
2367
+ weblink_3_url: '',
2368
+ personaName: personaName === null ? profileEdit.strPersonaName : personaName,
2369
+ real_name: realName === null ? profileEdit.strRealName : realName,
2370
+ customURL: customURL === null ? profileEdit.strCustomURL : customURL,
2371
+ country: country === null ? profileEdit.LocationData.locCountryCode : country,
2372
+ state: profileEdit.LocationData.locStateCode,
2373
+ city: profileEdit.LocationData.locCityCode,
2374
+ summary: summary || profileEdit.strSummary,
2375
+ hide_profile_awards: hide_profile_awards === null ? profileEdit.ProfilePreferences.hide_profile_awards : (hide_profile_awards ? '1' : '0'),
2376
+ json: 1,
2377
+ },
2378
+ headers: {
2379
+ 'Content-Type': 'multipart/form-data',
2380
+ },
2381
+ })
2382
+ return data
2383
+ const successExample = {
2384
+ success: 1,
2385
+ errmsg: ''
2386
+ }
2387
+ const errorExample = {
2388
+ success: 2,
2389
+ errmsg: 'The profile URL specified is already in use<br />Bad location specified.<br />'
2390
+ }
2391
+ }
2392
+
2393
+ static _formatPrivacySettings2String(privacySettings) {//number to string
2394
+ const Privacy = privacySettings?.data?.Privacy || privacySettings?.Privacy
2395
+ if(!Privacy) {
2396
+ return
2397
+ }
2398
+
2399
+ const _Privacy = Object.entries(Privacy.PrivacySettings).reduce((previousValue, [field, setting]) => ({
2400
+ ...previousValue,
2401
+ [field]: Object.entries(SteamUser._EPrivacyState).find(([, value]) => value == setting)[0]
2402
+ }), {})
2403
+
2404
+ _Privacy.eCommentPermission = Object.entries(SteamUser._ECommentPrivacyState).find(([, value]) => value == Privacy.eCommentPermission)[0]
2405
+
2406
+ return {
2407
+ profile: _Privacy.PrivacyProfile,
2408
+ inventory: _Privacy.PrivacyInventory,
2409
+ inventoryGifts: _Privacy.PrivacyInventoryGifts,
2410
+ gameDetails: _Privacy.PrivacyOwnedGames,
2411
+ playtime: _Privacy.PrivacyPlaytime,
2412
+ friendsList: _Privacy.PrivacyFriendsList,
2413
+ comment: _Privacy.eCommentPermission
2414
+ }
2415
+ }
2416
+
2417
+ static _formatPrivacySettings2Value(privacySettings) {//string to number
2418
+ const _Privacy = Object.entries(privacySettings).reduce((previousValue, [key, value]) => {
2419
+ const mapping = (key === 'comment' ? SteamUser._ECommentPrivacyState : SteamUser._EPrivacyState)
2420
+ //value: string or number
2421
+ let newValue
2422
+ if(Object.hasOwn(mapping, value)) {
2423
+ newValue = mapping[value]
2424
+ }
2425
+ else if(!isNaN(parseInt(value))) {
2426
+ newValue = value
2427
+ }
2428
+ else {
2429
+ throw new Error(`Invalid privacySettings value: ${value}`)
2430
+ }
2431
+
2432
+ return {
2433
+ ...previousValue,
2434
+ [key]: newValue
2435
+ }
2436
+ }, {})
2437
+
2438
+ return {
2439
+ PrivacySettings: {
2440
+ PrivacyProfile: _Privacy.profile,
2441
+ PrivacyInventory: _Privacy.inventory,
2442
+ PrivacyInventoryGifts: _Privacy.inventoryGifts,
2443
+ PrivacyOwnedGames: _Privacy.gameDetails,
2444
+ PrivacyPlaytime: _Privacy.playtime,
2445
+ PrivacyFriendsList: _Privacy.friendsList,
2446
+ },
2447
+ eCommentPermission: _Privacy.comment,
2448
+ }
2449
+ }
2450
+
2451
+ async getPrivacySettings() {
2452
+ const result = await this._httpRequest({
2453
+ url: `${this.getMySteamUserProfileURL()}/edit/settings`,
2454
+ })
2455
+
2456
+ if(!result.data) {
2457
+ return
2458
+ }
2459
+
2460
+ const $ = result._$()
2461
+ const existingSettings = $('#profile_edit_config').data('profile-edit')
2462
+ return SteamUser._formatPrivacySettings2String(existingSettings)
2463
+
2464
+ const successExample = {
2465
+ strPersonaName: 'Birdman',
2466
+ strCustomURL: '',
2467
+ strRealName: '',
2468
+ strSummary: 'No information given.',
2469
+ strAvatarHash: '33915da4fb4ca63a17d6841876fd18d7e30f9585',
2470
+ rtPersonaNameBannedUntil: 0,
2471
+ rtProfileSummaryBannedUntil: 0,
2472
+ rtAvatarBannedUntil: 0,
2473
+ LocationData: {
2474
+ locCountry: '',
2475
+ locCountryCode: '',
2476
+ locState: '',
2477
+ locStateCode: '',
2478
+ locCity: '',
2479
+ locCityCode: ''
2480
+ },
2481
+ ActiveTheme: {
2482
+ theme_id: '',
2483
+ title: '#ProfileTheme_Default'
2484
+ },
2485
+ ProfilePreferences: {
2486
+ hide_profile_awards: 0
2487
+ },
2488
+ rgAvailableThemes: [{
2489
+ theme_id: '',
2490
+ title: '#ProfileTheme_Default'
2491
+ }, {
2492
+ theme_id: 'Summer',
2493
+ title: '#ProfileTheme_Summer'
2494
+ }, {
2495
+ theme_id: 'Midnight',
2496
+ title: '#ProfileTheme_Midnight'
2497
+ }, {
2498
+ theme_id: 'Steel',
2499
+ title: '#ProfileTheme_Steel'
2500
+ }, {
2501
+ theme_id: 'Cosmic',
2502
+ title: '#ProfileTheme_Cosmic'
2503
+ }, {
2504
+ theme_id: 'DarkMode',
2505
+ title: '#ProfileTheme_DarkMode'
2506
+ }],
2507
+ rgGoldenProfileData: [{
2508
+ appid: 1017190,
2509
+ css_url: 'https://community.cloudflare.steamstatic.com/public/css/promo/lny2019/goldenprofile.css?v=MNHyDjqMV1IZ&l=english&_cdn=cloudflare',
2510
+ frame_url: null,
2511
+ miniprofile_background: null,
2512
+ miniprofile_movie: null
2513
+ }, {
2514
+ appid: 1263950,
2515
+ css_url: 'https://community.cloudflare.steamstatic.com/public/css/promo/rewardsseason1/goldenprofile.css?v=.xVjuuVRtFd6T&_cdn=cloudflare',
2516
+ frame_url: 'https://cdn.cloudflare.steamstatic.com/steamcommunity/public/assets/rewardsseason1/goldenprofile/presige_frame_anim.png?v=2',
2517
+ miniprofile_background: 'https://cdn.cloudflare.steamstatic.com/steamcommunity/public/assets/rewardsseason1/goldenprofile/gp_mini_profile_still.png?v=2',
2518
+ miniprofile_movie: {
2519
+ 'video/webm': 'https://cdn.cloudflare.steamstatic.com/steamcommunity/public/assets/rewardsseason1/goldenprofile/summer2020_golden_mini_profile_background.webm?v=2'
2520
+ }
2521
+ },],
2522
+ Privacy: {
2523
+ PrivacySettings: {
2524
+ PrivacyProfile: 'Public',//Your community profile includes your profile summary, friends list, badges, Steam Level, showcases, comments, and group membership.
2525
+ PrivacyInventory: 'Public',//This controls who can see your list of friends on your Steam Community profile.
2526
+ PrivacyInventoryGifts: 'Public',//Always keep Steam Gifts private even if users can see my inventory.
2527
+ PrivacyOwnedGames: 'Public',//Game details
2528
+ PrivacyPlaytime: 'Public',//Always keep my total playtime private even if users can see my game details.
2529
+ PrivacyFriendsList: 'Public'//This controls who can see your list of friends on your Steam Community profile.
2530
+ },
2531
+ eCommentPermission: 'Public'
2532
+ },
2533
+ webapi_token: '38a169cea9b4402fe1a3db093a0f0e1b'
2534
+ }
2535
+ }
2536
+
2537
+ async updatePrivacySettings(privacySettings) {
2538
+ let existingSettings = {}
2539
+ //get missing setting
2540
+ if(Object.keys(PrivacySettings).some(key => !Object.hasOwn(privacySettings, key))) {
2541
+ existingSettings = await this.getPrivacySettings()
2542
+ if(existingSettings) {
2543
+ const isEqual = _.isEqual(_.pick(existingSettings, Object.keys(privacySettings)), privacySettings)
2544
+ if(isEqual) {
2545
+ //nothing to update
2546
+ return
2547
+ }
2548
+ }
2549
+ else {
2550
+ //cant get current setting
2551
+ return
2552
+ }
2553
+ }
2554
+
2555
+ const privacy = {...existingSettings, ...privacySettings}
2556
+ const privacyValue = SteamUser._formatPrivacySettings2Value(privacy)
2557
+
2558
+ const {
2559
+ data,
2560
+ } = await this._httpRequestAjax({
2561
+ url: `${this.getMySteamUserProfileURL()}/ajaxsetprivacy`,
2562
+ method: 'POST',
2563
+ data: {
2564
+ Privacy: JSON.stringify(privacyValue.PrivacySettings),
2565
+ eCommentPermission: privacyValue.eCommentPermission
2566
+ }
2567
+ })
2568
+
2569
+ if(!data || data === 'null') {
2570
+ return
2571
+ }
2572
+
2573
+ return SteamUser._formatPrivacySettings2String(data)
2574
+ }
2575
+
2576
+ async publicPrivacySettings() {
2577
+ return await this.updatePrivacySettings({
2578
+ profile: EPrivacyState.Public,
2579
+ inventory: EPrivacyState.Public,
2580
+ inventoryGifts: EPrivacyState.Public,
2581
+ gameDetails: EPrivacyState.Public,
2582
+ playtime: EPrivacyState.Public,
2583
+ friendsList: EPrivacyState.Public,
2584
+ comment: EPrivacyState.Public
2585
+ })
2586
+ }
2587
+
2588
+ async privatePrivacySettings() {
2589
+ return await this.updatePrivacySettings({
2590
+ profile: EPrivacyState.Private,
2591
+ inventory: EPrivacyState.Private,
2592
+ inventoryGifts: EPrivacyState.Private,
2593
+ gameDetails: EPrivacyState.Private,
2594
+ playtime: EPrivacyState.Private,
2595
+ friendsList: EPrivacyState.Private,
2596
+ comment: EPrivacyState.Private
2597
+ })
2598
+ }
2599
+
2600
+ async postComment(steamID, message) {
2601
+ const {data} = await this._httpRequestAjax({
2602
+ url: `comment/Profile/post/${steamID}/-1/`,
2603
+ method: 'POST',
2604
+ data: {
2605
+ comment: message,
2606
+ count: 6,
2607
+ feature2: -1,
2608
+ }
2609
+ })
2610
+ if(!data?.comments_html) {
2611
+ return
2612
+ }
2613
+
2614
+ data.comments = this._parseComments(data.comments_html)
2615
+ data.comment = data.comments.find(c => c.timestamp == data.timelastpost)
2616
+ return data
2617
+
2618
+ const errorExample = {
2619
+ success: false,
2620
+ error: 'You\'ve been posting too frequently, and can\'t make another post right now'
2621
+ }
2622
+ const successExample = {
2623
+ success: true,
2624
+ name: 'Profile_76561199277912057',
2625
+ start: 0,
2626
+ pagesize: '6',
2627
+ total_count: 5,
2628
+ upvotes: 0,
2629
+ has_upvoted: 0,
2630
+ comments_html: '\t\t\r\n\t\r\n\t<div data-panel="{&quot;flow-children&quot;:&quot;row&quot;,&quot;type&quot;:&quot;PanelGroup&quot;}" class="commentthread_comment responsive_body_text " id="comment_3455968685021221554" style="">\r\n\t\t\t\t<div class="commentthread_comment_avatar playerAvatar online">\r\n\t\t\t\t\t\t<a href="https://steamcommunity.com/id/natri99" data-miniprofile="1080136620">\r\n\t\t\t\t\t\t\t\t\t<img src="https://avatars.cloudflare.steamstatic.com/834966fea6a0a8a3b7011db7f96d38b51ee0ba64.jpg" srcset="https://avatars.cloudflare.steamstatic.com/834966fea6a0a8a3b7011db7f96d38b51ee0ba64.jpg 1x, https://avatars.cloudflare.steamstatic.com/834966fea6a0a8a3b7011db7f96d38b51ee0ba64_medium.jpg 2x">\t\t\t\t\t\t\t</a>\r\n\t\t</div>\r\n\t\t<div class="commentthread_comment_content">\r\n\t\t\t<div data-panel="{&quot;flow-children&quot;:&quot;row&quot;}" class="commentthread_comment_author">\r\n\t\t\t\t<a class="hoverunderline commentthread_author_link" href="https://steamcommunity.com/id/natri99" data-miniprofile="1080136620">\r\n\t\t\t\t\t<bdi>Natri</bdi></a>\r\n\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t<span class="commentthread_comment_timestamp" title="9 August, 2022 @ 6:36:13 am +07" data-timestamp="1660001773">\r\n\t\t\t\t\tJust now&nbsp;\r\n\t\t\t\t</span>\r\n\t\t\t\t\t\t\t\t<div class="commentthread_comment_actions" >\r\n\t\t\t\t\t<a class="actionlink" data-tooltip-text="Delete" href="javascript:CCommentThread.DeleteComment( \'Profile_76561199277912057\', \'3455968685021221554\' );"><img src="https://community.cloudflare.steamstatic.com/public/images/skin_1/notification_icon_trash_bright.png?v=1" ></a>\t\t\t\t</div>\r\n\t\t\t</div>\r\n\t\t\t<div class="commentthread_comment_text" id="comment_content_3455968685021221554">\r\n\t\t\t\tddddddd\t\t\t</div>\r\n\t\t\t\t\t\t\t\t</div>\r\n\t\t\t</div>\r\n\t\t\r\n\t\r\n\t<div data-panel="{&quot;flow-children&quot;:&quot;row&quot;,&quot;type&quot;:&quot;PanelGroup&quot;}" class="commentthread_comment responsive_body_text " id="comment_3455968685021217640" style="">\r\n\t\t\t\t<div class="commentthread_comment_avatar playerAvatar online">\r\n\t\t\t\t\t\t<a href="https://steamcommunity.com/id/natri99" data-miniprofile="1080136620">\r\n\t\t\t\t\t\t\t\t\t<img src="https://avatars.cloudflare.steamstatic.com/834966fea6a0a8a3b7011db7f96d38b51ee0ba64.jpg" srcset="https://avatars.cloudflare.steamstatic.com/834966fea6a0a8a3b7011db7f96d38b51ee0ba64.jpg 1x, https://avatars.cloudflare.steamstatic.com/834966fea6a0a8a3b7011db7f96d38b51ee0ba64_medium.jpg 2x">\t\t\t\t\t\t\t</a>\r\n\t\t</div>\r\n\t\t<div class="commentthread_comment_content">\r\n\t\t\t<div data-panel="{&quot;flow-children&quot;:&quot;row&quot;}" class="commentthread_comment_author">\r\n\t\t\t\t<a class="hoverunderline commentthread_author_link" href="https://steamcommunity.com/id/natri99" data-miniprofile="1080136620">\r\n\t\t\t\t\t<bdi>Natri</bdi></a>\r\n\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t<span class="commentthread_comment_timestamp" title="9 August, 2022 @ 6:34:41 am +07" data-timestamp="1660001681">\r\n\t\t\t\t\tJust now&nbsp;\r\n\t\t\t\t</span>\r\n\t\t\t\t\t\t\t\t<div class="commentthread_comment_actions" >\r\n\t\t\t\t\t<a class="actionlink" data-tooltip-text="Delete" href="javascript:CCommentThread.DeleteComment( \'Profile_76561199277912057\', \'3455968685021217640\' );"><img src="https://community.cloudflare.steamstatic.com/public/images/skin_1/notification_icon_trash_bright.png?v=1" ></a>\t\t\t\t</div>\r\n\t\t\t</div>\r\n\t\t\t<div class="commentthread_comment_text" id="comment_content_3455968685021217640">\r\n\t\t\t\tddddddd\t\t\t</div>\r\n\t\t\t\t\t\t\t\t</div>\r\n\t\t\t</div>\r\n\t\t\r\n\t\r\n\t<div data-panel="{&quot;flow-children&quot;:&quot;row&quot;,&quot;type&quot;:&quot;PanelGroup&quot;}" class="commentthread_comment responsive_body_text " id="comment_3455968685021214294" style="">\r\n\t\t\t\t<div class="commentthread_comment_avatar playerAvatar online">\r\n\t\t\t\t\t\t<a href="https://steamcommunity.com/id/natri99" data-miniprofile="1080136620">\r\n\t\t\t\t\t\t\t\t\t<img src="https://avatars.cloudflare.steamstatic.com/834966fea6a0a8a3b7011db7f96d38b51ee0ba64.jpg" srcset="https://avatars.cloudflare.steamstatic.com/834966fea6a0a8a3b7011db7f96d38b51ee0ba64.jpg 1x, https://avatars.cloudflare.steamstatic.com/834966fea6a0a8a3b7011db7f96d38b51ee0ba64_medium.jpg 2x">\t\t\t\t\t\t\t</a>\r\n\t\t</div>\r\n\t\t<div class="commentthread_comment_content">\r\n\t\t\t<div data-panel="{&quot;flow-children&quot;:&quot;row&quot;}" class="commentthread_comment_author">\r\n\t\t\t\t<a class="hoverunderline commentthread_author_link" href="https://steamcommunity.com/id/natri99" data-miniprofile="1080136620">\r\n\t\t\t\t\t<bdi>Natri</bdi></a>\r\n\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t<span class="commentthread_comment_timestamp" title="9 August, 2022 @ 6:33:22 am +07" data-timestamp="1660001602">\r\n\t\t\t\t\t2 minutes ago&nbsp;\r\n\t\t\t\t</span>\r\n\t\t\t\t\t\t\t\t<div class="commentthread_comment_actions" >\r\n\t\t\t\t\t<a class="actionlink" data-tooltip-text="Delete" href="javascript:CCommentThread.DeleteComment( \'Profile_76561199277912057\', \'3455968685021214294\' );"><img src="https://community.cloudflare.steamstatic.com/public/images/skin_1/notification_icon_trash_bright.png?v=1" ></a>\t\t\t\t</div>\r\n\t\t\t</div>\r\n\t\t\t<div class="commentthread_comment_text" id="comment_content_3455968685021214294">\r\n\t\t\t\tddddddd\t\t\t</div>\r\n\t\t\t\t\t\t\t\t</div>\r\n\t\t\t</div>\r\n\t\t\r\n\t\r\n\t<div data-panel="{&quot;flow-children&quot;:&quot;row&quot;,&quot;type&quot;:&quot;PanelGroup&quot;}" class="commentthread_comment responsive_body_text " id="comment_3455968685021213633" style="">\r\n\t\t\t\t<div class="commentthread_comment_avatar playerAvatar online">\r\n\t\t\t\t\t\t<a href="https://steamcommunity.com/id/natri99" data-miniprofile="1080136620">\r\n\t\t\t\t\t\t\t\t\t<img src="https://avatars.cloudflare.steamstatic.com/834966fea6a0a8a3b7011db7f96d38b51ee0ba64.jpg" srcset="https://avatars.cloudflare.steamstatic.com/834966fea6a0a8a3b7011db7f96d38b51ee0ba64.jpg 1x, https://avatars.cloudflare.steamstatic.com/834966fea6a0a8a3b7011db7f96d38b51ee0ba64_medium.jpg 2x">\t\t\t\t\t\t\t</a>\r\n\t\t</div>\r\n\t\t<div class="commentthread_comment_content">\r\n\t\t\t<div data-panel="{&quot;flow-children&quot;:&quot;row&quot;}" class="commentthread_comment_author">\r\n\t\t\t\t<a class="hoverunderline commentthread_author_link" href="https://steamcommunity.com/id/natri99" data-miniprofile="1080136620">\r\n\t\t\t\t\t<bdi>Natri</bdi></a>\r\n\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t<span class="commentthread_comment_timestamp" title="9 August, 2022 @ 6:33:06 am +07" data-timestamp="1660001586">\r\n\t\t\t\t\t3 minutes ago&nbsp;\r\n\t\t\t\t</span>\r\n\t\t\t\t\t\t\t\t<div class="commentthread_comment_actions" >\r\n\t\t\t\t\t<a class="actionlink" data-tooltip-text="Delete" href="javascript:CCommentThread.DeleteComment( \'Profile_76561199277912057\', \'3455968685021213633\' );"><img src="https://community.cloudflare.steamstatic.com/public/images/skin_1/notification_icon_trash_bright.png?v=1" ></a>\t\t\t\t</div>\r\n\t\t\t</div>\r\n\t\t\t<div class="commentthread_comment_text" id="comment_content_3455968685021213633">\r\n\t\t\t\tddddddd\t\t\t</div>\r\n\t\t\t\t\t\t\t\t</div>\r\n\t\t\t</div>\r\n\t\t\r\n\t\r\n\t<div data-panel="{&quot;flow-children&quot;:&quot;row&quot;,&quot;type&quot;:&quot;PanelGroup&quot;}" class="commentthread_comment responsive_body_text " id="comment_3455968685021212374" style="">\r\n\t\t\t\t<div class="commentthread_comment_avatar playerAvatar online">\r\n\t\t\t\t\t\t<a href="https://steamcommunity.com/id/natri99" data-miniprofile="1080136620">\r\n\t\t\t\t\t\t\t\t\t<img src="https://avatars.cloudflare.steamstatic.com/834966fea6a0a8a3b7011db7f96d38b51ee0ba64.jpg" srcset="https://avatars.cloudflare.steamstatic.com/834966fea6a0a8a3b7011db7f96d38b51ee0ba64.jpg 1x, https://avatars.cloudflare.steamstatic.com/834966fea6a0a8a3b7011db7f96d38b51ee0ba64_medium.jpg 2x">\t\t\t\t\t\t\t</a>\r\n\t\t</div>\r\n\t\t<div class="commentthread_comment_content">\r\n\t\t\t<div data-panel="{&quot;flow-children&quot;:&quot;row&quot;}" class="commentthread_comment_author">\r\n\t\t\t\t<a class="hoverunderline commentthread_author_link" href="https://steamcommunity.com/id/natri99" data-miniprofile="1080136620">\r\n\t\t\t\t\t<bdi>Natri</bdi></a>\r\n\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t<span class="commentthread_comment_timestamp" title="9 August, 2022 @ 6:32:37 am +07" data-timestamp="1660001557">\r\n\t\t\t\t\t3 minutes ago&nbsp;\r\n\t\t\t\t</span>\r\n\t\t\t\t\t\t\t\t<div class="commentthread_comment_actions" >\r\n\t\t\t\t\t<a class="actionlink" data-tooltip-text="Delete" href="javascript:CCommentThread.DeleteComment( \'Profile_76561199277912057\', \'3455968685021212374\' );"><img src="https://community.cloudflare.steamstatic.com/public/images/skin_1/notification_icon_trash_bright.png?v=1" ></a>\t\t\t\t</div>\r\n\t\t\t</div>\r\n\t\t\t<div class="commentthread_comment_text" id="comment_content_3455968685021212374">\r\n\t\t\t\tddddddd\t\t\t</div>\r\n\t\t\t\t\t\t\t\t</div>\r\n\t\t\t</div>\r\n',
2631
+ timelastpost: 1660001773,
2632
+ comments: [{
2633
+ id: '3455968685021221554',
2634
+ content: 'ddddddd',
2635
+ timestamp: 1660001773,
2636
+ author: {
2637
+ steamID: '76561199040402348',
2638
+ profileURL: 'https://steamcommunity.com/id/natri99',
2639
+ miniprofile: 1080136620,
2640
+ name: 'Natri',
2641
+ avatar: 'https://avatars.cloudflare.steamstatic.com/834966fea6a0a8a3b7011db7f96d38b51ee0ba64.jpg'
2642
+ }
2643
+ },],
2644
+ comment: {
2645
+ id: '3455968685021221554',
2646
+ content: 'ddddddd',
2647
+ timestamp: 1660001773,
2648
+ author: {
2649
+ steamID: '76561199040402348',
2650
+ profileURL: 'https://steamcommunity.com/id/natri99',
2651
+ miniprofile: 1080136620,
2652
+ name: 'Natri',
2653
+ avatar: 'https://avatars.cloudflare.steamstatic.com/834966fea6a0a8a3b7011db7f96d38b51ee0ba64.jpg'
2654
+ }
2655
+ }
2656
+ }
2657
+ }
2658
+
2659
+ async deleteComment(steamID, gidcomment) {
2660
+ const {data} = await this._httpRequestAjax({
2661
+ url: `comment/Profile/delete/${steamID}/-1/`,
2662
+ method: 'POST',
2663
+ data: {
2664
+ gidcomment: gidcomment,
2665
+ start: 0,
2666
+ count: 6,
2667
+ feature2: -1,
2668
+ }
2669
+ })
2670
+ return data
2671
+ const successExample = {
2672
+ success: true,
2673
+ name: 'Profile_76561199277912057',
2674
+ start: 0,
2675
+ pagesize: '6',
2676
+ total_count: 0,
2677
+ upvotes: 0,
2678
+ has_upvoted: 0,
2679
+ comments_html: '',
2680
+ timelastpost: 0
2681
+ }
2682
+ }
2683
+
2684
+ _formatCommentModel(comment) {
2685
+ if(comment?.comments_html) {
2686
+ comment.commentID = comment.comments_html?.substringAfter('javascript:CCommentThread.DeleteComment')
2687
+ ?.substringAfter('\', \'')
2688
+ ?.substringBefore('\' );')
2689
+ comment.steamID = comment.comments_html?.substringAfter('javascript:CCommentThread.DeleteComment( \'Profile_')
2690
+ ?.substringBefore('\', \'')
2691
+ }
2692
+ return comment
2693
+ }
2694
+
2695
+ _formatPlayingTime(hrs) {
2696
+ if(typeof hrs !== 'string') {
2697
+ return hrs
2698
+ }
2699
+ hrs = hrs.trim()
2700
+ if(hrs.endsWith('hrs')) {
2701
+ return parseFloat(hrs.substringBefore('hrs'))
2702
+ }
2703
+ return hrs
2704
+ }
2705
+
2706
+ //sorted: false, ASC, DESC
2707
+ async getFriendsThatPlay(appID, sorted = false) {
2708
+ if(!appID) {
2709
+ return
2710
+ }
2711
+ const _self = this
2712
+ const result = await this._httpRequest(`${this.getMySteamUserProfileURL()}/friendsthatplay/${appID}`)
2713
+ const data = {
2714
+ YourOwnPlaytime: [],//Your own playtime _
2715
+ FriendsCurrentlyPlaying: [],//Friends currently playing Counter-Strike: Global Offensive_
2716
+ FriendsTwoWeeks: [],//Friends who have played Counter-Strike: Global Offensive in the last 2 weeks _
2717
+ FriendsPreviously: [],//Friends who have played Counter-Strike: Global Offensive previously _
2718
+ FriendsAddedLibrary: [],//Friends who have Counter-Strike: Global Offensive in their library _
2719
+ }
2720
+ if(!result.data) {
2721
+ return data
2722
+ }
2723
+ const $ = result._$()
2724
+ let currentList
2725
+
2726
+ $('#memberList').children(function () {
2727
+ const el = $(this)
2728
+ if(el.hasClass('mainSectionHeader')) {
2729
+ const title = el.text()?.trim() || ''
2730
+ if(title.startsWith('Your own playtime')) {
2731
+ currentList = data.YourOwnPlaytime
2732
+ }
2733
+ else if(title.startsWith('Friends currently playing')) {
2734
+ currentList = data.FriendsCurrentlyPlaying
2735
+ }
2736
+ else if(title.startsWith('Friends who have played') && title.includes('in the last 2 weeks')) {
2737
+ currentList = data.FriendsTwoWeeks
2738
+ }
2739
+ else if(title.startsWith('Friends who have played') && title.includes('previously')) {
2740
+ currentList = data.FriendsPreviously
2741
+ }
2742
+ else if(title.startsWith('Friends who have') && !title.startsWith('Friends who have played') && title.includes('in their library')) {
2743
+ currentList = data.FriendsAddedLibrary
2744
+ }
2745
+ }
2746
+ else if(el.hasClass('profile_friends')) {
2747
+ el.find('.friendBlock.persona').each(function () {
2748
+ const friendBlock = $(this)
2749
+ const miniprofile = parseInt(friendBlock.attr('data-miniprofile'))
2750
+ let onlineStatus = ''
2751
+ if(friendBlock.hasClass('in-game')) {
2752
+ onlineStatus = 'ingame'
2753
+ }
2754
+ else if(friendBlock.hasClass('online')) {
2755
+ onlineStatus = 'online'
2756
+ }
2757
+ else if(friendBlock.hasClass('offline')) {
2758
+ onlineStatus = 'offline'
2759
+ }
2760
+ const friendBlockContent = friendBlock.find('.friendBlockContent')
2761
+ friendBlock.find('.friendSmallText .whiteLink').remove()
2762
+ const friendSmallText = friendBlockContent.find('.friendSmallText').text()?.trim()
2763
+
2764
+ const playingTime = {
2765
+ last2Week: '',
2766
+ total: '',
2767
+ }
2768
+ if(friendSmallText) {
2769
+ if(friendSmallText.includes('/')) {
2770
+ playingTime.last2Week = _self._formatPlayingTime(friendSmallText.substringBeforeOrNull('/')?.trim())
2771
+ playingTime.total = _self._formatPlayingTime(friendSmallText.substringAfterOrNull('/')?.trim())
2772
+ }
2773
+ else {
2774
+ playingTime.total = _self._formatPlayingTime(friendSmallText)
2775
+ }
2776
+ }
2777
+
2778
+ friendBlockContent.find('.friendSmallText').remove()
2779
+ const name = friendBlockContent.text()?.trim()
2780
+
2781
+
2782
+ currentList.push({
2783
+ playingTime,
2784
+ name,
2785
+ onlineStatus,
2786
+ miniprofile,
2787
+ })
2788
+ })
2789
+ }
2790
+
2791
+ })
2792
+
2793
+
2794
+ if(sorted) {
2795
+ const order = sorted === 'ASC' ? 1 : -1
2796
+ for (let key in data) {
2797
+ data[key].sort(function (player1, player2) {
2798
+ const player1_last2Week = player1.playingTime?.last2Week || 0
2799
+ const player2_last2Week = player2.playingTime?.last2Week || 0
2800
+
2801
+ if(player1_last2Week > player2_last2Week) {
2802
+ return order
2803
+ }
2804
+ else if(player1_last2Week < player2_last2Week) {
2805
+ return -order
2806
+ }
2807
+ else {
2808
+ const player1_total = player1.playingTime?.total || 0
2809
+ const player2_total = player2.playingTime?.total || 0
2810
+ if(player1_total > player2_total) {
2811
+ return order
2812
+ }
2813
+ else if(player1_total < player2_total) {
2814
+ return -order
2815
+ }
2816
+ else {
2817
+ return 0
2818
+ }
2819
+ }
2820
+ })
2821
+ }
2822
+ }
2823
+
2824
+ return data
2825
+ }
2826
+
2827
+ async getOwnedAppsDetail() {
2828
+ const response = await this._httpRequest(`${this.getMySteamUserProfileURL()}/games/?tab=all`)
2829
+
2830
+ let rgGames = response
2831
+ ?.data
2832
+ ?.substringBetweenOrNull('var rgGames = ', 'var rgChangingGames = []')
2833
+ ?.trim()
2834
+ ?.removeSuffix(';')
2835
+ ?.trim()
2836
+ if(!rgGames) {
2837
+ return
2838
+ }
2839
+
2840
+ rgGames = JSON_parse(rgGames)
2841
+ return rgGames
2842
+ const resultExample = [{
2843
+ appid: 730,
2844
+ name: 'Counter-Strike: Global Offensive',
2845
+ friendly_name: 'CSGO',
2846
+ has_adult_content: 1,
2847
+ is_visible_in_steam_china: 1,
2848
+ app_type: 1,
2849
+ logo: 'https://cdn.cloudflare.steamstatic.com/steam/apps/730/capsule_184x69.jpg',
2850
+ friendlyURL: 'CSGO',
2851
+ availStatLinks: {
2852
+ achievements: true,
2853
+ global_achievements: true,
2854
+ stats: false,
2855
+ gcpd: true,
2856
+ leaderboards: false,
2857
+ global_leaderboards: false
2858
+ },
2859
+ hours: '41.3',
2860
+ hours_forever: '4,297',
2861
+ last_played: 1659900692
2862
+ },]
2863
+ }
2864
+
2865
+ async GetOwnedApps() {
2866
+ let response = await this._httpRequest(`/actions/GetOwnedApps/?sessionid=${this.getSessionid()}`)
2867
+ return response?.data
2868
+ const resultExample = [{
2869
+ appid: 730,
2870
+ name: 'Counter-Strike: Global Offensive',
2871
+ icon: 'https:\/\/cdn.akamai.steamstatic.com\/steamcommunity\/public\/images\/apps\/730\/69f7ebe2735c366c65c0b33dae00e12dc40edbe4.jpg',
2872
+ logo: 'https:\/\/cdn.akamai.steamstatic.com\/steam\/apps\/730\/capsule_184x69.jpg'
2873
+ }]
2874
+ }
2875
+
2876
+ async getOwnedAppsID() {
2877
+ let response = await this._httpRequest(`https://store.steampowered.com/dynamicstore/userdata/?id=${this._miniprofile_user}`)
2878
+ return response?.data?.rgOwnedApps
2879
+ const resultExample = [730, 223750,]
2880
+ }
2881
+
2882
+ async getListGroupInvitable(userSteamID) {
2883
+ const result = await this._httpRequestAjax(`${this.getSteamUserProfileURL(userSteamID)}/ajaxgroupinvite?new_profile=1`)
2884
+ const $ = result._$()
2885
+ const groupList = []
2886
+ $('.group_list_results > .group_list_option').each(function () {
2887
+ const group_list_option = $(this)
2888
+ const id = group_list_option.attr('data-groupid')
2889
+ const avatarHash = group_list_option.attr('_groupavatarhash')
2890
+ const avatar = group_list_option.find('.playerAvatar img').attr('src')
2891
+ const name = group_list_option.find('.group_list_groupname').text()?.trim()
2892
+ groupList.push({
2893
+ id,
2894
+ avatarHash,
2895
+ avatar,
2896
+ name,
2897
+ })
2898
+ })
2899
+ return groupList
2900
+ const resultExample = [{
2901
+ id: NatriGroupSteamID,
2902
+ avatarHash: '71213b7e643da6216b1f8d8a381fc09b7c1932ef',
2903
+ avatar: 'https://avatars.cloudflare.steamstatic.com/71213b7e643da6216b1f8d8a381fc09b7c1932ef.jpg',
2904
+ name: '♔⌒Natri'
2905
+ }]
2906
+ }
2907
+
2908
+ async inviteUserToGroup(userSteamIDs = [], groupSteamID) {
2909
+ const params = {
2910
+ json: 1,
2911
+ type: 'groupInvite',
2912
+ group: groupSteamID,
2913
+ invitee: userSteamIDs,
2914
+ }
2915
+ if(userSteamIDs instanceof Array && userSteamIDs.length === 1) {
2916
+ userSteamIDs = userSteamIDs[0]
2917
+ }
2918
+
2919
+ if(typeof userSteamIDs === 'string') {
2920
+ params.invitee = userSteamIDs
2921
+ }
2922
+ else if(userSteamIDs instanceof Array) {
2923
+ params.invitee_list = JSON_stringify(userSteamIDs)
2924
+ }
2925
+ else {
2926
+ //error
2927
+ }
2928
+ const {data} = await this._httpRequestAjax({
2929
+ url: 'actions/GroupInvite',
2930
+ data: params,
2931
+ method: 'POST',
2932
+ })
2933
+ return data
2934
+ const errorExample = {
2935
+ results: 'The invite to this user failed for the following reason...',
2936
+ rgAccounts: {
2937
+ 1317646329: {
2938
+ persona: '<a href="https://steamcommunity.com/profiles/76561199277912057" data-miniprofile="1317646329">Natri</a>',
2939
+ strError: 'This user has already been invited or is currently a member.'
2940
+ }
2941
+ }
2942
+ }
2943
+ const successExample = {
2944
+ results: 'OK',
2945
+ groupId: NatriGroupSteamID
2946
+ }
2947
+ }
2948
+
2949
+ async inviteAllFriendToGroup(groupSteamID) {
2950
+ const friendList = await this.getMyFriendsList()
2951
+ for (const {steamId} of friendList) {
2952
+ const groupInvitableList = await this.getListGroupInvitable(steamId)
2953
+ if(groupInvitableList.some(g => g.id == groupSteamID)) {
2954
+ await this.inviteUserToGroup(steamId, groupSteamID)
2955
+ }
2956
+ }
2957
+ }
2958
+
2959
+ async _respondToGroupInvite(groupSteamID, accept) {
2960
+ const {data} = await this._httpRequestAjax({
2961
+ url: `${this.getMySteamUserProfileURL()}/friends/action`,
2962
+ data: {
2963
+ steamid: this.getSteamidUser(),
2964
+ ajax: 1,
2965
+ action: accept ? 'group_accept' : 'group_ignore',
2966
+ 'steamids[]': groupSteamID,
2967
+ },
2968
+ method: 'POST',
2969
+ })
2970
+
2971
+ return data?.success === 1 || data?.success === true
2972
+
2973
+ const errorExample = {
2974
+ success: 42,//2
2975
+ rgCounts: {
2976
+ cFriendsPending: 11,
2977
+ cFriendsBlocked: 1,
2978
+ cFollowing: 179,
2979
+ cGroupsPending: 0,
2980
+ cFriends: 183,
2981
+ cGroups: 1,
2982
+ success: 1
2983
+ }
2984
+ }
2985
+
2986
+ const successExample = {
2987
+ success: 1,
2988
+ rgCounts: {
2989
+ cFriendsPending: 0,
2990
+ cFriendsBlocked: 0,
2991
+ cFollowing: 0,
2992
+ cGroupsPending: 0,
2993
+ cFriends: 1,
2994
+ cGroups: 1,
2995
+ success: 1
2996
+ }
2997
+ }
2998
+ }
2999
+
3000
+ async acceptInviteUserToGroup(groupSteamID) {
3001
+ return this._respondToGroupInvite(groupSteamID, true)
3002
+ }
3003
+
3004
+ async ignoreInviteUserToGroup(groupSteamID) {
3005
+ return this._respondToGroupInvite(groupSteamID, false)
3006
+ }
3007
+
3008
+ async leaveGroup(groupSteamID) {
3009
+ const {data} = await this._httpRequestAjax({
3010
+ url: `${this.getMySteamUserProfileURL()}/friends/action`,
3011
+ data: {
3012
+ steamid: this.getSteamidUser(),
3013
+ ajax: 1,
3014
+ action: 'leave_group',
3015
+ 'steamids[]': groupSteamID,
3016
+ },
3017
+ method: 'POST',
3018
+ })
3019
+
3020
+ return data?.success === 1 || data?.success === true
3021
+
3022
+ const successExample = {
3023
+ success: true,
3024
+ rgCounts: {
3025
+ cFriendsPending: 0,
3026
+ cFriendsBlocked: 0,
3027
+ cFollowing: 0,
3028
+ cGroupsPending: 0,
3029
+ cFriends: 1,
3030
+ cGroups: 0,
3031
+ success: 1
3032
+ }
3033
+ }
3034
+ }
3035
+
3036
+ async joinGroup(groupSteamID) {
3037
+ const result = await this._httpRequest({
3038
+ url: `/gid/${groupSteamID}/friends/action`,
3039
+ data: {
3040
+ action: 'join',
3041
+ },
3042
+ method: 'POST',
3043
+ })
3044
+ if(!result?.data) {
3045
+ return {
3046
+ success: false
3047
+ }
3048
+ }
3049
+
3050
+ if(result.data.includes('You are already a member of this group.')) {
3051
+ return {
3052
+ success: false,
3053
+ error: 'You are already a member of this group.'
3054
+ }
3055
+ }
3056
+
3057
+ const $ = result._$()
3058
+ return {
3059
+ success: !!($('form#leave_group_form').length)
3060
+ }
3061
+ }
3062
+
3063
+ async getFriendsInCommon(steamID) {
3064
+ const accountid = SteamUser.steamID642Miniprofile(steamID)
3065
+ const result = await this._httpRequest({
3066
+ url: `actions/PlayerList/?type=friendsincommon&target=${accountid}`,
3067
+ })
3068
+ return this._parseFriendList(result?.data)
3069
+ }
3070
+
3071
+ async getFriendsInGroup(groupID) {
3072
+ const accountid = SteamUser.steamID642Miniprofile(groupID)
3073
+ const result = await this._httpRequest({
3074
+ url: `actions/PlayerList/?type=friendsingroup&target=${accountid}`,
3075
+ })
3076
+ return this._parseFriendList(result?.data)
3077
+ }
3078
+
3079
+ _parseSteamWebAPIKey($) {
3080
+ if($('form[action*="/dev/registerkey"]').length) {
3081
+ return {
3082
+ success: false,
3083
+ error: 'registerkey'
3084
+ }
3085
+ }
3086
+
3087
+ let key = null
3088
+ $('#bodyContents_ex > p').each(function () {
3089
+ if(!key) {
3090
+ const text = $(this).text()?.trim()
3091
+ if(text.startsWith('Key:')) {
3092
+ key = text.substringAfter('Key:').trim()
3093
+ }
3094
+ }
3095
+ })
3096
+
3097
+ return {
3098
+ success: true,
3099
+ key,
3100
+ }
3101
+ }
3102
+
3103
+ async getSteamWebAPIKey(domain = 'localhost') {
3104
+ const _self = this
3105
+ const result = await this._httpRequest({
3106
+ url: `dev/apikey`,
3107
+ })
3108
+
3109
+ if(result?.data?.includes('You will be granted access to Steam Web API keys when you have games in your Steam account.')) {
3110
+ return {
3111
+ success: false,
3112
+ error: 'dontHaveGames'
3113
+ }
3114
+ }
3115
+
3116
+ const parseResult = _self._parseSteamWebAPIKey(result._$())
3117
+ if(parseResult.error === 'registerkey') {
3118
+ const result2 = await this._httpRequest({
3119
+ url: `dev/registerkey`,
3120
+ data: {
3121
+ domain,
3122
+ agreeToTerms: 'agreed',
3123
+ Submit: 'agreRegistered',
3124
+ },
3125
+ headers: {
3126
+ 'Content-Type': 'multipart/form-data',
3127
+ },
3128
+ method: 'POST',
3129
+ })
3130
+ return _self._parseSteamWebAPIKey(result2._$())
3131
+ }
3132
+ else {
3133
+ return parseResult
3134
+ }
3135
+ }
3136
+
3137
+ async revokeSteamWebAPIKey() {
3138
+ const _self = this
3139
+ const result = await this._httpRequest({
3140
+ url: `dev/revokekey`,
3141
+ data: {
3142
+ Revoke: 'Revoke My Steam Web API Key'
3143
+ },
3144
+ headers: {
3145
+ 'Content-Type': 'multipart/form-data',
3146
+ },
3147
+ method: 'POST',
3148
+ })
3149
+
3150
+ const parseResult = _self._parseSteamWebAPIKey(result._$())
3151
+ return parseResult
3152
+ }
3153
+
3154
+ _parsePlayerListFromblotter_daily_rollup_line($, contentEl) {
3155
+ const players = []
3156
+ contentEl.find('a[data-miniprofile]').each((index, el) => {
3157
+ el = $(el)
3158
+ const nickname = el.find('.nickname_name').text()
3159
+ const miniprofile = parseInt(el.attr('data-miniprofile'))
3160
+ el.find('.nickname_block').remove()
3161
+ el.find('.nickname_name').remove()
3162
+ const name = el.text()?.trim()
3163
+ players.push({
3164
+ name,
3165
+ nickname,
3166
+ miniprofile,
3167
+ steamID: SteamUser.miniprofile2SteamID64(miniprofile)
3168
+ })
3169
+ })
3170
+ return players
3171
+ }
3172
+
3173
+ _parseAppIDFromLink(link) {
3174
+ const prefixs = ['steamcommunity.com/app/', 'store.steampowered.com/app/', 'store.steampowered.com/sub/']
3175
+ for (let i = 0; i < prefixs.length; i++) {
3176
+ const prefix = prefixs[i]
3177
+ if(link.includes(prefix)) {
3178
+ link = link.substringAfter(prefix)
3179
+ if(link.includes('/')) {
3180
+ link = link.substringBefore('/')
3181
+ }
3182
+ return parseInt(link)
3183
+ }
3184
+ }
3185
+
3186
+ return -1
3187
+ }
3188
+
3189
+ _parseAppListFromBlotter($, contentEl) {
3190
+ const _self = this
3191
+
3192
+ let apps = []
3193
+ contentEl.find('a').each((index, appEl) => {
3194
+ appEl = $(appEl)
3195
+ const link = appEl.attr('href')
3196
+ const id = _self._parseAppIDFromLink(link)
3197
+ const name = appEl.text()?.trim()
3198
+ apps.push({
3199
+ name,
3200
+ link,
3201
+ id,
3202
+ })
3203
+ })
3204
+ apps = apps.filter(app => app.link && app.id !== -1)
3205
+ return apps
3206
+ }
3207
+
3208
+ _parseAchievedblotter_daily_rollup_line($, contentEl) {
3209
+ const achieved = []
3210
+ contentEl.find('> img[title]').each((index, imgEl) => {
3211
+ imgEl = $(imgEl)
3212
+ const img = imgEl.attr('src')
3213
+ const title = imgEl.attr('title')
3214
+ achieved.push({
3215
+ img,
3216
+ title
3217
+ })
3218
+ })
3219
+ return achieved
3220
+ }
3221
+
3222
+ _parseGroupListFromblotter_daily_rollup_line($, contentEl) {
3223
+ const groups = []
3224
+ contentEl.find('a[href*="steamcommunity.com/groups/"]').each((index, appEl) => {
3225
+ appEl = $(appEl)
3226
+ const link = appEl.attr('href')
3227
+ if(link) {
3228
+ groups.push({
3229
+ name: appEl.text()?.trim(),
3230
+ link: link,
3231
+ URL: link.substringAfter('steamcommunity.com/groups/').removeSuffix('/'),
3232
+ })
3233
+ }
3234
+ })
3235
+ return groups
3236
+ }
3237
+
3238
+ _parseBlotterDailyRollup($, blotterBlockEl) {
3239
+ const _self = this
3240
+ const activity = []
3241
+ blotterBlockEl.find('.blotter_daily_rollup_line').each(function () {
3242
+ const blotter_daily_rollup_line = $(this)
3243
+ const miniprofile = parseInt(blotter_daily_rollup_line.find('.blotter_rollup_avatar .blotter_small_friend_block_container img[data-miniprofile]').attr('data-miniprofile'))
3244
+ const steamID = SteamUser.miniprofile2SteamID64(miniprofile)
3245
+ const contentEl = $(blotter_daily_rollup_line.find(' > span')[0])
3246
+ const contentText = contentEl.text()?.trim()
3247
+
3248
+ const players = () => _self._parsePlayerListFromblotter_daily_rollup_line($, contentEl)
3249
+ const apps = () => _self._parseAppListFromBlotter($, contentEl)
3250
+ const groups = () => _self._parseGroupListFromblotter_daily_rollup_line($, contentEl)
3251
+ const achieved = () => _self._parseAchievedblotter_daily_rollup_line($, blotter_daily_rollup_line)
3252
+
3253
+ if(contentText.includes('and') && contentText.includes(' are now friends.')) {
3254
+ activity.push({
3255
+ type: EActivityType.newFriend,
3256
+ players: players(),
3257
+ })
3258
+ }
3259
+ else if(contentText.includes(' is now friends with ')) {
3260
+ activity.push({
3261
+ type: EActivityType.newFriend,
3262
+ players: players(),
3263
+ })
3264
+ }
3265
+ else if(contentText.includes(' played ') && contentText.includes('for the first time.')) {
3266
+ activity.push({
3267
+ type: EActivityType.playedFirstTime,
3268
+ players: players(),
3269
+ apps: apps(),
3270
+ })
3271
+ }
3272
+ else if(contentText.includes(' achieved ')) {
3273
+ activity.push({
3274
+ type: EActivityType.achieved,
3275
+ players: players(),
3276
+ apps: apps(),
3277
+ achieved: achieved(),
3278
+ })
3279
+ }
3280
+ else if(contentText.includes(' has added ') && contentText.includes('to their wishlist.')) {
3281
+ activity.push({
3282
+ type: EActivityType.added2wishlist,
3283
+ players: players(),
3284
+ apps: apps(),
3285
+ })
3286
+ }
3287
+ else if(contentText.includes(' is now following ')) {
3288
+ activity.push({
3289
+ type: EActivityType.following,
3290
+ players: players(),
3291
+ apps: apps(),
3292
+ })
3293
+ }
3294
+ else if(contentText.includes(' has joined ')) {
3295
+ activity.push({
3296
+ type: EActivityType.joined,
3297
+ players: players(),
3298
+ groups: groups(),
3299
+ })
3300
+ }
3301
+ else {
3302
+ console_log(contentText)
3303
+ }
3304
+ })
3305
+ return activity
3306
+ }
3307
+
3308
+ _parseBlotterGamepurchase($, blotterBlockEl) {
3309
+ const _self = this
3310
+ const activity = []
3311
+ const authorAvatarEl = blotterBlockEl.find('.blotter_author_block .blotter_avatar_holder .playerAvatar img')
3312
+ const miniprofile = parseInt(authorAvatarEl.attr('data-miniprofile'))
3313
+ const avatarHash = SteamUser.GetAvatarHashFromURL(authorAvatarEl.attr('src'))
3314
+ const authorEl = $(blotterBlockEl.find(`a[data-miniprofile="${miniprofile}"]`)[0]).clone()
3315
+ const nickname = authorEl.find('.nickname_name').text()
3316
+ authorEl.find('.nickname_block').remove()
3317
+ authorEl.find('.nickname_name').remove()
3318
+ const profileURL = authorEl.attr('href')
3319
+ const customURL = SteamUser.getCustomURL_from_ProfileURL(profileURL)
3320
+ const name = authorEl.text()?.trim()
3321
+
3322
+ const apps = []
3323
+ const appppp = [..._self._parseAppListFromBlotter($, blotterBlockEl.find('.blotter_author_block')), ..._self._parseAppListFromBlotter($, blotterBlockEl.find('.blotter_gamepurchase_content')), ..._self._parseAppListFromBlotter($, blotterBlockEl.find('.blotter_gamepurchase_details'))]
3324
+
3325
+ appppp.forEach(app => {
3326
+ const index = apps.findIndex(_app => _app.id === app.id)
3327
+ if(index === -1) {
3328
+ apps.push(app)
3329
+ }
3330
+ else {
3331
+ for (let key in apps[index]) {
3332
+ if(!apps[index][key] && app[key]) {
3333
+ apps[index][key] = app[key]
3334
+ }
3335
+ }
3336
+ }
3337
+ })
3338
+
3339
+ const author = {
3340
+ name,
3341
+ nickname,
3342
+ avatarHash,
3343
+ miniprofile,
3344
+ steamID: SteamUser.miniprofile2SteamID64(miniprofile),
3345
+ url: profileURL,
3346
+ customURL,
3347
+ }
3348
+
3349
+ activity.push({
3350
+ type: EActivityType.own,
3351
+ author,
3352
+ apps,
3353
+ })
3354
+ return activity
3355
+ }
3356
+
3357
+ async getFriendActivity(start_or_url) {//start or next_request
3358
+ start_or_url = start_or_url || Math.round((new Date()).getTime() / 1000)
3359
+ const _self = this
3360
+ const activity = []
3361
+ let next_request_timestart = null,
3362
+ next_request = null
3363
+ const {data} = await this._httpRequestAjax(typeof start_or_url === 'string' ? start_or_url : `my/ajaxgetusernews/?start=${start_or_url}`)//use my, not profiles/76561197977736539 getMySteamUserProfileURL
3364
+ if(data?.success) {
3365
+ next_request = data.next_request || null
3366
+ next_request_timestart = parseInt(next_request.substringAfterLast('?start=')) || null
3367
+ const $ = cheerio.load(data.blotter_html
3368
+ .replaceAll(/[\t\n\r]/gi, '')
3369
+ .trim())
3370
+ $('.blotter_day').each(function () {
3371
+ const blotter_day = $(this)
3372
+ const timestamp = parseInt(blotter_day.attr('id').substringAfter('blotter_day_'))
3373
+ const header_date = blotter_day.find('.blotter_day_header_date').text()
3374
+
3375
+ let _activity = []
3376
+
3377
+ const activityType = {
3378
+ daily_rollup(blotterBlockEl) {
3379
+ return _self._parseBlotterDailyRollup($, blotterBlockEl)
3380
+ },
3381
+ gamepurchase(blotterBlockEl) {
3382
+ return _self._parseBlotterGamepurchase($, blotterBlockEl)
3383
+ },
3384
+ workshopitempublished() {
3385
+
3386
+ },
3387
+ recommendation() {
3388
+
3389
+ },
3390
+ userstatus() {
3391
+
3392
+ },
3393
+ screenshot() {//screenshot_fullscreen
3394
+
3395
+ },
3396
+ videopublished() {
3397
+
3398
+ },
3399
+ }
3400
+
3401
+
3402
+ blotter_day.find('.blotter_block').each(function (index, blotterBlockEl) {
3403
+ blotterBlockEl = $(blotterBlockEl)
3404
+ let type = []
3405
+ blotterBlockEl.children().each(function () {
3406
+ const classList = $(this).attr('class')
3407
+ ?.replaceAll('blotter_entry', '')
3408
+ ?.trim()
3409
+ if(classList) {
3410
+ type = type.concat(classList.split(' ').map(_class => _class?.removePrefix('blotter_')))
3411
+ }
3412
+ })
3413
+
3414
+ let excute
3415
+ if((excute = type.find(_class => Object.hasOwn(activityType, _class)))) {
3416
+ const __activity = activityType[excute](blotterBlockEl)
3417
+ if(__activity instanceof Array) {
3418
+ __activity.forEach(___activity => {
3419
+ if(!___activity.type) {
3420
+ ___activity.type = excute
3421
+ }
3422
+
3423
+ const activityID = [___activity.type, ___activity.author?.steamID, (___activity.apps?.map?.(app => app.id) || []).sort().join('|'), (___activity.players?.map?.(p => p.steamID) || []).sort().join('|'), (___activity.achieved?.map?.(p => p.title.replaceAll(/\s/gi, '').toLowerCase()) || []).sort().join('|'), (___activity.groups?.map?.(p => p.URL) || []).sort().join('|'),].filter(Boolean)
3424
+ _activity.push(___activity)
3425
+ })
3426
+ }
3427
+ }
3428
+ else {
3429
+ const html = blotterBlockEl.html()
3430
+ console.log(html)
3431
+ }
3432
+ })
3433
+ _activity.forEach(ac => {
3434
+ ac.timestamp = timestamp
3435
+ ac.header_date = header_date
3436
+ // if (ac.players?.[0]?.name == '𝙎𝙋𝙏 popiii') {
3437
+ // console_log(ac);
3438
+ // }
3439
+ activity.push(ac)
3440
+ })
3441
+ })
3442
+ }
3443
+
3444
+ return {
3445
+ activity,
3446
+ next_request_timestart,
3447
+ next_request,
3448
+ }
3449
+ }
3450
+
3451
+ async getFriendActivityFull({
3452
+ cbOnActivity,
3453
+ cbOnActivities
3454
+ }) {
3455
+ const iscbOnActivity = typeof cbOnActivity === 'function'
3456
+ const iscbOnActivities = typeof cbOnActivities === 'function'
3457
+ let next_request = null
3458
+ do {
3459
+ const activities = await this.getFriendActivity(next_request)
3460
+ next_request = activities.next_request
3461
+ if(activities.activity instanceof Array) {
3462
+ iscbOnActivities && cbOnActivities?.(activities.activity)
3463
+ iscbOnActivity && activities.activity.forEach(function (a) {
3464
+ cbOnActivity?.(a)
3465
+ })
3466
+ }
3467
+ } while (next_request)
3468
+ }
3469
+
3470
+ async searchSteamUserByName(text, page = 1) {
3471
+ const {data} = await this._httpRequestAjax(`search/SearchCommunityAjax?text=${encodeURIComponent(text)}&filter=users&sessionid=${this._sessionid}&steamid_user=${this.getSteamidUser()}&page=${page}`)
3472
+ if(data?.success === 1) {
3473
+ let {
3474
+ html,
3475
+ search_filter,
3476
+ search_page,
3477
+ search_result_count,
3478
+ search_text,
3479
+ success
3480
+ } = data
3481
+ search_page = parseInt(search_page)
3482
+ const $ = cheerio.load(html.replaceAll(/[\t\n\r]/gi, '').trim())
3483
+ const players = []
3484
+ $('.search_row').each(function (index, el) {
3485
+ el = $(el)
3486
+ const miniprofile = parseInt(el.find('.mediumHolder_default[data-miniprofile]').attr('data-miniprofile'))
3487
+ const avatarHash = SteamUser.GetAvatarHashFromURL(el.find('.avatarMedium a img').attr('src'))
3488
+ const searchPersonaEl = el.find('a.searchPersonaName')
3489
+ const name = searchPersonaEl.text()?.trim()
3490
+ const profileURL = searchPersonaEl.attr('href')
3491
+ players.push({
3492
+ miniprofile,
3493
+ steamID: SteamUser.miniprofile2SteamID64(miniprofile),
3494
+ avatarHash,
3495
+ name,
3496
+ profileURL,
3497
+ customURL: SteamUser.getCustomURL_from_ProfileURL(profileURL),
3498
+ })
3499
+ })
3500
+
3501
+ let nextPage = null,
3502
+ prevPage = null
3503
+ $('.community_searchresults_paging a').each(function (index, pagingEl) {
3504
+ pagingEl = $(pagingEl)
3505
+ const onclick = pagingEl.attr('onclick')
3506
+ if(onclick.includes('CommunitySearch.PrevPage()')) {
3507
+ prevPage = page - 1
3508
+ }
3509
+ else if(onclick.includes('CommunitySearch.NextPage()')) {
3510
+ nextPage = page + 1
3511
+ }
3512
+ })
3513
+
3514
+ return {
3515
+ players,
3516
+ prevPage,
3517
+ nextPage,
3518
+ search_filter,
3519
+ search_page,
3520
+ search_result_count,
3521
+ search_text,
3522
+ success,
3523
+ }
3524
+ }
3525
+
3526
+ return {
3527
+ success: 0
3528
+ }
3529
+ }
3530
+
3531
+ async getMyGroupsList() {
3532
+ const steamID = this.getSteamidUser()
3533
+ return await this.getUserGroupsList(steamID)
3534
+ }
3535
+
3536
+ async getUserGroupsList(steamID = this.getSteamidUser()) {
3537
+ return await SteamUser.getUserGroupsList(steamID, this.getCookies())
3538
+ }
3539
+
3540
+ static async getUserGroupsList(steamID, cookie) {
3541
+ const {data} = (await request({
3542
+ baseURL: SteamcommunityURL,
3543
+ url: `https://steamcommunity.com/profiles/${steamID}/groups/`,
3544
+ headers: {...cookie && {cookie}},
3545
+ }))
3546
+ return SteamUser._parseUserGroupList(data?.replaceAll(/[\t\n\r]/gi, ''))
3547
+ }
3548
+
3549
+ static _parseUserGroupList(html) {
3550
+ if(!html) {
3551
+ return
3552
+ }
3553
+ const $ = cheerio.load(html)
3554
+ const groups = []
3555
+ $('#groups_list #search_results .group_block').each(function (index, groupEl) {
3556
+ groupEl = $(groupEl)
3557
+ const groupTitleEl = groupEl.find('.group_block_details a.linkTitle')
3558
+ const link = groupTitleEl.attr('href').removeSuffix('/')
3559
+ const url = link.substringAfter('steamcommunity.com/groups/').removeSuffix('/')
3560
+ const name = groupTitleEl.text()?.trim()
3561
+ const gid = groupEl.find('.groupMemberStat[href*="OpenGroupChat"]').attr('href')?.substringBetweenOrNull(`'`, `'`)
3562
+ const avatarHash = SteamUser.GetAvatarHashFromURL(groupEl.find('div[class*="avatar"] a img[src*="avatars"]').attr('src'))
3563
+ const groupMemberStat = [...groupEl.find('a, .groupMemberStat')].map(e => $(e).text()?.trim()).filter(Boolean)
3564
+ const memberCount = SteamUser._formatString2Int(groupMemberStat.find(text => text.endsWith('Members'))?.removeSuffix('Members'))
3565
+ const membersInGame = SteamUser._formatString2Int(groupMemberStat.find(text => text.endsWith('In-Game'))?.removeSuffix('In-Game'))
3566
+ const membersOnline = SteamUser._formatString2Int(groupMemberStat.find(text => text.endsWith('Online'))?.removeSuffix('Online'))
3567
+ const membersInChat = SteamUser._formatString2Int(groupMemberStat.find(text => text.endsWith('In Group Chat'))?.removeSuffix('In Group Chat'))
3568
+
3569
+ groups.push({
3570
+ id: gid,
3571
+ link,
3572
+ name,
3573
+ url,
3574
+ avatarHash,
3575
+ memberCount,
3576
+ membersInGame,
3577
+ membersOnline,
3578
+ membersInChat,
3579
+ })
3580
+ })
3581
+ return groups
3582
+ }
3583
+
3584
+ async getNotifications() {
3585
+ const {data} = await this._httpRequest(`/actions/GetNotificationCounts`)
3586
+ if(!data) {
3587
+ return
3588
+ }
3589
+
3590
+ const exampleJSON = {
3591
+ notifications: {
3592
+ 1: 0,
3593
+ 2: 0,
3594
+ 3: 0,
3595
+ 4: 0,
3596
+ 5: 0,
3597
+ 6: 0,
3598
+ 8: 0,
3599
+ 9: 0,
3600
+ 10: 0,
3601
+ 11: 0
3602
+ }
3603
+ }
3604
+ const mapping1 = {
3605
+ 1: 'Trade offers',
3606
+ 2: 'Games notification (turns waiting)',
3607
+ 3: 'Community messages',
3608
+ 4: 'New comments',
3609
+ 5: 'Inventory',
3610
+ 6: 'Invites',
3611
+ 8: 'Gifts',
3612
+ 9: 'Chat messages',
3613
+ 10: 'Replies from Steam Support',
3614
+ 11: 'Account warning or alert'
3615
+ }
3616
+ const mapping = {
3617
+ 1: 'trades',
3618
+ 2: 'gameTurns',
3619
+ 3: 'moderatorMessages',
3620
+ 4: 'comments',
3621
+ 5: 'items',
3622
+ 6: 'invites',
3623
+ 8: 'gifts',
3624
+ 9: 'chat',
3625
+ 10: 'helpRequestReplies',
3626
+ 11: 'accountAlerts'
3627
+ }
3628
+
3629
+ if(data?.notifications) {
3630
+ Object.keys(data.notifications).forEach(key => {
3631
+ const value = data.notifications[key]
3632
+ delete data.notifications[key]
3633
+ data.notifications[mapping[key]] = value
3634
+ })
3635
+ }
3636
+ return data
3637
+
3638
+ const example = {
3639
+ notifications: {
3640
+ trades: 0,
3641
+ gameTurns: 0,
3642
+ moderatorMessages: 0,
3643
+ comments: 0,
3644
+ items: 0,
3645
+ invites: 8,
3646
+ gifts: 0,
3647
+ chat: 0,
3648
+ helpRequestReplies: 0,
3649
+ accountAlerts: 0
3650
+ }
3651
+ }
3652
+ }
3653
+
3654
+ async addFreeLicense(appID) {
3655
+ const {data} = await this._httpRequestAjax({
3656
+ url: `https://store.steampowered.com/checkout/addfreelicense/${appID}`,
3657
+ data: {
3658
+ ajax: true
3659
+ },
3660
+ method: 'POST',
3661
+ })
3662
+ return Array.isArray(data) && !data.length
3663
+ }
3664
+
3665
+ async addSubFreeLicense(subid) {
3666
+ const {data} = await this._httpRequestAjax({
3667
+ url: `https://store.steampowered.com/checkout/addfreelicense/`,
3668
+ data: {
3669
+ snr: '1_5_9__403',
3670
+ originating_snr: '1_store-navigation__',
3671
+ action: 'add_to_cart',
3672
+ subid: subid,
3673
+ },
3674
+ method: 'POST',
3675
+ })
3676
+
3677
+ if(data?.includes('There was a problem adding this product to your Steam account.')) {
3678
+ return false
3679
+ }
3680
+
3681
+ return data
3682
+ }
3683
+
3684
+ requestFreeLicense = this.addFreeLicense
3685
+
3686
+ async getCurrentSteamLogin() {
3687
+ const result = await this._httpRequest({
3688
+ url: `${this.getMySteamUserProfileURL()}/games/?tab=all`,
3689
+ })
3690
+ const $ = result._$()
3691
+ return $('.clientConnMachineText').text()?.trim()?.substringBeforeLast('|')?.trim()
3692
+ }
3693
+
3694
+ //not working
3695
+ async setLanguagePreferences() {
3696
+ const primary_language_list = ['greek', 'dutch', 'norwegian', 'danish', 'german', 'russian', 'romanian', 'vietnamese', 'bulgarian', 'swedish', 'spanish', 'latam', 'english', 'ukrainian', 'italian', 'japanese', 'schinese', 'tchinese', 'czech', 'thai', 'turkish', 'brazilian', 'portuguese', 'polish', 'french', 'finnish', 'koreana', 'hungarian']
3697
+ const formData = new URLSearchParams()
3698
+ formData.append('primary_language', 'koreana')
3699
+ const {data} = await this._httpRequestAjax({
3700
+ url: 'https://store.steampowered.com/account/savelanguagepreferences', // data: 'primary_language=english',
3701
+ data: formData,
3702
+ headers: {
3703
+ referer: 'https://store.steampowered.com/account/languagepreferences'
3704
+ },
3705
+ method: 'POST'
3706
+ })
3707
+ return data
3708
+ }
3709
+
3710
+ async ChangeLanguage() {
3711
+ const primary_language_list = ['greek', 'dutch', 'norwegian', 'danish', 'german', 'russian', 'romanian', 'vietnamese', 'bulgarian', 'swedish', 'spanish', 'latam', 'english', 'ukrainian', 'italian', 'japanese', 'schinese', 'tchinese', 'czech', 'thai', 'turkish', 'brazilian', 'portuguese', 'polish', 'french', 'finnish', 'koreana', 'hungarian']
3712
+ const formData = new URLSearchParams()
3713
+ formData.append('language', 'koreana')
3714
+ const {data} = await this._httpRequestAjax({
3715
+ url: 'https://steamcommunity.com/actions/SetLanguage/', // data: 'primary_language=english',
3716
+ data: formData,
3717
+ method: 'POST'
3718
+ })
3719
+ return data
3720
+ }
3721
+
3722
+ //Personal Game Data
3723
+ async getCompetitiveCooldownLevel() {
3724
+ const result = await this._httpRequest({
3725
+ url: `${this.getMySteamUserProfileURL()}/gcpd/730/?tab=matchmaking`,
3726
+ })
3727
+ const $ = result._$()
3728
+
3729
+ const $table = $('table.generic_kv_table')
3730
+ let cooldown_table = table2json($, getTableHasHeaders($, $table, ['Competitive Cooldown Expiration']))
3731
+ let matchmaking_detail_table = table2json($, getTableHasHeaders($, $table, ['Matchmaking Mode', 'Wins', 'Skill Group']))
3732
+ let matchmaking_table = getTableHasHeaders($, $table, ['Matchmaking Mode', 'Last Match']).map(t => table2json($, t)).filter(t => Object.keys(t[0]).length === 2)[0]
3733
+
3734
+ cooldown_table = cooldown_table.map(function (object) {
3735
+ removeSpaceKeys(object)
3736
+ return {
3737
+ Acknowledged: object.Acknowledged,
3738
+ Competitive_Cooldown_Expiration: object.Competitive_Cooldown_Expiration,
3739
+ Competitive_Cooldown_Level: isNaN(parseInt(object.Competitive_Cooldown_Level)) ? object.Competitive_Cooldown_Level : parseInt(object.Competitive_Cooldown_Level),
3740
+ }
3741
+ })
3742
+
3743
+ matchmaking_detail_table = matchmaking_detail_table.map(function (object) {
3744
+ removeSpaceKeys(object)
3745
+ return {
3746
+ Wins: isNaN(parseInt(object.Wins)) ? object.Wins : parseInt(object.Wins),
3747
+ Ties: isNaN(parseInt(object.Ties)) ? object.Ties : parseInt(object.Ties),
3748
+ Losses: isNaN(parseInt(object.Losses)) ? object.Losses : parseInt(object.Losses),
3749
+ Matchmaking_Mode: object.Matchmaking_Mode,
3750
+ Skill_Group: isNaN(parseInt(object.Skill_Group)) ? object.Skill_Group : parseInt(object.Skill_Group),
3751
+ Last_Match: object.Last_Match,
3752
+ }
3753
+ })
3754
+ matchmaking_table = matchmaking_table.map(function (object) {
3755
+ removeSpaceKeys(object)
3756
+ return {
3757
+ Matchmaking_Mode: object.Matchmaking_Mode,
3758
+ Last_Match: object.Last_Match,//2022-05-02 15:39:48 GMT
3759
+ }
3760
+ })
3761
+
3762
+ return {
3763
+ cooldown_table,
3764
+ matchmaking_detail_table,
3765
+ matchmaking_table,
3766
+ }
3767
+
3768
+ const resultExample = {
3769
+ cooldown_table: [{
3770
+ Acknowledged: 'No',
3771
+ Competitive_Cooldown_Expiration: '2022-09-10 01:40:36 GMT',
3772
+ Competitive_Cooldown_Level: 1
3773
+ }],
3774
+ matchmaking_detail_table: [{
3775
+ Wins: 151,
3776
+ Ties: 20,
3777
+ Losses: 218,
3778
+ Matchmaking_Mode: 'Competitive',
3779
+ Skill_Group: 4,
3780
+ Last_Match: '2022-08-07 19:30:42 GMT'
3781
+ }, {
3782
+ Wins: 1,
3783
+ Ties: 0,
3784
+ Losses: 1,
3785
+ Matchmaking_Mode: 'Wingman',
3786
+ Skill_Group: '',
3787
+ Last_Match: '2020-05-29 14:03:30 GMT'
3788
+ }],
3789
+ matchmaking_table: [{
3790
+ Matchmaking_Mode: 'Competitive',
3791
+ Last_Match: '2022-09-10 01:13:09 GMT'
3792
+ }, {
3793
+ Matchmaking_Mode: 'Deathmatch',
3794
+ Last_Match: '2022-08-16 16:31:35 GMT'
3795
+ }]
3796
+ }
3797
+ }
3798
+
3799
+ async getPersonalGameDataAccountInformation() {
3800
+ const result = await this._httpRequest({
3801
+ url: `${this.getMySteamUserProfileURL()}/gcpd/730/?tab=accountmain`,
3802
+ })
3803
+ if(!result?.data) {
3804
+ return
3805
+ }
3806
+ const $ = result._$()
3807
+
3808
+ const $table = $('table.generic_kv_table')
3809
+ const accountmain_table = table2json($, getTableHasHeaders($, $table, ['Recorded Activity', 'Activity Time']))
3810
+ .reduce(function (previousValue, currentValue, currentIndex, array) {
3811
+ return {
3812
+ ...previousValue,
3813
+ [currentValue['Recorded Activity']]: currentValue['Activity Time']
3814
+ }
3815
+ }, {})
3816
+
3817
+ const extraInfo = [...($('.generic_kv_table .generic_kv_line'))]
3818
+ .map(lineEl => $(lineEl).text()?.trim()?.replaceAll('CS:GO', 'CSGO'))
3819
+ .reduce(function (previousValue, currentValue, currentIndex, array) {
3820
+ const key = currentValue.substringBefore(':')?.trim()
3821
+ const value = currentValue.substringAfter(':')?.trim()
3822
+ previousValue[key] = value
3823
+ return previousValue
3824
+ }, {})
3825
+
3826
+ const doc = {
3827
+ ...accountmain_table, ...extraInfo,
3828
+ }
3829
+
3830
+ const cleanDoc = getCleanObject(doc)
3831
+
3832
+ return {
3833
+ Logged_out_of_CS_GO: cleanDoc.Logged_out_of_CS_GO,//'2022-09-09 16:14:54 GMT'
3834
+ Launched_CS_GO_using_Steam_Client: cleanDoc.Launched_CS_GO_using_Steam_Client,//'2022-05-06 10:08:29 GMT'
3835
+ Started_playing_CS_GO: cleanDoc.Started_playing_CS_GO,//'2022-05-06 10:08:29 GMT'
3836
+ First_Counter_Strike_franchise_game: cleanDoc.First_Counter_Strike_franchise_game,//'2022-05-06 10:07:35 GMT'
3837
+ Last_known_IP_address: cleanDoc.Last_known_IP_address,//'42.1.70.156'
3838
+ Earned_a_Service_Medal: cleanDoc.Earned_a_Service_Medal,//'No' Yes
3839
+ CSGO_Profile_Rank: isNaN(parseInt(cleanDoc.CSGO_Profile_Rank)) ? cleanDoc.CSGO_Profile_Rank : parseInt(cleanDoc.CSGO_Profile_Rank),
3840
+ Experience_points_earned_towards_next_rank: isNaN(parseInt(cleanDoc.Experience_points_earned_towards_next_rank)) ? cleanDoc.Experience_points_earned_towards_next_rank : parseInt(cleanDoc.Experience_points_earned_towards_next_rank),
3841
+ Anti_addiction_online_time: cleanDoc.Anti_addiction_online_time,//'0:11:25'
3842
+ }
3843
+ }
3844
+
3845
+ async resolveUsers(steamIDs = []) {
3846
+ return await SteamUser.resolveUsers(steamIDs, this.getCookies())
3847
+ }
3848
+
3849
+ static async resolveUsers(steamIDs = [], cookie) {//steamIDs max = 200
3850
+ if(typeof steamIDs === 'string') {
3851
+ steamIDs = [steamIDs]
3852
+ }
3853
+
3854
+ steamIDs = _.uniq(steamIDs)
3855
+
3856
+ const {data} = await request({
3857
+ url: `https://steamcommunity.com/actions/ajaxresolveusers?steamids=${steamIDs.join(',')}`,
3858
+ headers: {...cookie && {cookie}},
3859
+ })
3860
+
3861
+ if(!Array.isArray(data)) {
3862
+ return []
3863
+ }
3864
+
3865
+ return data.map(function (player) {
3866
+ return {
3867
+ steamID: player.steamid,
3868
+ accountid: player.accountid,
3869
+ name: player.persona_name,
3870
+ realname: player.real_name,
3871
+ avatarHash: player.avatar_url,
3872
+ customURL: player.profile_url,
3873
+ persona_state: player.persona_state,
3874
+ city: player.city,
3875
+ state: player.state,
3876
+ country: player.country,
3877
+ is_friend: player.is_friend,
3878
+ friends_in_common: player.friends_in_common,
3879
+ }
3880
+ })
3881
+ }
3882
+
3883
+ static GetAvatarURLFromHash(hash, size) {
3884
+ if(!hash) {
3885
+ return
3886
+ }
3887
+ let strURL = 'https://avatars.akamai.steamstatic.com/' + hash
3888
+
3889
+ if(size === 'full') {
3890
+ strURL += '_full.jpg'
3891
+ }
3892
+ else if(size === 'medium') {
3893
+ strURL += '_medium.jpg'
3894
+ }
3895
+ else {
3896
+ strURL += '.jpg'
3897
+ }
3898
+
3899
+ return strURL
3900
+ }
3901
+
3902
+ static GetAvatarHashFromURL(url) {
3903
+ if(!url) {
3904
+ return
3905
+ }
3906
+ const suffixes = ['_full.jpg', '_medium.jpg', '.jpg']
3907
+
3908
+ for (const suffix of suffixes) {
3909
+ if(url.includes(suffix)) {
3910
+ return url.split(suffix)[0].split('/').pop()
3911
+ }
3912
+ }
3913
+ }
3914
+
3915
+ static GetAvatarHashFromMultipleURL(urls = []) {
3916
+ let avatarHash
3917
+ for (const avatar of urls) {
3918
+ if((avatarHash = SteamUser.GetAvatarHashFromURL(avatar))) {
3919
+ break
3920
+ }
3921
+ }
3922
+ return avatarHash
3923
+ }
3924
+
3925
+ static createFriendCode(steamID) {
3926
+ const accountid = SteamUser.steamID642Miniprofile(steamID)
3927
+ let acctIdHex = accountid.toString(16)
3928
+ let friendCode = ''
3929
+
3930
+ for (let i = 0; i < acctIdHex.length; i++) {
3931
+ let char = parseInt(acctIdHex[i], 16)
3932
+ friendCode += FRIEND_CODE_REPLACEMENTS[char]
3933
+ }
3934
+
3935
+ let dashPos = Math.floor(friendCode.length / 2)
3936
+ return friendCode.substring(0, dashPos) + '-' + friendCode.substring(dashPos)
3937
+ }
3938
+
3939
+ static parseFriendCode(friendCode) {
3940
+ friendCode = friendCode.replaceAll('-', '')
3941
+ let acctIdHex = ''
3942
+
3943
+ for (let i = 0; i < friendCode.length; i++) {
3944
+ let char = friendCode[i]
3945
+ acctIdHex += FRIEND_CODE_REPLACEMENTS.indexOf(char).toString(16)
3946
+ }
3947
+
3948
+ return new SteamID(`[U:1:${parseInt(acctIdHex, 16)}]`)
3949
+ };
3950
+
3951
+ static _formatString2Int(text) {
3952
+ const count = parseInt(text?.replaceAll(',', '')?.trim())
3953
+ return isNaN(count) ? null : count
3954
+ }
3955
+
3956
+ async testFullLanguage(cb) {
3957
+ const self = this
3958
+ const initialLanguage = self.Steam_Language
3959
+ const langs = Object.values(ELanguage)
3960
+ _.pull(langs, [ELanguage.english])
3961
+ for (const lang of [ELanguage.english, ...langs]) {
3962
+ self.setSteamLanguage(lang)
3963
+ if(cb.constructor.name === 'AsyncFunction') {
3964
+ // 👇️ this runs
3965
+ // console.log('✅ function is async')
3966
+ await cb.call(self, lang)
3967
+ }
3968
+ else {
3969
+ // console.log('⛔️ function is NOT async')
3970
+ cb.call(self, lang)
3971
+ }
3972
+ }
3973
+ self.setSteamLanguage(initialLanguage)
3974
+ }
3975
+
3976
+ async testNotYetSetupTextList(steamID) {//steamID that not setup
3977
+ const notYetSetupTexts = []
3978
+ await this.testFullLanguage(async function (lang) {
3979
+ const sum = await this.getUserSummaryFromProfile(steamID)
3980
+ notYetSetupTexts.push(sum.profile_private_info)
3981
+ })
3982
+ return _.isEqual(NotYetSetupProfileTextList, notYetSetupTexts)
3983
+ }
3984
+
3985
+ async testPrivateText(steamID) {//steamID that private
3986
+ const privateTexts = []
3987
+ await this.testFullLanguage(async function (lang) {
3988
+ const sum = await this.getUserSummaryFromProfile(steamID)
3989
+ privateTexts.push(sum.profile_private_info)
3990
+ })
3991
+ return _.isEqual(PrivateProfileTextList, privateTexts)
3992
+ }
3993
+
3994
+ async testGameBan(steamID) {//steamID that private
3995
+ const list = []
3996
+ await this.testFullLanguage(async function (lang) {
3997
+ const sum = await this.getUserSummaryFromProfile(steamID)
3998
+ if(sum.sectionText) {
3999
+ list.push(sum.sectionText.replace('1 ', '$1').replace('1162 ', '$2'))
4000
+ // list.push(sum.gameBanFull.replace('1 ', '$1').replace('1162 ', '$2'))
4001
+ }
4002
+ })
4003
+ return list
4004
+ }
4005
+
4006
+ static parseGameBanType(gameBanFull) {
4007
+ if(Array.isArray(gameBanFull) && !gameBanFull.length) {
4008
+ return {
4009
+ isVACBan: 0,
4010
+ isGameBan: 0,
4011
+ isTradeBan: 0,
4012
+ daysSinceLastBan: null,
4013
+ }
4014
+ }
4015
+
4016
+ const isTradeBan = gameBanFull.some(txt => ECurrentlyTradeBanned.includes(txt)) ? 1 : 0
4017
+
4018
+ let isVACBan = 0
4019
+ if(gameBanFull.some(txt => E1VACBanOnRecord.includes(txt))) {
4020
+ isVACBan = 1
4021
+ }
4022
+ else if(gameBanFull.some(txt => EMultipleVACBansOnRecord.includes(txt))) {
4023
+ isVACBan = 'Multiple'
4024
+ }
4025
+
4026
+ let isGameBan = 0
4027
+ if(gameBanFull.some(txt => E1GameBanOnRecord.includes(txt))) {
4028
+ isGameBan = 1
4029
+ }
4030
+ else if(gameBanFull.some(txt => EMultipleGameBansOnRecord.includes(txt))) {
4031
+ isGameBan = 'Multiple'
4032
+ }
4033
+
4034
+ let daysSinceLastBan = gameBanFull
4035
+ .map(function (gameBanPart) {
4036
+ return Object.values(EdaySinceLastBanRegExp)
4037
+ .map(s => [...gameBanPart.matchAll(new RegExp(s, 'gi'))])
4038
+ .filter(s => s.length)
4039
+ })
4040
+ .filter(s => s.length)
4041
+
4042
+ if(Array.isArray(daysSinceLastBan?.[0]?.[0]?.[0])) {
4043
+ daysSinceLastBan = parseInt(daysSinceLastBan[0][0][0][1])
4044
+ if(isNaN(daysSinceLastBan)) {
4045
+ //error
4046
+ }
4047
+ }
4048
+ else {
4049
+ daysSinceLastBan = null
4050
+ }
4051
+
4052
+ if(!isTradeBan && !isVACBan && !isGameBan) {
4053
+ console.error('parseGameBanType Error', gameBanFull)
4054
+ }
4055
+
4056
+ return {
4057
+ isVACBan,
4058
+ isGameBan,
4059
+ isTradeBan,
4060
+ daysSinceLastBan
4061
+ }
4062
+ }
4063
+ }
4064
+
4065
+ export default SteamUser