diff --git a/README.MD b/README.MD index 2958f36..af32124 100644 --- a/README.MD +++ b/README.MD @@ -4,12 +4,12 @@ # Maturador de chips -maturador de chips é um pequeno programa para "esquentar" contas do whatssap e dessa forma, evitar banimento da rede social. +maturador de chips é um pequeno programa para "esquentar" contas do whatssap e dessa forma, evitar banimento da rede social ## Instalação -Se você é usuário Windows, pode fazer download do instalador x64 ou siga as instruções abaixo para executar o código-fonte da versão de codigo aberto: +Se você é usuário Windows, pode fazer download do instalador x64 ou siga as instruções abaixo para executar o código-fonte da versão de codigo aberto: - Clone o repositório @@ -52,24 +52,10 @@ python main.py - escolher nome instância - remover instância -## Roadmap - -- Adicionar envio de arquivos -- Adicionar disparador - -## FAQ - - -### Quais são as portas usadas por este programa? - - -A porta 5025 é usada pela API e a porta 5026 é usada pelo websocket. - - -### Quais são os requisitos mínimos do sistema? - - -desenvolvi em uma maquina com processador i3 e 4GB de memoria RAM, portanto, se sua maquina possui configurações semelhantes ou superiores a essas, o maturador rodará sem problemas. +### Versões Alternativas: + + - https://github.com/APIBrasil/maturador-de-chips + - https://github.com/AdrianoF0nseca/maturador-de-chips ### Como gerar meu próprio executável? @@ -77,17 +63,10 @@ desenvolvi em uma maquina com processador i3 e 4GB de memoria RAM, portanto, se Este é o comando pyinstaller para gerar o executavel do maturador: ```shell -pyinstaller --noconfirm --onedir --windowed --icon "$(pwd)/pages/assets/medias/icon.ico" --add-data "$(pwd)/state.json;." --add-data "$(pwd)/user-agent;." --add-data "$(pwd)/LICENSE;." --add-data "$(pwd)/scripts;scripts/" --add-data "$(pwd)/pages;pages/" "$(pwd)/main.py" -``` - -Este é o comando pyinstaller para gerar o executável do servidor websocket: +pyinstaller --noconfirm --onedir --windowed --icon "(pwd)/pages/assets/medias/icon.ico" --add-data "(pwd)/pages;pages/" --add-data "(pwd)/scripts;scripts/" --add-data "(pwd)/LICENSE;." --add-data "(pwd)/README.MD;." --add-data "(pwd)/user-agent;." --add-data "(pwd)/requirements.txt;." "(pwd)/main.py" - -```shell -pyinstaller --noconfirm --onefile --windowed "$(pwd)/websocket_server.py" ``` - ## Licença diff --git a/controller.py b/controller.py index 486da3c..94f4b01 100644 --- a/controller.py +++ b/controller.py @@ -1,5 +1,5 @@ +from PyQt5.QtWidgets import QMessageBox, QMainWindow from PyQt5.QtCore import pyqtSlot, QObject -from PyQt5.QtWidgets import QMessageBox from tkinter import filedialog import webbrowser import json @@ -14,6 +14,8 @@ def __init__(self, accounts_page, version, signals): self.VERSION = version self.configs:dict = json.loads((open(file="state.json", mode="r", encoding="utf8").read())) self.connected_numbers = {} + self.dashboard_window:QMainWindow = None + self.notifications = [] # bot de números virtuais no telegram @@ -71,12 +73,13 @@ def select_file(self): if not file_path: return json.dumps({"ok":False, "message": "nenhum arquivo selecionado", "filename": self.messages_base["filename"]}) file = open(mode="r", encoding="utf8", file=file_path) + if not file.read(): file.close() return json.dumps({"ok":False, "message": "o arquivo selecionado está vazio.", "filename": self.messages_base["filename"]}) - + file.seek(0) self.messages_base["filename"]= file.name.split("/")[len(file.name.split("/")) - 1] - self.messages_base["content"]= file.read() + self.messages_base["content"]= file.readlines() file.close() return json.dumps({"ok":True, "message": "alterações foram salvas com êxito", "filename": self.messages_base["filename"]}) @@ -85,7 +88,7 @@ def select_file(self): @pyqtSlot() def view_project_version(self): QMessageBox.about( - self.accounts_page_instance, + self.dashboard_window, "Maturador de Chips", f"você está usando a versão {self.VERSION}, verifique a qualquer momento no github se há atualizações disponíveis." ) @@ -95,7 +98,7 @@ def view_project_version(self): @pyqtSlot() def disparador(self): QMessageBox.about( - self.accounts_page_instance, + self.dashboard_window, "Maturador de Chips", "este recurso estará disponível na proxima atualização!" ) @@ -150,4 +153,21 @@ def start_maturation(self): @pyqtSlot(result=str) def stop_maturation(self): - self.signals.stop_maturation.emit() \ No newline at end of file + self.signals.stop_maturation.emit() + + # referencia: https://pt.stackoverflow.com/questions/254506/o-que-%C3%A9-long-polling + + @pyqtSlot(result=str) + def long_polling(self) -> str|None: + result = self.notifications.copy() + self.notifications = [] + return json.dumps(result) + + # exibir o QmessageBox + + def message_box(self, message, title) -> None: + QMessageBox.about( + self.dashboard_window, + title, + message + ) \ No newline at end of file diff --git a/main.py b/main.py index f60ae08..a91b1a1 100644 --- a/main.py +++ b/main.py @@ -24,19 +24,20 @@ class SignalReceive(QtCore.QObject): stop_maturation = QtCore.pyqtSignal() # iniciar maturação start_maturation = QtCore.pyqtSignal(dict, dict) - + # exibir um Qmessagebox + message_box = QtCore.pyqtSignal(str, str) # iniciar maturação wapp:WhatsApp = None def start_maturation(window, messages_file, phones,signals): - global wapp - wapp = WhatsApp(window=window, messages_file=messages_file, phones=phones,signals=signals, webviews=accounts_page.webviews) + global wapp, controller_instance + wapp = WhatsApp(window=window, messages_file=messages_file, phones=phones,signals=signals, webviews=accounts_page.webviews, controller=controller_instance) wapp.start() # iniciar a aplicação -VERSION = "27.09.2023" +VERSION = "16.12.2023" if __name__ == "__main__": @@ -47,6 +48,7 @@ def start_maturation(window, messages_file, phones,signals): controller_instance = Controller(accounts_page, VERSION, signals) window = dashboard.MainWindow(accounts_page, signals, app, controller_instance) accounts_page.controller = controller_instance + controller_instance.dashboard_window = window # conectar os sinais de pyqtsignal @@ -54,6 +56,7 @@ def start_maturation(window, messages_file, phones,signals): signals.start_maturation.connect(lambda messages_file, phones: start_maturation(window=window, messages_file=messages_file, phones=phones,signals=signals)) signals.stop_maturation.connect(lambda: threading.Thread(target=wapp.stop() if wapp else None, daemon=True).start()) signals.account_blocked.connect(lambda account_data: ( controller_instance.account_blocked(account_data) == wapp.set_account_block(phone=account_data["phone"]) )) + signals.message_box.connect(lambda title, message: controller_instance.message_box(message=message, title=title)) # iniciar o programa na interface dashboard diff --git a/pages/assets/js/updates.js b/pages/assets/js/updates.js index 7a96727..b49c2e0 100644 --- a/pages/assets/js/updates.js +++ b/pages/assets/js/updates.js @@ -1,23 +1,15 @@ - // se conecta com o servidor web socket +// atualizar o status da aquecimento - var socket; - function ConnectWebSocketServer() { - socket = new WebSocket('ws://localhost:5026'); - socket.onopen = function (event) { - $.notify("conexão websocket estabelecida", "success"); - }; +setInterval(() => { + const notifications = controller.long_polling().then(notifications => { + notifications = JSON.parse(notifications); + notifications.forEach( notification => { + + var SendOf = notification.enviadoDe; + var ReceivedBy = notification.recebidoPor; + var message = notification.mensagem; + var hours = notification.horario; //new Date().toLocaleDateString(); - // nova atualização recebida do servidor - - socket.onmessage = function (event) { - - var data = JSON.parse(event.data); - var SendOf = data.enviadoDe; - var ReceivedBy = data.recebidoPor; - var message = data.mensagem; - var hours = data.horario; //new Date().toLocaleDateString(); - - // criar uma nova linha na tabela de log var newRow = $(""); var cols = ""; @@ -32,42 +24,13 @@ var logTableContainer = document.getElementsByClassName("log-table-container")[0]; logTableContainer.scrollTop = logTableContainer.scrollHeight; - }; - - // por algum motivo a conexão websocket não foi realizada (o motivo principal é que ele ainda esta iniciando) - - socket.onerror = function (error) { - //$.notify("erro ao me conectar no servidor websocket", "error"); - ConnectWebSocketServer() - }; - - // conexão com o servidor foi fechada - - socket.onclose = function () { - $.notify("a conexão com o servidor websocket foi fechada, agora não é mais possível atualizar o log.", "error"); - }; - } - - // chama a função que inicia o servidor websocket, além de liberar o botão parar maturação - - function StartLog() { - ConnectWebSocketServer(); - $("#stopButton").prop("disabled", false); - } - - // parar maturação - - function StopMaturation() { - $("#stopButton").prop("disabled", true); - if (socket) { - socket.close(); - } - let xhr = new XMLHttpRequest() - xhr.open("GET", "/api/stop-maturation", true) - xhr.send() + })}); 1 * 1000 +}) - } +// parar o aquecimento - $("#stopButton").click(StopMaturation); - $(document).ready(StartLog); +document.querySelector(".btn-danger").addEventListener("click", (el)=> { + el.target.disabled = true; + controller.stop_maturation(); +}) \ No newline at end of file diff --git a/pages/updates.html b/pages/updates.html index c01ac54..2c71063 100644 --- a/pages/updates.html +++ b/pages/updates.html @@ -110,7 +110,12 @@ - + + diff --git a/scripts/send_message.js b/scripts/send_message.js index 3db25cd..4d56d3e 100644 --- a/scripts/send_message.js +++ b/scripts/send_message.js @@ -9,4 +9,6 @@ setTimeout(() => { let SendButton = document.querySelector('[data-icon="send"]') SendButton.click(); -}, 500); \ No newline at end of file +}, 500); + +// eu ia mexer nesse script de envio para a mensagem ser enviada direto, mas desisti \ No newline at end of file diff --git a/state.json b/state.json index b3313f4..8a3091b 100644 --- a/state.json +++ b/state.json @@ -1,8 +1,8 @@ { - "ContinueOnBlock": true, + "ContinueOnBlock": false, "MinimumMessageInterval": "67", "MaximumMessageInterval": "90", "ChangeAccountEveryMessages": "5", - "StopAfterMessages": "2", + "StopAfterMessages": "10", "ShutdownAfterCompletion": false } \ No newline at end of file diff --git a/websocket_server.exe b/websocket_server.exe deleted file mode 100644 index 96d6fe4..0000000 Binary files a/websocket_server.exe and /dev/null differ diff --git a/websocket_server.py b/websocket_server.py deleted file mode 100644 index 2b5bdc4..0000000 --- a/websocket_server.py +++ /dev/null @@ -1,53 +0,0 @@ -import websockets -import asyncio - -class WebSocketServer: - def __init__(self): - self.connections = set() - self.server = None - self.stop_requested = False - - async def handle_message(self, message): - if message == "stop_server=True": - self.stop_requested = True - return - - for connection in self.connections: - try: - await connection.send(message) - except: - pass - - async def receive_message(self, websocket): - async for message in websocket: - await self.handle_message(message) - - if self.stop_requested: - break - - async def receiver_message(self, websocket, path): - self.connections.add(websocket) - - await self.receive_message(websocket) - self.connections.remove(websocket) - - async def start_server(self): - self.server = await websockets.serve(self.receiver_message, 'localhost', 5026) - await self.server.wait_closed() - - def stop(self): - if self.server: - self.connections.clear() - self.server.close() - self.server = None - - def run(self): - asyncio.run(self.start_server()) - -e = WebSocketServer() -e.run() - -while True: - if e.stop_requested: - e.stop() - break \ No newline at end of file diff --git a/whatsapp.py b/whatsapp.py index 9e3a942..ddabeac 100644 --- a/whatsapp.py +++ b/whatsapp.py @@ -1,207 +1,167 @@ from PyQt5.QtWebEngineWidgets import QWebEngineView +from PyQt5.QtWidgets import QMessageBox from PyQt5.QtCore import QUrl, QThread +from controller import Controller from dashboard import MainWindow -from tkinter import messagebox -import websockets -import subprocess import datetime -import asyncio import random -import time import json +import time import os class WhatsApp(QThread): - def __init__(self, window:MainWindow, messages_file:dict, phones:dict, signals, webviews:list) -> None: + def __init__(self, window:MainWindow, messages_file:dict, phones:dict, signals, webviews:list, controller:Controller) -> None: super().__init__() self.messages = [i.rstrip("\r").rstrip("\n") for i in messages_file["content"]] - self.phones = list(phones.values()) - self.phones_len = [] - self.phones_dict = phones - self.webviews = webviews + self.configs = json.load(open(file="state.json", mode="r", encoding="utf8")) + self.OPEN_CHAT_SCRIPT = open(file="scripts/open_chat.js", mode="r", encoding="utf8").read() + self.CHECK_BLOCK_SCRIPT = open(file="scripts/check_block.js", mode="r", encoding="utf8").read() + self.SEND_MESSAGE_SCRIPT = open(file="scripts/send_message.js", mode="r", encoding="utf8").read() + self.CLOSE_CHAT_SCRIPT = open(file="scripts/close_chat.js", mode="r", encoding="utf8").read() + self.have_a_blocked_account = False + self.controller = controller + self.phones = list(set(phones.values())) self.window = window + self.webviews = webviews self.signals = signals - self.rundding = False - self.configs = self.load_configs() - - # variáveis que ajuda a gerenciar a maturação + self.phones_sender_control = [] + self.phones_receiver_control = self.phones.copy() + self.current_sending_account = None + self.current_receiver_account = None self.messages_send_count = 0 - self.last_number_sender = None - self.last_message = None - self.account_sender:QWebEngineView = None - self.block = False + self.sending = tuple() def start(self) -> None: + + if len(self.phones) < 2: + return QMessageBox.about( + self.window, + "Maturador de Chips", + "número de contas insuficiente para iniciar a maturação." + ) + + elif not self.messages: + return QMessageBox.about( + self.window, + "Maturador de Chips", + "o arquivo de mensagens base não foi escolhido ou está vazio." + ) - if len(self.phones) < 2: - messagebox.showerror(title="Maturador de chips", message="número de contas insuficiente para iniciar a maturação. duas contas ou mais são necessárias.") - return - - if not self.messages: - messagebox.showerror(title="Maturador de chips", message="você não escolheu nenhum arquivo de mensagens ou o escolhido está vazio, por favor corrija isso.") - return - - # fechar a pagina de contas - self.signals.close_accounts_page.emit() - # transfere o webview principal para a tela de logs e configura a tela - - self.start_websocket_server() - self.window.webview.load(QUrl("http://127.0.0.1:5025/maturation-updates")) - self.window.setWindowTitle("Maturador de chips - maturação em andamento ") self.window.setFixedSize(749, 560) + self.window.webview.load(QUrl.fromLocalFile("/pages/updates.html")) + self.window.setWindowTitle("Maturador de chips - maturação em andamento ") super().start() - # maturar os chips em uma theard separada para não travar a interface - - def run(self): - for i in range(0 , int(self.configs["stop_after_messages"])): - - # mudar a conta que envia mensagem - - if self.messages_send_count == int(self.configs["change_account_after_messages"]) or not self.account_sender : - new_sender = random.randint(0, (len(self.phones) - 1)) - while new_sender == self.last_number_sender: - new_sender = random.randint(0, (len(self.phones) -1) ) - - self.last_number_sender = new_sender - self.account_sender = self.webviews[new_sender] - self.messages_send_count = 0 # reset na variável - - # todos números foram usados, resetar a lista - - if not self.phones_len or len(self.phones_len) == 1 and self.phones_len[0] == new_sender: - self.phones_len = [i for i in range(0, len(self.phones) -1)] + # finalizar a thread de maturação + + def stop(self) -> None: + super().terminate() + + # aquecendo os chips em uma segunda thread para não travar a interface gráfica - # escolher a conta que vai receber mensagem + def run(self) -> None: + for execution_count in range(0, int(self.configs["StopAfterMessages"])): + self.messages_send_count += 1 - receive_phone_index = random.randint(0, len(self.phones_len) -1 ) + if len( self.phones_receiver_control) <= 2: + self.phones_receiver_control = self.phones.copy() + self.phones_sender_control = [] - while receive_phone_index == self.last_number_sender: - receive_phone_index = random.randint(0, len(self.phones_len) -1 ) - receive_phone_index = self.phones_len.pop(receive_phone_index) + # escolher a conta que envia mensagem + + if not self.current_sending_account or int(self.configs["ChangeAccountEveryMessages"]) == self.messages_send_count: + current_sending_account = random.choice(self.phones_receiver_control) + while current_sending_account in self.phones_sender_control : + current_sending_account = random.choice(self.phones_receiver_control) + self.phones_sender_control.append(current_sending_account) + self.current_sending_account = current_sending_account + self.messages_send_count = 0 - # escolher a mensagem que vai ser enviada - - message = self.messages[random.randint(0, (len(self.messages) -1) )] - receive_phone_number = self.phones[receive_phone_index] - sender_phone_number = self.phones[self.last_number_sender] - - account_is_blocked = self.check_block(sender_phone_number) + # escolher a conta que vai receber + + current_receiver_account = random.choice(self.phones_receiver_control) + while current_receiver_account == self.current_sending_account: + current_receiver_account = random.choice(self.phones_receiver_control) + self.current_receiver_account = current_receiver_account + self.phones_receiver_control.remove(current_receiver_account) + self.sending = ( self.current_sending_account, self.current_receiver_account) + + # checando bloqueio e enviando mensagem + + account_sender_webview:QWebEngineView = self.webviews[ self.phones.index(self.sending[0] ) ] + message = random.choice(self.messages) + account_is_blocked = self.check_block(self.sending[0], account_webview=account_sender_webview) if account_is_blocked == -1: continue elif account_is_blocked: return - # tudo certo enviar a mensagem e aguardar o intervalo escolhido pelo usuário - self.open_chat(phone=receive_phone_number, message=message) - #self.send_message(message=message) - self.send_message() - self.close_chat() - try: asyncio.run(self.send_websocket_message(sender=sender_phone_number, receiver=receive_phone_number, message=message)) - except: pass - self.messages_send_count += 1 - interval = random.randint(int(self.configs["min_message_interval"]), int(self.configs["max_message_interval"])) - time.sleep(interval) - + account_sender_webview.page().runJavaScript(self.OPEN_CHAT_SCRIPT) + time.sleep(2) + account_sender_webview.page().runJavaScript(self.SEND_MESSAGE_SCRIPT.replace("@PHONE", self.sending[1] ).replace("@MESSAGE", message)) + time.sleep(1) + account_sender_webview.page().runJavaScript(self.CLOSE_CHAT_SCRIPT) + time.sleep(1) + + self.controller.notifications.append({ + "enviadoDe": self.sending[0], + "recebidoPor": self.sending[1], + "mensagem": message, + "horario": datetime.datetime.now().strftime("%H:%M:%S") + }) + + # aguardando o fim do intervalo definido pelo usuário + + time.sleep( random.randint( int(self.configs["MinimumMessageInterval"]) , int(self.configs["MaximumMessageInterval"]) )) + # maturação concluída - if self.configs["shutdown_computer"] == "True": + + if self.configs["ShutdownAfterCompletion"]: os.system("shutdown /s /t 30") - messagebox.showinfo(title="Maturador de chips", message="maturação concluída com sucesso!") + self.signals.message_box.emit( + "Maturador de chips", + "maturação concluída com sucesso!") self.signals.stop_maturation.emit() - - # parar maturação e voltar pro dashboard - + # parar maturação + def stop(self): - self.rundding = False super().terminate() - #self.stop_websocket_server() - #self.websocket_server.terminate() - try: - subprocess.check_call(['taskkill', '/F', '/IM', "websocket_server.exe" ], creationflags=subprocess.CREATE_NO_WINDOW) - except: - pass - self.window.webview.load(QUrl("http://127.0.0.1:5025/dashboard?t=0")) - self.window.setWindowTitle("Maturador de chips - Dashboard") self.window.setFixedSize(767, 620) - - # enviar mensagem de log para o websocket - - async def send_websocket_message(self, sender:str, receiver:str, message:str): - async with websockets.connect('ws://localhost:5026') as websocket: - data = { - "enviadoDe": sender, - "recebidoPor": receiver, - "mensagem":message, - "horario": datetime.datetime.now().strftime("%H:%M:%S") - } - mensagem_json = json.dumps(data) - await websocket.send(mensagem_json) - - # iniciar servidor websocket - - def start_websocket_server(self): - subprocess.Popen(executable="websocket_server.exe", args=[]) - - # carrega as configurações do usuário - - def load_configs(self) -> dict: - with open(file="state.json", mode="r", encoding="utf-8") as f: - return json.load(fp=f) + self.window.webview.load(QUrl.fromLocalFile("/pages/dashboard.html")) + self.window.setWindowTitle("Maturador de chips - Dashboard") - # setar que uma conta foi bloqueada - + # definir que uma conta foi bloqueada + def set_account_block(self, phone:str): - self.block = True - + self.have_a_blocked_account = True + # checar se a conta que esta enviando mensagem foi bloqueada os desconectada - def check_block(self, phone:str) -> bool|int: - with open(file="scripts/check_block.js", mode="r", encoding="utf8") as f: - script = f.read() - self.account_sender.page().runJavaScript(script.replace("@PHONE", phone)) - time.sleep(3) # acredito que seja tempo suficiente para o script rodar e enviar o sinal para a API + def check_block(self, phone:str, account_webview:QWebEngineView) -> bool|int: + + account_webview.page().runJavaScript(self.CHECK_BLOCK_SCRIPT.replace("@PHONE", phone)) + time.sleep(3) - if self.block and self.configs["continue_with_block"] == "False": - messagebox.showerror(title="Maturador de chips" ,message=f"o número {phone} foi desconectado ou banido. parando maturação!") + if self.have_a_blocked_account and not self.configs["ContinueOnBlock"]: + self.signals.message_box.emit( + "Maturador de Chips", + f"o número {phone} foi desconectado ou banido. parando maturação!") self.signals.stop_maturation.emit() return True - elif self.block and len(self.phones) == 2: - messagebox.showerror(title="Maturador de chips", message=f"o número {phone} foi desconectado ou banido e agora o número de contas conectadas é insuficiente para continuar. parando maturação!") + elif self.have_a_blocked_account and len(self.phones) == 2: + self.signals.message_box.emit( + "Maturador de Chips", + f"o número {phone} foi desconectado ou banido e agora o número de contas conectadas é insuficiente para continuar. parando maturação!") self.signals.stop_maturation.emit() return True - elif self.block: - self.account_sender.page().runJavaScript(f""" $.notify("o numero {phone} foi desconectado", "error"); """) - self.phones.pop(self.last_number_sender) - self.block = False - self.account_sender = None - self.last_number_sender = None + elif self.have_a_blocked_account: + self.phones.remove(phone) + self.have_a_blocked_account = False return -1 - return False - - # executar o script que abre o chat da conversa - - def open_chat(self, phone:str, message): - with open(file="scripts/open_chat.js", mode="r", encoding="utf8") as script: - self.account_sender.page().runJavaScript(script.read().replace("@PHONE", phone ).replace("@MESSAGE", message)) - time.sleep(2) - - # enviar mensagem para o numero escolhido - - def send_message(self): - with open(file="scripts/send_message.js", mode="r", encoding="utf8") as script: - #self.account_sender.page().runJavaScript(script.read().replace("@MESSAGE", message )) - self.account_sender.page().runJavaScript(script.read()) - time.sleep(1) - - # fechar o chat com o contato atual - - def close_chat(self): - with open(file="scripts/close_chat.js", mode="r", encoding="utf8") as script: - self.account_sender.page().runJavaScript(script.read()) - time.sleep(1) + return False \ No newline at end of file