Advertisement
Guest User

autogartic.pyw

a guest
Feb 13th, 2022
54
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 20.90 KB | None | 0 0
  1. '''
  2. Бот для автопостинга картинок и текста в гартик
  3. v0.1
  4.  
  5. FAQ:
  6. Q: как запустить бота?
  7. A: 1) устанавливаете python версии не ниже 3.6
  8.   2) устанавливаете библиотеки через консоль командой: python -m pip install websocket_client requests pillow
  9.   3) запускаете этот файл (если не работает через даблклик, то через консоль командой: python autogartic.pyw)
  10.   4) вставляете в бота ссылку на игру, никнейм/куки, путь к файлу с фразами и к папке с картинками, нажимаете "подключиться". Бот сам наченет постить при старте игры
  11.  
  12. Q: что за файл с фразами?
  13. A: это строки, которые будут использоваться как предложения в начале игры и при описании рисунков. Они вставляются в игру в случайном порядке, как и картинки из папки
  14.  
  15. Q: куда сохраняются настройки
  16. A: в файл autogartic_settings.json в той же папке, где находится бот
  17.  
  18. Q: где взять куки для логина через твич?
  19. A: 1) в браузере открываете сетевой лог (в firefox F12, в хроме и прочих ctrl-shift-J)
  20.   2) создаете свое лобби в гартике с авторизацией через твич
  21.   3) в сетевых запросах ищете запрос на домен id.twitch.tv
  22.   4) в запросе ищете куки с названием persistent, должно быть что-то типа 701234671::safnk4jtn3nbgieru656hosd7, копируете эту строчку в бота
  23. '''
  24.  
  25. from PIL import Image
  26. import websocket
  27. import requests
  28.  
  29. import tkinter as tk
  30. from tkinter import RIGHT, ttk
  31. import tkinter.scrolledtext as scrolledtext
  32. from tkinter import filedialog
  33. import random
  34. import time
  35. import threading
  36. import json
  37. import re
  38. import datetime
  39. import os
  40.  
  41. # global vars
  42. logBox = None
  43. imagesList = None
  44. phrasesList = None
  45. playerUuid = None
  46.  
  47. def log(s):
  48.     t = datetime.datetime.now().strftime("%H:%M:%S")
  49.     logBox.insert(tk.END, f'[{t}] {s}\n')
  50.     logBox.see(tk.END)
  51.  
  52. def getRandomUuid():
  53.     s = []
  54.     for i in range(36):
  55.         s.append(random.choice('af'))
  56.     s[8] = '-'
  57.     s[13] = '-'
  58.     s[18] = '-'
  59.     s[23] = '-'
  60.     return ''.join(s)
  61.  
  62. class ImagesList:
  63.     def __init__(self, dir) -> None:
  64.         if not os.path.isdir(dir):
  65.             raise Exception('указана неверная папка с картинками')
  66.         self.filenames = [os.path.join(dir, fname) for fname in os.listdir(dir) \
  67.             if (fname.lower().endswith('.png') or fname.lower().endswith('.jpg'))]
  68.         if len(self.filenames) < 1:
  69.             raise Exception('в указанной папке нет картинок')
  70.         random.shuffle(self.filenames)
  71.         self.imageNum = 0
  72.  
  73.     def getNext(self):
  74.         fname = self.filenames[self.imageNum]
  75.         self.imageNum += 1
  76.         if self.imageNum == len(self.filenames):
  77.             self.imageNum = 0
  78.         return fname
  79.  
  80. class PhrasesList:
  81.     def __init__(self, path) -> None:
  82.         if not os.path.isfile(path):
  83.             raise Exception('указан неверный файл с фразами')
  84.         with open(path, encoding='utf8') as f:
  85.             self.phrases = f.read().split('\n')
  86.         random.shuffle(self.phrases)
  87.         self.phraseNum = 0
  88.  
  89.     def getNext(self):
  90.         phrase = self.phrases[self.phraseNum]
  91.         self.phraseNum += 1
  92.         if self.phraseNum == len(self.phrases):
  93.             self.phraseNum = 0
  94.         return phrase
  95.  
  96. def rgb2hex(r, g, b):
  97.     return '#{:02x}{:02x}{:02x}'.format(r, g, b)
  98.  
  99. def imgToMoves(turnNum, shakalValue, filename):
  100.     colorsDict = {}
  101.     img = Image.open(filename)
  102.     img = img.resize((758, 424), Image.ANTIALIAS)
  103.     for x in range(img.width):
  104.         for y in range(img.height):
  105.             if shakalValue > 1 and (x % shakalValue != 0 or y % shakalValue != 0):
  106.                 continue
  107.             pixel = img.getpixel((x,y))
  108.             colorHex = rgb2hex(pixel[0], pixel[1], pixel[2])
  109.             if not colorsDict.get(colorHex):
  110.                 colorsDict[colorHex] = f'[8,-1,["{colorHex}",1],{x},{y},{shakalValue},{shakalValue}'
  111.             else:
  112.                 colorsDict[colorHex] += f',{x},{y},{shakalValue},{shakalValue}'
  113.     img.close()
  114.  
  115.     movesArray = [colorsDict[i] for i in colorsDict]
  116.     random.shuffle(movesArray)
  117.  
  118.     moves = ['42[2,7,{"t":'+str(turnNum)+',"d":1,"v":'+move+']}]' for move in movesArray]
  119.     return moves
  120.  
  121. class WebsocketClient(threading.Thread):
  122.     def __init__(self, svServerHost, sid, userId) -> None:
  123.         threading.Thread.__init__(self)
  124.         self.isCloseRequest = False
  125.         self.svServerHost = svServerHost
  126.         self.sid = sid
  127.         self.userId = userId
  128.  
  129.     def on_message(self, ws, message):
  130.         if type(message) != str or message == '3' or message == '3probe':
  131.             return
  132.  
  133.         if message.startswith('42[2,5'):
  134.             log('Игра началась')
  135.         elif message.startswith('42[2,11'):
  136.             turnNum = json.loads(message[2:])[2]['turnNum']
  137.             screen = json.loads(message[2:])[2]['screen']
  138.             if screen == 3 or screen == 4:
  139.                 phrase = phrasesList.getNext()
  140.                 log(f'Ход {turnNum}, отправляем фразу "{phrase}"')
  141.                 ws.send('42[2,6,{"t":'+str(turnNum)+',"v":"'+phrase+'"}]')
  142.             elif screen == 5:
  143.                 try:
  144.                     filename = imagesList.getNext()
  145.                     log(f'Ход {turnNum}, отправляем картинку "{os.path.basename(filename)}"')
  146.                     for shakalValue in [4,2]:
  147.                         movesArray = imgToMoves(turnNum, shakalValue, filename)
  148.                         for move in movesArray:
  149.                             ws.send(move)
  150.                 except Exception as e:
  151.                     log(f'Ошибка при выгрузке картинки: {e}')
  152.             ws.send('42[2,15,true]') # сообщаем о готовности
  153.         elif message.startswith('42[2,15'):
  154.             pass # статус готовности других игроков
  155.         elif message.startswith('42[2,24'):
  156.             log('Игра закончена, игроки смотрят альбомы')
  157.         elif message.startswith('42[2,23') or message.startswith('42[2,12') or message.startswith('42[2,9'):
  158.             pass # просмотр ходов в альбоме
  159.         elif message.startswith('42[2,20]'):
  160.             log('Альбомы просмотрены, выходим в лобби')
  161.         elif message.startswith('42[2,14'):
  162.             id = json.loads(message[2:])[2]
  163.             if id != self.userId:
  164.                 log(f'Игрок с id {id} был забанен')
  165.             else:
  166.                 log(f'Бот был забанен, отключаемся и создаем новый ID')
  167.                 global playerUuid
  168.                 playerUuid = getRandomUuid()
  169.                 self.isCloseRequest = True
  170.         else:
  171.             # log(f'Неизвестный тип сообщения: {message}')
  172.             pass
  173.  
  174.     def on_error(self, ws, error):
  175.         log(f'WebSocket error: {error}')
  176.  
  177.     def on_close(self, ws, close_status_code, close_msg):
  178.         self.isCloseRequest = True
  179.         log(f'WebSocket closed: status: {close_status_code}, msg: {close_msg}')
  180.  
  181.     def on_open(self, ws):
  182.         log('WebSocket opened, starting new thread')
  183.         def pingFunc():
  184.             ws.send("2probe")
  185.             time.sleep(3)
  186.             ws.send("5")
  187.             self.onConnected()
  188.             time.sleep(3)
  189.             while True:
  190.                 time.sleep(3)
  191.                 if self.isCloseRequest:
  192.                    break
  193.                 ws.send("2")
  194.             ws.close()
  195.         self.pingThread = threading.Thread(target=pingFunc)
  196.         self.pingThread.start()
  197.  
  198.     def setHandlers(self, onConnected, onDisconnected):
  199.         self.onConnected = onConnected
  200.         self.onDisconnected = onDisconnected
  201.  
  202.     def stop(self):
  203.         self.isCloseRequest = True
  204.  
  205.     def run(self):
  206.         ws = websocket.WebSocketApp(f"wss://{self.svServerHost}/socket.io/?EIO=3&transport=websocket&sid={self.sid}",
  207.                                 on_open=self.on_open,
  208.                                 on_message=self.on_message,
  209.                                 on_error=self.on_error,
  210.                                 on_close=self.on_close,
  211.                                 cookie=f'io={self.sid}')
  212.         ws.run_forever()
  213.         self.pingThread.join()
  214.         log('WebSocket threads terminated')
  215.         self.onDisconnected()
  216.  
  217. # method: 'anon' or 'twitch'
  218. def authentication(inviteLink, method, twitchCookie = None, nickname = None):
  219.     global playerUuid
  220.  
  221.     if method == 'anon' and not nickname:
  222.         raise Exception('пустой ник')
  223.     elif method == 'twitch' and not twitchCookie:
  224.         raise Exception('отсутствуют cookie')
  225.     elif method != 'twitch' and method != 'anon':
  226.         raise Exception('неизвестный метод аутентификации')
  227.  
  228.     avatarNum = random.choice([31,32,33,40,16,39])
  229.     inviteCode = re.search('.*/\?c=(.*)', inviteLink)
  230.     if not inviteCode:
  231.         raise Exception('не удалось найти код приглашения в ссылке')
  232.     inviteCode = inviteCode.groups()[0]
  233.  
  234.     garticSession = requests.Session()
  235.  
  236.     if method == 'twitch':
  237.         r = garticSession.get(f'https://garticphone.com/api/auth/providers')
  238.  
  239.         # 1) get csrf
  240.         r = garticSession.get(f'https://garticphone.com/api/auth/csrf')
  241.         csrfToken = json.loads(r.content.decode('utf8'))['csrfToken']
  242.  
  243.         # 2) get twitch auth url
  244.         r = garticSession.post('https://garticphone.com/api/auth/signin/twitch',
  245.             data={
  246.                 'callbackUrl': f'https://garticphone.com/ru/redirecting?avatar={avatarNum}&c={inviteCode}',
  247.                 'csrfToken': csrfToken,
  248.                 'json': 'true'
  249.             }
  250.         )
  251.         twitchAuthUrl = json.loads(r.content.decode('utf8'))['url']
  252.  
  253.         # 3) twitch auth
  254.         r = requests.get(twitchAuthUrl, cookies={
  255.             'persistent':twitchCookie,
  256.         })
  257.  
  258.         content = r.content.decode('utf8')
  259.         nextUrl = re.search('.*URL=\'(.*)\'" />.*', content).groups()[0]
  260.         nextUrl = nextUrl.replace('&amp;', '&')
  261.         nextUrl = nextUrl.replace('%3A', ':')
  262.  
  263.         # 4) get session token from gartic
  264.         garticSession.get(nextUrl)
  265.  
  266.     # get server
  267.     r = garticSession.get(f'https://garticphone.com/api/server?code={inviteCode}')
  268.     svServerHost = r.content.decode('utf8')
  269.  
  270.     # get sid
  271.     r = garticSession.get(f'{svServerHost}/socket.io/?EIO=3&transport=polling&t=1')
  272.     sid = re.search('.*sid":"(.*)","upgrades.*', r.content.decode('utf8')).groups()[0]
  273.  
  274.     # register in lobby
  275.     if method == 'twitch':
  276.         r = garticSession.get('https://garticphone.com/api/auth/session')
  277.         twitchJwt = json.loads(r.content.decode('utf8'))['code']
  278.  
  279.         data = f'42[1,"{playerUuid}",null,"{avatarNum}","ru",false,"{inviteCode}","{twitchJwt}",null]'
  280.         r = garticSession.post(f'{svServerHost}/socket.io/?EIO=3&transport=polling&t=2&sid={sid}',
  281.             data=f'{len(data)}:{data}'.encode('utf-8'),
  282.             cookies={'io':sid})
  283.     else:
  284.         data = f'42[1,"{playerUuid}","{nickname}","{avatarNum}","ru",false,"{inviteCode}",null,null]'
  285.         r = requests.post(f'{svServerHost}/socket.io/?EIO=3&transport=polling&t=2&sid={sid}',
  286.             data=f'{len(data)}:{data}'.encode('utf-8'))
  287.         if r.status_code != 200:
  288.             raise Exception(f'anon register status_code={r.status_code}')
  289.  
  290.     # get lobby object
  291.     r = requests.get(f'{svServerHost}/socket.io/?EIO=3&transport=polling&t=3&sid={sid}',
  292.         cookies={'io':sid})
  293.     if r.status_code != 200:
  294.         raise Exception(f'get lobby obj status_code={r.status_code}')
  295.     s = r.content.decode('utf8')
  296.     lobbyObj = json.loads(s[s.find('['):])
  297.     garticError = lobbyObj[1].get('error')
  298.     if garticError == 1:
  299.         raise Exception('некорректный ник')
  300.     elif garticError == 2:
  301.         playerUuid = getRandomUuid()
  302.         if method == 'anon':
  303.             raise Exception('вы забанены в этом лобби. Новый ID создан, переподключитесь')
  304.         else:
  305.             raise Exception('вы забанены в этом лобби. Используйте другой аккаунт')
  306.     elif garticError == 3:
  307.         raise Exception('лобби не существует')
  308.     elif garticError == 4:
  309.         raise Exception('в лобби уже максимальное число игроков')
  310.     elif garticError == 6:
  311.         raise Exception('вы уже находитесь в этом лобби')
  312.     elif garticError == 8:
  313.         raise Exception('cookie expired')
  314.     elif garticError == 9:
  315.         raise Exception('неверный метод аутентификации')
  316.     elif garticError:
  317.         raise Exception(f'ошибка при входе в лобби: {garticError}')
  318.  
  319.     garticUser = lobbyObj[1].get('user')
  320.     if not garticUser:
  321.         raise Exception(f'неизвестная ошибка при входе в лобби. Данные лобби: {s}')
  322.  
  323.     return WebsocketClient(svServerHost[8:], sid, garticUser['id'])
  324.  
  325. def start():
  326.     global logBox
  327.     global playerUuid
  328.     playerUuid = getRandomUuid()
  329.  
  330.     window = tk.Tk()
  331.     window.title('Autogartic v0.1 (by 2ch anon)')
  332.     window.geometry('700x800')
  333.  
  334.     logBox = scrolledtext.ScrolledText(window)
  335.  
  336.     ttk.Label(window, text='Ссылка на приглашение в игру:').pack(padx=5, pady=5)
  337.     inviteLinkEntry = ttk.Entry(window, width=70)
  338.     inviteLinkEntry.pack(padx=5, pady=5)
  339.  
  340.     AUTH_METHOD_ANON_STR = 'Анонимно'
  341.     AUTH_METHOD_TWITCH_STR = 'Через twitch'
  342.     ttk.Label(window, text='Метод аутентификации:').pack(padx=5, pady=5)
  343.     authMethodStr = tk.StringVar()
  344.     authMethodCombobox = ttk.Combobox(window, textvariable=authMethodStr)
  345.     authMethodCombobox['values'] = [AUTH_METHOD_ANON_STR, AUTH_METHOD_TWITCH_STR]
  346.     authMethodCombobox['state'] = 'readonly'
  347.     authMethodCombobox.pack(padx=5, pady=5)
  348.  
  349.     authFrame = ttk.Frame(window)
  350.     authFrame.pack()
  351.  
  352.     nickLabel = ttk.Label(authFrame, text='Ник:')
  353.     nickEntry = ttk.Entry(authFrame, width=30)
  354.  
  355.     twitchCookieLabel = ttk.Label(authFrame, text='Cookie (см. readme):')
  356.     twitchCookieEntry = ttk.Entry(authFrame, width=50)
  357.  
  358.     def authMethodChanged(event):
  359.         if authMethodStr.get() == AUTH_METHOD_ANON_STR:
  360.             nickLabel.pack(padx=5, pady=1, side=tk.LEFT)
  361.             nickEntry.pack(padx=5, pady=1, side=tk.LEFT)
  362.             twitchCookieLabel.pack_forget()
  363.             twitchCookieEntry.pack_forget()
  364.         elif authMethodStr.get() == AUTH_METHOD_TWITCH_STR:
  365.             twitchCookieLabel.pack(padx=5, pady=1, side=tk.LEFT)
  366.             twitchCookieEntry.pack(padx=5, pady=1, side=tk.LEFT)
  367.             nickLabel.pack_forget()
  368.             nickEntry.pack_forget()
  369.  
  370.     authMethodCombobox.bind('<<ComboboxSelected>>', authMethodChanged)
  371.  
  372.     ttk.Label(window, text='Путь к файлу с фразами:').pack(padx=5, pady=1)
  373.     phrasesPathEntry = ttk.Entry(window, width=70)
  374.     phrasesPathEntry.pack(padx=5, pady=1)
  375.     def onChoosePhrases(event=None):
  376.         filePathSelected = filedialog.askopenfilename()
  377.         phrasesPathEntry.delete(0, tk.END)
  378.         phrasesPathEntry.insert(0, filePathSelected)
  379.     choosePhrasesButton = ttk.Button(window, text="Открыть...", command=onChoosePhrases)
  380.     choosePhrasesButton.pack(padx=5, pady=5)
  381.  
  382.     ttk.Label(window, text='Путь к папке с картинками:').pack(padx=5, pady=1)
  383.     imagesDirEntry = ttk.Entry(window, width=70)
  384.     imagesDirEntry.pack(padx=5, pady=1)
  385.     def onChooseImages(event=None):
  386.         dirPathSelected = filedialog.askdirectory()
  387.         imagesDirEntry.delete(0, tk.END)
  388.         imagesDirEntry.insert(0, dirPathSelected)
  389.     chooseImagesButton = ttk.Button(window, text="Открыть...", command=onChooseImages)
  390.     chooseImagesButton.pack(padx=5, pady=5)
  391.  
  392.     startButton = None
  393.     isConnected = False
  394.     websocketClient = None
  395.  
  396.     def onConnected():
  397.         nonlocal isConnected
  398.         log('Подключено!')
  399.         startButton.config(text='Отключиться')
  400.         startButton["state"] = "enabled"
  401.         isConnected = True
  402.  
  403.     def onDisconnected():
  404.         nonlocal isConnected
  405.         log('Отключено!')
  406.         startButton.config(text='Подключиться')
  407.         startButton["state"] = "enabled"
  408.         isConnected = False
  409.  
  410.     def onConnectClick(event=None):
  411.         nonlocal isConnected
  412.         nonlocal websocketClient
  413.         inviteLink = inviteLinkEntry.get()
  414.         startButton["state"] = "disabled"
  415.         if not isConnected:
  416.             try:
  417.                 global phrasesList
  418.                 phrasesList = PhrasesList(phrasesPathEntry.get())
  419.                 global imagesList
  420.                 imagesList = ImagesList(imagesDirEntry.get())
  421.  
  422.                 if authMethodStr.get() == AUTH_METHOD_ANON_STR:
  423.                     websocketClient = authentication(inviteLink, 'anon', nickname=nickEntry.get())
  424.                 elif authMethodStr.get() == AUTH_METHOD_TWITCH_STR:
  425.                     websocketClient = authentication(inviteLink, 'twitch', twitchCookie=twitchCookieEntry.get())
  426.                 else:
  427.                     raise Exception('не выбран метод аутентификации')
  428.                 websocketClient.setHandlers(onConnected, onDisconnected)
  429.                 websocketClient.start()
  430.             except Exception as e:
  431.                 log(f'Ошибка при подключении: {e}')
  432.                 startButton.config(text='Подключиться')
  433.                 startButton["state"] = "enabled"
  434.         else:
  435.             if websocketClient:
  436.                 websocketClient.stop()
  437.             else:
  438.                 startButton.config(text='Подключиться')
  439.                 startButton["state"] = "enabled"
  440.                 isConnected = False
  441.  
  442.     def onSaveSettingsClick(event=None):
  443.         try:
  444.             with open('autogartic_settings.json', 'w', encoding='utf8') as f:
  445.                 settings = {
  446.                     'inviteLink':inviteLinkEntry.get(),
  447.                     'authMethod':authMethodStr.get(),
  448.                     'nick':nickEntry.get(),
  449.                     'twitchCookie':twitchCookieEntry.get(),
  450.                     'phrasesPath':phrasesPathEntry.get(),
  451.                     'imagesDir':imagesDirEntry.get(),
  452.                 }
  453.                 json.dump(settings, f)
  454.                 log('Настройки сохранены')
  455.         except Exception as e:
  456.             log(f'Ошибка при сохранении настроек: {e}')
  457.  
  458.     saveSettingsButton = ttk.Button(window, text="Сохранить настройки", command=onSaveSettingsClick)
  459.     saveSettingsButton.pack(padx=5, pady=(15, 5))
  460.  
  461.     startButton = ttk.Button(window, text="Подключиться", command=onConnectClick)
  462.     startButton.pack(padx=5, pady=5)
  463.  
  464.     ttk.Label(window, text='Лог:').pack()
  465.     logBox.pack(padx=5, pady=5)
  466.  
  467.     settings = {
  468.         'inviteLink':'',
  469.         'authMethod':AUTH_METHOD_ANON_STR,
  470.         'nick':'',
  471.         'twitchCookie':'',
  472.         'phrasesPath':'',
  473.         'imagesDir':'',
  474.     }
  475.     try:
  476.         with open('autogartic_settings.json', 'r', encoding='utf8') as f:
  477.             loadedSettings = json.load(f)
  478.             for k in settings.keys():
  479.                 settings[k] = loadedSettings[k]
  480.             log('Настройки загружены')
  481.     except FileNotFoundError:
  482.         pass
  483.     except Exception as e:
  484.         log(f'Ошибка при загрузке настроек: {e}')
  485.  
  486.     # set loaded settings
  487.     inviteLinkEntry.delete(0, tk.END)
  488.     inviteLinkEntry.insert(0, settings['inviteLink'])
  489.     authMethodCombobox.set(settings['authMethod'])
  490.     authMethodChanged(None)
  491.     nickEntry.delete(0, tk.END)
  492.     nickEntry.insert(0, settings['nick'])
  493.     twitchCookieEntry.delete(0, tk.END)
  494.     twitchCookieEntry.insert(0, settings['twitchCookie'])
  495.     phrasesPathEntry.delete(0, tk.END)
  496.     phrasesPathEntry.insert(0, settings['phrasesPath'])
  497.     imagesDirEntry.delete(0, tk.END)
  498.     imagesDirEntry.insert(0, settings['imagesDir'])
  499.  
  500.     window.mainloop()
  501.  
  502. start()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement