├── config_path.txt ├── requirements.txt ├── __MODELS__ ├── config_path.txt ├── reasons.json └── config.json ├── .idea ├── vcs.xml ├── inspectionProfiles │ └── profiles_settings.xml ├── misc.xml └── modules.xml ├── .gitignore ├── teste.py ├── README.md ├── preparation.py ├── manager.py ├── sqlscript.sql ├── tools.py ├── LICENSE.md └── main.py /config_path.txt: -------------------------------------------------------------------------------- 1 | /srv/lucasbot/Settings/ 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | praw 2 | prawcore 3 | psutil 4 | mysql-connector-python 5 | -------------------------------------------------------------------------------- /__MODELS__/config_path.txt: -------------------------------------------------------------------------------- 1 | /home/breno/Disk2/Settings 2 | 3 | ESSE ARQUIVO É ONDE VOCÊ COLOCA A PASTA ONDE ESTÁ OS ARQUIVOS "config.json", "reasons.json". COLOQUE O CAMINHO NA PRIMEIRA LINHA!! -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | api.json 2 | data 3 | bodies 4 | log 5 | idlist 6 | runtime_info 7 | rid 8 | mercy 9 | praw 10 | praw-master.zip 11 | aid 12 | jid 13 | reasoning 14 | ignore_list 15 | pyenv 16 | cid 17 | __pycache__/ 18 | __venv__ 19 | .idea 20 | __pycache__ 21 | teste 22 | __venv__/ 23 | venv 24 | .gemini 25 | .pyen 26 | cpvenv 27 | .vs 28 | .vscode 29 | -------------------------------------------------------------------------------- /__MODELS__/reasons.json: -------------------------------------------------------------------------------- 1 | { 2 | "FAKE_OT": { 3 | "note": "NOTA REMOÇÃO PARA POST FORA DO SUB", 4 | "body": "TEXTO DO COMENTÁRIO" 5 | }, 6 | "TEXTWALL": { 7 | "note": "NOTA REMOÇÃO PARA PAREDE DE TEXTO", 8 | "body": "CORPO DO TEXTO" 9 | }, 10 | "NO_REASON": { 11 | "note": "Não justificou o post", 12 | "body": "Sua publicação foi removida pois você não justificou o post." 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /teste.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | 4 | reddit_data = json.load(open("/media/breno/Srv/lucasbot/submissions.json")) 5 | reddit_data["submissions"].append({"sdffvsf": "a"}) 6 | 7 | # Limpar para remover duplicados 8 | list_submissions = reddit_data["submissions"].copy() 9 | already_in = [] 10 | 11 | indx = -1 12 | for subm in reddit_data["submissions"]: 13 | indx += 1 14 | 15 | for key in subm.keys(): 16 | if key not in already_in: 17 | already_in.append(key) 18 | break 19 | else: 20 | # tools.logger(tp=2, ex=f"{key} é duplicado, por isso foi removido.") 21 | del list_submissions[indx] 22 | indx -= 1 23 | break 24 | 25 | reddit_data["submissions"] = list_submissions 26 | print(reddit_data) 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Lucasbot 3 | 4 | Lucasbot é um bot feito para o subreddit r/EuSouOBabaca utilizando `python` e a biblioteca `praw`. Ele também usa um servidor `mysql` para algumas tarefas como geração de estatísticas e algumas configurações básicas. 5 | 6 | ## Requisitos mínimos 7 | |Requisito|Valor | 8 | |--|--| 9 | | Python |3.9 | 10 | | Bibliotecas|`praw, psutil, mysql-connector-python` | 11 | | RAM|768 mb | 12 | | CPU|1 núcleo | 13 | | Disco|1 gb livre | 14 | 15 | ## Como rodar 16 | Depois de instalar as bibliotecas necessárias usando `python3 -m pip install -r requirements.txt`, basta configurar o bot usando o modelo em MODELOS e depois `python3 main.py -p "Senha do banco de dados"`. 17 | 18 | ![Terminal com o bot rodando](https://i.imgur.com/uyYvogh.png) 19 | ## Comandos 20 | 21 | |Comando|Output | 22 | |--|--| 23 | | R | Recarrega valores na memória | 24 | | RESTART | Reinicia o programa | 25 | | E| Termina o programa | 26 | | LEAVE| Sai do input de comandos. Pode ser útil para manter a segurança. | 27 | | MEMORY| Calcula a memória e o CPU usado em cada processo. ENTER para atualizar, digite qualquer coisa e aperte ENTER para sair do loop. | 28 | | LOGSTREAM | Mostra o registro do log em tempo real | 29 | | ADDSPLASH | Adiciona um texto debaixo do indicador de veredito | 30 | | INJECT SQL ou PYTHON | Se injectable em config.json for True, permite executar códigos sql ou python no programa | 31 | | SWITCH Chave em 'debug' | Altera temporariamente o valor presente no dicionário 'debug' em config.json | 32 | | LICENSE | Texto de licença | 33 | | ABOUTME | About me | 34 | -------------------------------------------------------------------------------- /preparation.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Stores initialization functions 3 | Copyright (C) 2024 Breno Martins 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | ''' 18 | 19 | import os 20 | 21 | 22 | def begin(config: dict) -> None: 23 | ''' 24 | Prepara os arquivos que serão usados pelo script 25 | :return: None 26 | ''' 27 | # Função de preparação 28 | 29 | #./bodies 30 | 31 | try: 32 | if not os.path.exists(f"{config['list_path']}/reasoning/reasonings.json"): 33 | open(f"{config['list_path']}/reasoning/reasonings.json", "w+").write("{}") 34 | 35 | # arquivo de log e id 36 | emptytxts = ["idlist", "log", "rid", "aid", "aarid", "jid", "cid", "keywords.txt", "pids"] 37 | for i in emptytxts: 38 | if not os.path.exists(f"{config['list_path']}/{i}"): 39 | open(f"{config['list_path']}/{i}", "w+") 40 | 41 | # Pastas vazias 42 | folders = ["runtime_info"] 43 | for i in folders: 44 | if not os.path.exists(f"{config['list_path']}/{i}"): 45 | os.mkdir(f"{config['list_path']}/{i}") 46 | except PermissionError: 47 | print("O diretóŕio em list_path é inacessível para o usuário executando o script. Edite o arquivo config.json ou conceda as devidas permissões.") 48 | exit(-1) 49 | except FileNotFoundError: 50 | print(f"O diretório especificado {config['list_path']} não existe. Edite o arquivo config.json ou crie-o.") 51 | exit(-1) 52 | -------------------------------------------------------------------------------- /manager.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This file is a standalone manager for the bot 3 | Copyright (C) 2024 Breno Martins 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | ''' 18 | 19 | import psutil 20 | import os 21 | import sys 22 | import json 23 | import tools 24 | import time 25 | 26 | def main(args: list, config: dict) -> int: 27 | # Se o número de argumentos for menor que 2, vai dar erro 28 | if len(args) <= 1: 29 | return -1 30 | 31 | # Se o argumento for de memória, vai fazer o calculo da memória 32 | if args[1] in ["memory", "m", "-m", "--memory"]: 33 | pids = tools.getfiletext(open(f'{config["list_path"]}/pids', "r")) 34 | 35 | pids = [int(x) for x in pids] 36 | 37 | pids.insert(0, os.getpid()) 38 | while True: 39 | time.sleep(0.5) 40 | 41 | tools.clear_console() 42 | mem = 0 43 | perc = 0 44 | index = 0 45 | cputotal = 0 46 | r = 0 47 | all_processes = psutil.process_iter() 48 | 49 | for process in all_processes: 50 | if process.pid in pids: 51 | perc += process.memory_percent() 52 | memory_info = process.memory_info() 53 | mem_qnt = memory_info.rss / 1024 / 1024 54 | mem += mem_qnt 55 | cpu = process.cpu_percent() 56 | cputotal += cpu 57 | 58 | print(f"{process.pid}: {mem_qnt:.0f} mb, {cpu:.2f}% CPU") 59 | 60 | if r > 0: 61 | index += 1 62 | 63 | r += 1 64 | 65 | print(f"Total: {mem:.0f} mb ({perc:.2f}%), {cputotal:.2f}% CPU") 66 | 67 | # Reiniciar/matar o processo principal 68 | else: 69 | return -1 70 | 71 | 72 | if __name__ == '__main__': 73 | config = json.load(open(f"{open('./config_path.txt').readlines()[0]}/config.json", "r")) 74 | code = main(sys.argv, config) 75 | 76 | if code == -1: # -1 é quando não tem argumentos 77 | print(f"Nenhum argumento válido! ", end="") 78 | 79 | print(f"Código: {code}") 80 | 81 | -------------------------------------------------------------------------------- /__MODELS__/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "python": "/home/breno/Codes/lucasbot/pyenv/bin/python3", 3 | "db": { 4 | "host": "IP DO BANCO DE DADOS SEM A PORTA", 5 | "user": "USUARIO", 6 | "database": "BANCO DE DADOS", 7 | "api_id": 1 8 | }, 9 | "info": { 10 | "name": "NOME DO BOT", 11 | "character": "PERSONAGEM DO BOT", 12 | "version": "VERSÃO DO BOT", 13 | "creator": "CRIADOR (JakeWisconsin)", 14 | "author": "AUTOR DO CÓDIGO", 15 | "github": "REPOSITORIO DO GITHUB" 16 | }, 17 | "debug": { 18 | "log_verbose": false, 19 | "injectable": false 20 | }, 21 | "config": "CAMINHO DA PASTA ONDE TEM OS ARQUIVOS DE CONFIGURAÇÃO", 22 | "upper_text": "TEXTO QUE FICA DEBAIXO DOS RESULTADOS DA VOTAÇÃO", 23 | "submissions": "NUMERO DE POSTS REQUISITADOS NA API DO praw", 24 | "cutting_chars": "NUMERO DE CARACTERES ANTES DE CORTAR POR SER MUITO GRANDE", 25 | "subreddit": "SUBREDDIT", 26 | "log_subreddit": "SUBREDDIT DE LOG", 27 | "min_before_lock": 10, 28 | "backup": { 29 | "path": "PASTA ONDE FICA OS BACKUPS", 30 | "time": ["HH:MM"], 31 | "max_days": 3 32 | }, 33 | "stat": { 34 | "csv_path": "ONDE FICA SALVO O ARQUIVO CSV (NAO UTILIZADO)" 35 | }, 36 | "list_path": "ONDE FICA OS ARQUIVOS DE TEXTO USADOS PELO BOT", 37 | "clear_log": 86400, 38 | "break_time": 604800, 39 | "replace_list": ["!", "?", ".", ",", ":", "(", ")", "[", "]", "{", "}", "-", "+", "/", "\\", "'", "\u0022", "~", "\n", "\n\n"], 40 | "sleep_time": { 41 | "main": 0.1, 42 | "textwall": 1, 43 | "justification": 2, 44 | "filter_sub": 0, 45 | "lock_com": 0, 46 | "filter_com": 0.25, 47 | "backup": 60, 48 | "stat": 3600 49 | }, 50 | "exdigit": [11, 0, 0, 41, 15, 38, 35], 51 | "flairs": { 52 | "VOTO_COMUM": ["ID DA FLAIR DO POST", 0, "DESCRIÇÃO DO VOTO QUE VAI SER USADO PARA CONTAR O JULGAMENTO"], 53 | "VOTO_COMUM_BABACA": ["ID DA FLAIR DO POST", 0, "DESCRIÇÃO DO VOTO QUE VAI SER USADO PARA CONTAR O JULGAMENTO"], 54 | "VOTO_REMOVEDOR": ["ID DA FLAIR DO POST", 1, "DESCRIÇÃO"], 55 | "INCONCLUSIVE": ["ID DA FLAIR ESPECIAL PARA SEM MAIORIA", 2, ""], 56 | "NOT_AVALIABLE": ["ID DA FLAIR ESPECIAL PARA NÃO DISPONÍVEL", 2, ""], 57 | "NOT_CLASSIFIED": ["ID DA FLAIR ESPECIAL PARA NÃO CLASSIFICADO", 2, ""] 58 | }, 59 | "flairs_ignore": ["INCONCLUSIVE", "NOT_AVALIABLE", "NOT_CLASSIFIED"], 60 | "asshole": ["VOTO_COMUM_BABACA"], 61 | "not_asshole": ["VOTO_COMUM"], 62 | "vote_name": ["NOME VOTO 1", "NOME VOTO 2", "NOME VOTO 3"], 63 | "text_filter": { 64 | "min_paragraphs": 1, 65 | "min_sentences": 2, 66 | "max_body": 9000, 67 | "min_body": 200 68 | }, 69 | "karma_filter": { 70 | "enabled": false, 71 | "min": 2, 72 | "wait": 3600 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /sqlscript.sql: -------------------------------------------------------------------------------- 1 | -- -------------------------------------------------------- 2 | -- Servidor: 192.168.1.21 3 | -- Versão do servidor: 10.11.6-MariaDB-0+deb12u1 - Debian 12 4 | -- OS do Servidor: debian-linux-gnu 5 | -- HeidiSQL Versão: 12.8.0.6908 6 | -- -------------------------------------------------------- 7 | 8 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; 9 | /*!40101 SET NAMES utf8 */; 10 | /*!50503 SET NAMES utf8mb4 */; 11 | /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; 12 | /*!40103 SET TIME_ZONE='+00:00' */; 13 | /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; 14 | /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; 15 | /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; 16 | 17 | 18 | -- Copiando estrutura do banco de dados para lucasbot 19 | CREATE DATABASE IF NOT EXISTS `lucasbot` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci */; 20 | USE `lucasbot`; 21 | 22 | -- Copiando estrutura para tabela lucasbot.splashes 23 | CREATE TABLE IF NOT EXISTS `splashes` ( 24 | `id` int(10) unsigned NOT NULL, 25 | `owner` int(10) unsigned NOT NULL, 26 | `text` varchar(128) NOT NULL DEFAULT '', 27 | PRIMARY KEY (`id`), 28 | UNIQUE KEY `id` (`id`), 29 | KEY `owner` (`owner`), 30 | CONSTRAINT `owner` FOREIGN KEY (`owner`) REFERENCES `users` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION 31 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; 32 | 33 | -- Exportação de dados foi desmarcado. 34 | 35 | -- Copiando estrutura para tabela lucasbot.statistics 36 | CREATE TABLE IF NOT EXISTS `statistics` ( 37 | `id` int(10) unsigned NOT NULL, 38 | `owner` int(10) unsigned NOT NULL, 39 | `datetime` datetime NOT NULL, 40 | `members` int(255) unsigned NOT NULL, 41 | `growt` int(255) NOT NULL, 42 | `growt_percent` decimal(65,9) NOT NULL, 43 | PRIMARY KEY (`id`), 44 | KEY `stat_owner` (`owner`), 45 | CONSTRAINT `stat_owner` FOREIGN KEY (`owner`) REFERENCES `users` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION 46 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; 47 | 48 | -- Exportação de dados foi desmarcado. 49 | 50 | -- Copiando estrutura para tabela lucasbot.subreddits 51 | CREATE TABLE IF NOT EXISTS `subreddits` ( 52 | `subreddit` varchar(21) NOT NULL DEFAULT '0', 53 | `user` int(10) unsigned NOT NULL DEFAULT 0, 54 | PRIMARY KEY (`subreddit`) USING BTREE, 55 | UNIQUE KEY `subreddit` (`subreddit`), 56 | KEY `user_sub` (`user`), 57 | CONSTRAINT `user_sub` FOREIGN KEY (`user`) REFERENCES `users` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION 58 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='Lista de subs do bot'; 59 | 60 | -- Exportação de dados foi desmarcado. 61 | 62 | -- Copiando estrutura para tabela lucasbot.users 63 | CREATE TABLE IF NOT EXISTS `users` ( 64 | `id` int(10) unsigned NOT NULL, 65 | `username` varchar(20) NOT NULL DEFAULT '', 66 | `client` varchar(50) NOT NULL, 67 | `secret` varchar(50) NOT NULL, 68 | `password` varchar(1000) NOT NULL, 69 | `subreddit` varchar(21) DEFAULT NULL, 70 | PRIMARY KEY (`id`), 71 | UNIQUE KEY `username` (`username`), 72 | UNIQUE KEY `client` (`client`), 73 | UNIQUE KEY `secret` (`secret`), 74 | UNIQUE KEY `id` (`id`), 75 | KEY `subredditfk` (`subreddit`), 76 | CONSTRAINT `subredditfk` FOREIGN KEY (`subreddit`) REFERENCES `subreddits` (`subreddit`) ON DELETE NO ACTION ON UPDATE NO ACTION 77 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; 78 | 79 | -- Exportação de dados foi desmarcado. 80 | 81 | /*!40103 SET TIME_ZONE=IFNULL(@OLD_TIME_ZONE, 'system') */; 82 | /*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */; 83 | /*!40014 SET FOREIGN_KEY_CHECKS=IFNULL(@OLD_FOREIGN_KEY_CHECKS, 1) */; 84 | /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; 85 | /*!40111 SET SQL_NOTES=IFNULL(@OLD_SQL_NOTES, 1) */; 86 | -------------------------------------------------------------------------------- /tools.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This file has some functions used by the main process 3 | Copyright (C) 2024 Breno Martins 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | ''' 18 | import datetime 19 | import io 20 | import json 21 | import os 22 | import time 23 | import mysql.connector 24 | import re 25 | from typing import Dict 26 | 27 | try: 28 | config_path = open("./config_path.txt").readlines()[0].strip() 29 | print(f"Config path lido: {config_path}") 30 | except Exception as e: 31 | print(f"Erro ao ler config_path.txt: {e}") 32 | exit(-1) 33 | 34 | try: 35 | if not os.path.exists(f"{config_path}/config.json"): 36 | print(f"Arquivo config.json não encontrado no caminho {config_path}.") 37 | exit(-1) 38 | config = json.load(open(f"{config_path}/config.json", "r")) 39 | reasons = json.load(open(f"{config_path}/reasons.json", "r")) 40 | boot = True 41 | print("Configurações carregadas com sucesso!") 42 | except Exception as e: 43 | print(f"Erro ao carregar configurações: {e}") 44 | exit(-1) 45 | 46 | REGEXES: Dict[str, str] = {} 47 | 48 | 49 | # Função que escreve no arquivo de log 50 | def logit(msg): 51 | open(f"{config['list_path']}/log", "a").write(msg + "\n") 52 | 53 | 54 | # Função principal de logging 55 | def logger(tp, sub_id="", ex="", num="", reason="", bprint=False, com_id=""): 56 | current_time = datetime.datetime.now().strftime("%d/%m/%Y às %H:%M:%S") # Pega a hora atual pra por no log 57 | msg = "" 58 | 59 | # Altera a string de mensagem do log a depender do tipo de chamada de função 60 | if tp == 0: 61 | msg = f"Comentário enviado em {sub_id}" 62 | elif tp == 1: 63 | msg = f"Comentário editado em {sub_id}" 64 | elif tp == 2 or tp == 5 or tp == 7: 65 | msg = f"{ex}" 66 | if tp == 5: 67 | if bprint: 68 | print(f"ERRO ({current_time}): {ex}") 69 | 70 | time.sleep(1) 71 | if bprint: 72 | print(ex) 73 | elif tp == 3: 74 | msg = f"Número {num} ({sub_id})" 75 | elif tp == 4: 76 | msg = f"{sub_id} foi removido. MOTIVO: {reason}" 77 | elif tp == 6: 78 | msg = f"Comentário denunciado: {ex} em {sub_id}/{com_id}" 79 | 80 | msg = f"[{current_time}] "+msg 81 | if config["debug"]["log_verbose"]: 82 | print(msg) 83 | 84 | if tp != 7: 85 | logit(msg) 86 | 87 | 88 | def log_runtime(func, a: float, b: float): 89 | # Abrir o arquivo da função para armazenar o tempo de runtime 90 | try: 91 | funct_file = open(f"{config['list_path']}/runtime_info/{func.__name__}", "a") 92 | except FileNotFoundError: 93 | funct_file = open(f"{config['list_path']}/runtime_info/{func.__name__}", "w+") 94 | 95 | difference_runtime = b - a 96 | # O resultado da diferença entre as timestamps em milisegundos ACIMA. 97 | 98 | # colocar o tempo em minutos 99 | funct_file.write(f"[{datetime.datetime.now().strftime('%d/%m/%Y às %H:%M:%S')}] Runtime: {(difference_runtime/60)} minutos. \n") 100 | 101 | 102 | def getfiletext(file: io.TextIOWrapper) -> list: 103 | """ 104 | Gets text from a file and returns each formatted line in a list 105 | :param file: Open file object 106 | :return: List of strings 107 | """ 108 | # Read all lines at once and process them 109 | text = [line.strip().removesuffix('\n') for line in file.readlines()] 110 | return text 111 | 112 | 113 | def clear_console() -> None: 114 | os.system("cls" if os.name=="nt" else "clear") 115 | 116 | 117 | def wait(exdigit: int) -> None: 118 | ''' 119 | Para o programa até parar em um milisegundo terminado em um número específico 120 | :param exdigit: int 121 | :return: None 122 | ''' 123 | if exdigit < 0 or exdigit > 59: 124 | raise ValueError("O dígito de espera deve estar entre 0 e 59.") 125 | elif exdigit == 0: 126 | return None 127 | 128 | while True: 129 | second = int(datetime.datetime.now().second) 130 | if second % exdigit == 0: 131 | logger(tp=7, ex=f"Rodado em {second}!") 132 | break 133 | else: 134 | time.sleep(0.1) # Aguarda 100 milissegundos 135 | 136 | 137 | return None 138 | 139 | 140 | def db_connect(args): 141 | try: 142 | sql = mysql.connector.connect( 143 | host=config["db"]["host"], 144 | user=config["db"]["user"], 145 | password=args.p, 146 | database=config["db"]["database"] 147 | ) 148 | except mysql.connector.ProgrammingError: 149 | print("Permissão negada ao conectar ao banco de dados mysql.") 150 | exit() 151 | except mysql.connector.Error as e: 152 | print(f"Erro: {e}") 153 | exit() 154 | 155 | return sql 156 | 157 | 158 | def load_regexes(): 159 | global REGEXES 160 | if not REGEXES: 161 | with open(f"{config_path}/regexes.txt", "r", encoding="utf-8") as f: 162 | lines = f.readlines() 163 | REGEXES = { 164 | "age": lines[0].strip(), 165 | "phone": lines[2].strip(), 166 | "email": lines[3].strip(), 167 | "cpf": lines[4].strip(), 168 | "url": lines[5].strip() 169 | } 170 | 171 | 172 | def match(regex_type: str, text: str) -> bool: 173 | """ 174 | Match text against predefined regex patterns. 175 | :param regex_type: Type of regex to use ('age', 'gender', or 'phone') 176 | :param text: Text to match against. 177 | :return: Boolean indicating if there's a match. 178 | """ 179 | load_regexes() 180 | 181 | if regex_type not in REGEXES: 182 | logger(tp=2, ex=f"Regex type {regex_type} not found.") 183 | return True 184 | 185 | regx = REGEXES[regex_type] 186 | result = re.search(regx, text, flags=re.M | re.IGNORECASE) 187 | 188 | if regex_type == "phone": 189 | if result: 190 | split_res = result.group().split() 191 | return any(len(i) >= 5 for i in split_res) 192 | return False 193 | 194 | return result is not None 195 | 196 | 197 | def smart_strip(text: str, strip_list: list) -> str: 198 | for i in strip_list: 199 | text = text.replace(i, " ") 200 | 201 | return text 202 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | GNU General Public License 2 | ========================== 3 | 4 | _Version 3, 29 June 2007_ 5 | _Copyright © 2007 Free Software Foundation, Inc. <>_ 6 | 7 | Everyone is permitted to copy and distribute verbatim copies of this license 8 | document, but changing it is not allowed. 9 | 10 | ## Preamble 11 | 12 | The GNU General Public License is a free, copyleft license for software and other 13 | kinds of works. 14 | 15 | The licenses for most software and other practical works are designed to take away 16 | your freedom to share and change the works. By contrast, the GNU General Public 17 | License is intended to guarantee your freedom to share and change all versions of a 18 | program--to make sure it remains free software for all its users. We, the Free 19 | Software Foundation, use the GNU General Public License for most of our software; it 20 | applies also to any other work released this way by its authors. You can apply it to 21 | your programs, too. 22 | 23 | When we speak of free software, we are referring to freedom, not price. Our General 24 | Public Licenses are designed to make sure that you have the freedom to distribute 25 | copies of free software (and charge for them if you wish), that you receive source 26 | code or can get it if you want it, that you can change the software or use pieces of 27 | it in new free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you these rights or 30 | asking you to surrender the rights. Therefore, you have certain responsibilities if 31 | you distribute copies of the software, or if you modify it: responsibilities to 32 | respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether gratis or for a fee, 35 | you must pass on to the recipients the same freedoms that you received. You must make 36 | sure that they, too, receive or can get the source code. And you must show them these 37 | terms so they know their rights. 38 | 39 | Developers that use the GNU GPL protect your rights with two steps: **(1)** assert 40 | copyright on the software, and **(2)** offer you this License giving you legal permission 41 | to copy, distribute and/or modify it. 42 | 43 | For the developers' and authors' protection, the GPL clearly explains that there is 44 | no warranty for this free software. For both users' and authors' sake, the GPL 45 | requires that modified versions be marked as changed, so that their problems will not 46 | be attributed erroneously to authors of previous versions. 47 | 48 | Some devices are designed to deny users access to install or run modified versions of 49 | the software inside them, although the manufacturer can do so. This is fundamentally 50 | incompatible with the aim of protecting users' freedom to change the software. The 51 | systematic pattern of such abuse occurs in the area of products for individuals to 52 | use, which is precisely where it is most unacceptable. Therefore, we have designed 53 | this version of the GPL to prohibit the practice for those products. If such problems 54 | arise substantially in other domains, we stand ready to extend this provision to 55 | those domains in future versions of the GPL, as needed to protect the freedom of 56 | users. 57 | 58 | Finally, every program is threatened constantly by software patents. States should 59 | not allow patents to restrict development and use of software on general-purpose 60 | computers, but in those that do, we wish to avoid the special danger that patents 61 | applied to a free program could make it effectively proprietary. To prevent this, the 62 | GPL assures that patents cannot be used to render the program non-free. 63 | 64 | The precise terms and conditions for copying, distribution and modification follow. 65 | 66 | ## TERMS AND CONDITIONS 67 | 68 | ### 0. Definitions 69 | 70 | “This License” refers to version 3 of the GNU General Public License. 71 | 72 | “Copyright” also means copyright-like laws that apply to other kinds of 73 | works, such as semiconductor masks. 74 | 75 | “The Program” refers to any copyrightable work licensed under this 76 | License. Each licensee is addressed as “you”. “Licensees” and 77 | “recipients” may be individuals or organizations. 78 | 79 | To “modify” a work means to copy from or adapt all or part of the work in 80 | a fashion requiring copyright permission, other than the making of an exact copy. The 81 | resulting work is called a “modified version” of the earlier work or a 82 | work “based on” the earlier work. 83 | 84 | A “covered work” means either the unmodified Program or a work based on 85 | the Program. 86 | 87 | To “propagate” a work means to do anything with it that, without 88 | permission, would make you directly or secondarily liable for infringement under 89 | applicable copyright law, except executing it on a computer or modifying a private 90 | copy. Propagation includes copying, distribution (with or without modification), 91 | making available to the public, and in some countries other activities as well. 92 | 93 | To “convey” a work means any kind of propagation that enables other 94 | parties to make or receive copies. Mere interaction with a user through a computer 95 | network, with no transfer of a copy, is not conveying. 96 | 97 | An interactive user interface displays “Appropriate Legal Notices” to the 98 | extent that it includes a convenient and prominently visible feature that **(1)** 99 | displays an appropriate copyright notice, and **(2)** tells the user that there is no 100 | warranty for the work (except to the extent that warranties are provided), that 101 | licensees may convey the work under this License, and how to view a copy of this 102 | License. If the interface presents a list of user commands or options, such as a 103 | menu, a prominent item in the list meets this criterion. 104 | 105 | ### 1. Source Code 106 | 107 | The “source code” for a work means the preferred form of the work for 108 | making modifications to it. “Object code” means any non-source form of a 109 | work. 110 | 111 | A “Standard Interface” means an interface that either is an official 112 | standard defined by a recognized standards body, or, in the case of interfaces 113 | specified for a particular programming language, one that is widely used among 114 | developers working in that language. 115 | 116 | The “System Libraries” of an executable work include anything, other than 117 | the work as a whole, that **(a)** is included in the normal form of packaging a Major 118 | Component, but which is not part of that Major Component, and **(b)** serves only to 119 | enable use of the work with that Major Component, or to implement a Standard 120 | Interface for which an implementation is available to the public in source code form. 121 | A “Major Component”, in this context, means a major essential component 122 | (kernel, window system, and so on) of the specific operating system (if any) on which 123 | the executable work runs, or a compiler used to produce the work, or an object code 124 | interpreter used to run it. 125 | 126 | The “Corresponding Source” for a work in object code form means all the 127 | source code needed to generate, install, and (for an executable work) run the object 128 | code and to modify the work, including scripts to control those activities. However, 129 | it does not include the work's System Libraries, or general-purpose tools or 130 | generally available free programs which are used unmodified in performing those 131 | activities but which are not part of the work. For example, Corresponding Source 132 | includes interface definition files associated with source files for the work, and 133 | the source code for shared libraries and dynamically linked subprograms that the work 134 | is specifically designed to require, such as by intimate data communication or 135 | control flow between those subprograms and other parts of the work. 136 | 137 | The Corresponding Source need not include anything that users can regenerate 138 | automatically from other parts of the Corresponding Source. 139 | 140 | The Corresponding Source for a work in source code form is that same work. 141 | 142 | ### 2. Basic Permissions 143 | 144 | All rights granted under this License are granted for the term of copyright on the 145 | Program, and are irrevocable provided the stated conditions are met. This License 146 | explicitly affirms your unlimited permission to run the unmodified Program. The 147 | output from running a covered work is covered by this License only if the output, 148 | given its content, constitutes a covered work. This License acknowledges your rights 149 | of fair use or other equivalent, as provided by copyright law. 150 | 151 | You may make, run and propagate covered works that you do not convey, without 152 | conditions so long as your license otherwise remains in force. You may convey covered 153 | works to others for the sole purpose of having them make modifications exclusively 154 | for you, or provide you with facilities for running those works, provided that you 155 | comply with the terms of this License in conveying all material for which you do not 156 | control copyright. Those thus making or running the covered works for you must do so 157 | exclusively on your behalf, under your direction and control, on terms that prohibit 158 | them from making any copies of your copyrighted material outside their relationship 159 | with you. 160 | 161 | Conveying under any other circumstances is permitted solely under the conditions 162 | stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 163 | 164 | ### 3. Protecting Users' Legal Rights From Anti-Circumvention Law 165 | 166 | No covered work shall be deemed part of an effective technological measure under any 167 | applicable law fulfilling obligations under article 11 of the WIPO copyright treaty 168 | adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention 169 | of such measures. 170 | 171 | When you convey a covered work, you waive any legal power to forbid circumvention of 172 | technological measures to the extent such circumvention is effected by exercising 173 | rights under this License with respect to the covered work, and you disclaim any 174 | intention to limit operation or modification of the work as a means of enforcing, 175 | against the work's users, your or third parties' legal rights to forbid circumvention 176 | of technological measures. 177 | 178 | ### 4. Conveying Verbatim Copies 179 | 180 | You may convey verbatim copies of the Program's source code as you receive it, in any 181 | medium, provided that you conspicuously and appropriately publish on each copy an 182 | appropriate copyright notice; keep intact all notices stating that this License and 183 | any non-permissive terms added in accord with section 7 apply to the code; keep 184 | intact all notices of the absence of any warranty; and give all recipients a copy of 185 | this License along with the Program. 186 | 187 | You may charge any price or no price for each copy that you convey, and you may offer 188 | support or warranty protection for a fee. 189 | 190 | ### 5. Conveying Modified Source Versions 191 | 192 | You may convey a work based on the Program, or the modifications to produce it from 193 | the Program, in the form of source code under the terms of section 4, provided that 194 | you also meet all of these conditions: 195 | 196 | * **a)** The work must carry prominent notices stating that you modified it, and giving a 197 | relevant date. 198 | * **b)** The work must carry prominent notices stating that it is released under this 199 | License and any conditions added under section 7. This requirement modifies the 200 | requirement in section 4 to “keep intact all notices”. 201 | * **c)** You must license the entire work, as a whole, under this License to anyone who 202 | comes into possession of a copy. This License will therefore apply, along with any 203 | applicable section 7 additional terms, to the whole of the work, and all its parts, 204 | regardless of how they are packaged. This License gives no permission to license the 205 | work in any other way, but it does not invalidate such permission if you have 206 | separately received it. 207 | * **d)** If the work has interactive user interfaces, each must display Appropriate Legal 208 | Notices; however, if the Program has interactive interfaces that do not display 209 | Appropriate Legal Notices, your work need not make them do so. 210 | 211 | A compilation of a covered work with other separate and independent works, which are 212 | not by their nature extensions of the covered work, and which are not combined with 213 | it such as to form a larger program, in or on a volume of a storage or distribution 214 | medium, is called an “aggregate” if the compilation and its resulting 215 | copyright are not used to limit the access or legal rights of the compilation's users 216 | beyond what the individual works permit. Inclusion of a covered work in an aggregate 217 | does not cause this License to apply to the other parts of the aggregate. 218 | 219 | ### 6. Conveying Non-Source Forms 220 | 221 | You may convey a covered work in object code form under the terms of sections 4 and 222 | 5, provided that you also convey the machine-readable Corresponding Source under the 223 | terms of this License, in one of these ways: 224 | 225 | * **a)** Convey the object code in, or embodied in, a physical product (including a 226 | physical distribution medium), accompanied by the Corresponding Source fixed on a 227 | durable physical medium customarily used for software interchange. 228 | * **b)** Convey the object code in, or embodied in, a physical product (including a 229 | physical distribution medium), accompanied by a written offer, valid for at least 230 | three years and valid for as long as you offer spare parts or customer support for 231 | that product model, to give anyone who possesses the object code either **(1)** a copy of 232 | the Corresponding Source for all the software in the product that is covered by this 233 | License, on a durable physical medium customarily used for software interchange, for 234 | a price no more than your reasonable cost of physically performing this conveying of 235 | source, or **(2)** access to copy the Corresponding Source from a network server at no 236 | charge. 237 | * **c)** Convey individual copies of the object code with a copy of the written offer to 238 | provide the Corresponding Source. This alternative is allowed only occasionally and 239 | noncommercially, and only if you received the object code with such an offer, in 240 | accord with subsection 6b. 241 | * **d)** Convey the object code by offering access from a designated place (gratis or for 242 | a charge), and offer equivalent access to the Corresponding Source in the same way 243 | through the same place at no further charge. You need not require recipients to copy 244 | the Corresponding Source along with the object code. If the place to copy the object 245 | code is a network server, the Corresponding Source may be on a different server 246 | (operated by you or a third party) that supports equivalent copying facilities, 247 | provided you maintain clear directions next to the object code saying where to find 248 | the Corresponding Source. Regardless of what server hosts the Corresponding Source, 249 | you remain obligated to ensure that it is available for as long as needed to satisfy 250 | these requirements. 251 | * **e)** Convey the object code using peer-to-peer transmission, provided you inform 252 | other peers where the object code and Corresponding Source of the work are being 253 | offered to the general public at no charge under subsection 6d. 254 | 255 | A separable portion of the object code, whose source code is excluded from the 256 | Corresponding Source as a System Library, need not be included in conveying the 257 | object code work. 258 | 259 | A “User Product” is either **(1)** a “consumer product”, which 260 | means any tangible personal property which is normally used for personal, family, or 261 | household purposes, or **(2)** anything designed or sold for incorporation into a 262 | dwelling. In determining whether a product is a consumer product, doubtful cases 263 | shall be resolved in favor of coverage. For a particular product received by a 264 | particular user, “normally used” refers to a typical or common use of 265 | that class of product, regardless of the status of the particular user or of the way 266 | in which the particular user actually uses, or expects or is expected to use, the 267 | product. A product is a consumer product regardless of whether the product has 268 | substantial commercial, industrial or non-consumer uses, unless such uses represent 269 | the only significant mode of use of the product. 270 | 271 | “Installation Information” for a User Product means any methods, 272 | procedures, authorization keys, or other information required to install and execute 273 | modified versions of a covered work in that User Product from a modified version of 274 | its Corresponding Source. The information must suffice to ensure that the continued 275 | functioning of the modified object code is in no case prevented or interfered with 276 | solely because modification has been made. 277 | 278 | If you convey an object code work under this section in, or with, or specifically for 279 | use in, a User Product, and the conveying occurs as part of a transaction in which 280 | the right of possession and use of the User Product is transferred to the recipient 281 | in perpetuity or for a fixed term (regardless of how the transaction is 282 | characterized), the Corresponding Source conveyed under this section must be 283 | accompanied by the Installation Information. But this requirement does not apply if 284 | neither you nor any third party retains the ability to install modified object code 285 | on the User Product (for example, the work has been installed in ROM). 286 | 287 | The requirement to provide Installation Information does not include a requirement to 288 | continue to provide support service, warranty, or updates for a work that has been 289 | modified or installed by the recipient, or for the User Product in which it has been 290 | modified or installed. Access to a network may be denied when the modification itself 291 | materially and adversely affects the operation of the network or violates the rules 292 | and protocols for communication across the network. 293 | 294 | Corresponding Source conveyed, and Installation Information provided, in accord with 295 | this section must be in a format that is publicly documented (and with an 296 | implementation available to the public in source code form), and must require no 297 | special password or key for unpacking, reading or copying. 298 | 299 | ### 7. Additional Terms 300 | 301 | “Additional permissions” are terms that supplement the terms of this 302 | License by making exceptions from one or more of its conditions. Additional 303 | permissions that are applicable to the entire Program shall be treated as though they 304 | were included in this License, to the extent that they are valid under applicable 305 | law. If additional permissions apply only to part of the Program, that part may be 306 | used separately under those permissions, but the entire Program remains governed by 307 | this License without regard to the additional permissions. 308 | 309 | When you convey a copy of a covered work, you may at your option remove any 310 | additional permissions from that copy, or from any part of it. (Additional 311 | permissions may be written to require their own removal in certain cases when you 312 | modify the work.) You may place additional permissions on material, added by you to a 313 | covered work, for which you have or can give appropriate copyright permission. 314 | 315 | Notwithstanding any other provision of this License, for material you add to a 316 | covered work, you may (if authorized by the copyright holders of that material) 317 | supplement the terms of this License with terms: 318 | 319 | * **a)** Disclaiming warranty or limiting liability differently from the terms of 320 | sections 15 and 16 of this License; or 321 | * **b)** Requiring preservation of specified reasonable legal notices or author 322 | attributions in that material or in the Appropriate Legal Notices displayed by works 323 | containing it; or 324 | * **c)** Prohibiting misrepresentation of the origin of that material, or requiring that 325 | modified versions of such material be marked in reasonable ways as different from the 326 | original version; or 327 | * **d)** Limiting the use for publicity purposes of names of licensors or authors of the 328 | material; or 329 | * **e)** Declining to grant rights under trademark law for use of some trade names, 330 | trademarks, or service marks; or 331 | * **f)** Requiring indemnification of licensors and authors of that material by anyone 332 | who conveys the material (or modified versions of it) with contractual assumptions of 333 | liability to the recipient, for any liability that these contractual assumptions 334 | directly impose on those licensors and authors. 335 | 336 | All other non-permissive additional terms are considered “further 337 | restrictions” within the meaning of section 10. If the Program as you received 338 | it, or any part of it, contains a notice stating that it is governed by this License 339 | along with a term that is a further restriction, you may remove that term. If a 340 | license document contains a further restriction but permits relicensing or conveying 341 | under this License, you may add to a covered work material governed by the terms of 342 | that license document, provided that the further restriction does not survive such 343 | relicensing or conveying. 344 | 345 | If you add terms to a covered work in accord with this section, you must place, in 346 | the relevant source files, a statement of the additional terms that apply to those 347 | files, or a notice indicating where to find the applicable terms. 348 | 349 | Additional terms, permissive or non-permissive, may be stated in the form of a 350 | separately written license, or stated as exceptions; the above requirements apply 351 | either way. 352 | 353 | ### 8. Termination 354 | 355 | You may not propagate or modify a covered work except as expressly provided under 356 | this License. Any attempt otherwise to propagate or modify it is void, and will 357 | automatically terminate your rights under this License (including any patent licenses 358 | granted under the third paragraph of section 11). 359 | 360 | However, if you cease all violation of this License, then your license from a 361 | particular copyright holder is reinstated **(a)** provisionally, unless and until the 362 | copyright holder explicitly and finally terminates your license, and **(b)** permanently, 363 | if the copyright holder fails to notify you of the violation by some reasonable means 364 | prior to 60 days after the cessation. 365 | 366 | Moreover, your license from a particular copyright holder is reinstated permanently 367 | if the copyright holder notifies you of the violation by some reasonable means, this 368 | is the first time you have received notice of violation of this License (for any 369 | work) from that copyright holder, and you cure the violation prior to 30 days after 370 | your receipt of the notice. 371 | 372 | Termination of your rights under this section does not terminate the licenses of 373 | parties who have received copies or rights from you under this License. If your 374 | rights have been terminated and not permanently reinstated, you do not qualify to 375 | receive new licenses for the same material under section 10. 376 | 377 | ### 9. Acceptance Not Required for Having Copies 378 | 379 | You are not required to accept this License in order to receive or run a copy of the 380 | Program. Ancillary propagation of a covered work occurring solely as a consequence of 381 | using peer-to-peer transmission to receive a copy likewise does not require 382 | acceptance. However, nothing other than this License grants you permission to 383 | propagate or modify any covered work. These actions infringe copyright if you do not 384 | accept this License. Therefore, by modifying or propagating a covered work, you 385 | indicate your acceptance of this License to do so. 386 | 387 | ### 10. Automatic Licensing of Downstream Recipients 388 | 389 | Each time you convey a covered work, the recipient automatically receives a license 390 | from the original licensors, to run, modify and propagate that work, subject to this 391 | License. You are not responsible for enforcing compliance by third parties with this 392 | License. 393 | 394 | An “entity transaction” is a transaction transferring control of an 395 | organization, or substantially all assets of one, or subdividing an organization, or 396 | merging organizations. If propagation of a covered work results from an entity 397 | transaction, each party to that transaction who receives a copy of the work also 398 | receives whatever licenses to the work the party's predecessor in interest had or 399 | could give under the previous paragraph, plus a right to possession of the 400 | Corresponding Source of the work from the predecessor in interest, if the predecessor 401 | has it or can get it with reasonable efforts. 402 | 403 | You may not impose any further restrictions on the exercise of the rights granted or 404 | affirmed under this License. For example, you may not impose a license fee, royalty, 405 | or other charge for exercise of rights granted under this License, and you may not 406 | initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging 407 | that any patent claim is infringed by making, using, selling, offering for sale, or 408 | importing the Program or any portion of it. 409 | 410 | ### 11. Patents 411 | 412 | A “contributor” is a copyright holder who authorizes use under this 413 | License of the Program or a work on which the Program is based. The work thus 414 | licensed is called the contributor's “contributor version”. 415 | 416 | A contributor's “essential patent claims” are all patent claims owned or 417 | controlled by the contributor, whether already acquired or hereafter acquired, that 418 | would be infringed by some manner, permitted by this License, of making, using, or 419 | selling its contributor version, but do not include claims that would be infringed 420 | only as a consequence of further modification of the contributor version. For 421 | purposes of this definition, “control” includes the right to grant patent 422 | sublicenses in a manner consistent with the requirements of this License. 423 | 424 | Each contributor grants you a non-exclusive, worldwide, royalty-free patent license 425 | under the contributor's essential patent claims, to make, use, sell, offer for sale, 426 | import and otherwise run, modify and propagate the contents of its contributor 427 | version. 428 | 429 | In the following three paragraphs, a “patent license” is any express 430 | agreement or commitment, however denominated, not to enforce a patent (such as an 431 | express permission to practice a patent or covenant not to sue for patent 432 | infringement). To “grant” such a patent license to a party means to make 433 | such an agreement or commitment not to enforce a patent against the party. 434 | 435 | If you convey a covered work, knowingly relying on a patent license, and the 436 | Corresponding Source of the work is not available for anyone to copy, free of charge 437 | and under the terms of this License, through a publicly available network server or 438 | other readily accessible means, then you must either **(1)** cause the Corresponding 439 | Source to be so available, or **(2)** arrange to deprive yourself of the benefit of the 440 | patent license for this particular work, or **(3)** arrange, in a manner consistent with 441 | the requirements of this License, to extend the patent license to downstream 442 | recipients. “Knowingly relying” means you have actual knowledge that, but 443 | for the patent license, your conveying the covered work in a country, or your 444 | recipient's use of the covered work in a country, would infringe one or more 445 | identifiable patents in that country that you have reason to believe are valid. 446 | 447 | If, pursuant to or in connection with a single transaction or arrangement, you 448 | convey, or propagate by procuring conveyance of, a covered work, and grant a patent 449 | license to some of the parties receiving the covered work authorizing them to use, 450 | propagate, modify or convey a specific copy of the covered work, then the patent 451 | license you grant is automatically extended to all recipients of the covered work and 452 | works based on it. 453 | 454 | A patent license is “discriminatory” if it does not include within the 455 | scope of its coverage, prohibits the exercise of, or is conditioned on the 456 | non-exercise of one or more of the rights that are specifically granted under this 457 | License. You may not convey a covered work if you are a party to an arrangement with 458 | a third party that is in the business of distributing software, under which you make 459 | payment to the third party based on the extent of your activity of conveying the 460 | work, and under which the third party grants, to any of the parties who would receive 461 | the covered work from you, a discriminatory patent license **(a)** in connection with 462 | copies of the covered work conveyed by you (or copies made from those copies), or **(b)** 463 | primarily for and in connection with specific products or compilations that contain 464 | the covered work, unless you entered into that arrangement, or that patent license 465 | was granted, prior to 28 March 2007. 466 | 467 | Nothing in this License shall be construed as excluding or limiting any implied 468 | license or other defenses to infringement that may otherwise be available to you 469 | under applicable patent law. 470 | 471 | ### 12. No Surrender of Others' Freedom 472 | 473 | If conditions are imposed on you (whether by court order, agreement or otherwise) 474 | that contradict the conditions of this License, they do not excuse you from the 475 | conditions of this License. If you cannot convey a covered work so as to satisfy 476 | simultaneously your obligations under this License and any other pertinent 477 | obligations, then as a consequence you may not convey it at all. For example, if you 478 | agree to terms that obligate you to collect a royalty for further conveying from 479 | those to whom you convey the Program, the only way you could satisfy both those terms 480 | and this License would be to refrain entirely from conveying the Program. 481 | 482 | ### 13. Use with the GNU Affero General Public License 483 | 484 | Notwithstanding any other provision of this License, you have permission to link or 485 | combine any covered work with a work licensed under version 3 of the GNU Affero 486 | General Public License into a single combined work, and to convey the resulting work. 487 | The terms of this License will continue to apply to the part which is the covered 488 | work, but the special requirements of the GNU Affero General Public License, section 489 | 13, concerning interaction through a network will apply to the combination as such. 490 | 491 | ### 14. Revised Versions of this License 492 | 493 | The Free Software Foundation may publish revised and/or new versions of the GNU 494 | General Public License from time to time. Such new versions will be similar in spirit 495 | to the present version, but may differ in detail to address new problems or concerns. 496 | 497 | Each version is given a distinguishing version number. If the Program specifies that 498 | a certain numbered version of the GNU General Public License “or any later 499 | version” applies to it, you have the option of following the terms and 500 | conditions either of that numbered version or of any later version published by the 501 | Free Software Foundation. If the Program does not specify a version number of the GNU 502 | General Public License, you may choose any version ever published by the Free 503 | Software Foundation. 504 | 505 | If the Program specifies that a proxy can decide which future versions of the GNU 506 | General Public License can be used, that proxy's public statement of acceptance of a 507 | version permanently authorizes you to choose that version for the Program. 508 | 509 | Later license versions may give you additional or different permissions. However, no 510 | additional obligations are imposed on any author or copyright holder as a result of 511 | your choosing to follow a later version. 512 | 513 | ### 15. Disclaimer of Warranty 514 | 515 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. 516 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 517 | PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER 518 | EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 519 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE 520 | QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE 521 | DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 522 | 523 | ### 16. Limitation of Liability 524 | 525 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY 526 | COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS 527 | PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, 528 | INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE 529 | PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE 530 | OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE 531 | WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 532 | POSSIBILITY OF SUCH DAMAGES. 533 | 534 | ### 17. Interpretation of Sections 15 and 16 535 | 536 | If the disclaimer of warranty and limitation of liability provided above cannot be 537 | given local legal effect according to their terms, reviewing courts shall apply local 538 | law that most closely approximates an absolute waiver of all civil liability in 539 | connection with the Program, unless a warranty or assumption of liability accompanies 540 | a copy of the Program in return for a fee. 541 | 542 | _END OF TERMS AND CONDITIONS_ 543 | 544 | ## How to Apply These Terms to Your New Programs 545 | 546 | If you develop a new program, and you want it to be of the greatest possible use to 547 | the public, the best way to achieve this is to make it free software which everyone 548 | can redistribute and change under these terms. 549 | 550 | To do so, attach the following notices to the program. It is safest to attach them 551 | to the start of each source file to most effectively state the exclusion of warranty; 552 | and each file should have at least the “copyright” line and a pointer to 553 | where the full notice is found. 554 | 555 | 556 | Copyright (C) 557 | 558 | This program is free software: you can redistribute it and/or modify 559 | it under the terms of the GNU General Public License as published by 560 | the Free Software Foundation, either version 3 of the License, or 561 | (at your option) any later version. 562 | 563 | This program is distributed in the hope that it will be useful, 564 | but WITHOUT ANY WARRANTY; without even the implied warranty of 565 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 566 | GNU General Public License for more details. 567 | 568 | You should have received a copy of the GNU General Public License 569 | along with this program. If not, see . 570 | 571 | Also add information on how to contact you by electronic and paper mail. 572 | 573 | If the program does terminal interaction, make it output a short notice like this 574 | when it starts in an interactive mode: 575 | 576 | Copyright (C) 577 | This program comes with ABSOLUTELY NO WARRANTY; for details type 'show w'. 578 | This is free software, and you are welcome to redistribute it 579 | under certain conditions; type 'show c' for details. 580 | 581 | The hypothetical commands `show w` and `show c` should show the appropriate parts of 582 | the General Public License. Of course, your program's commands might be different; 583 | for a GUI interface, you would use an “about box”. 584 | 585 | You should also get your employer (if you work as a programmer) or school, if any, to 586 | sign a “copyright disclaimer” for the program, if necessary. For more 587 | information on this, and how to apply and follow the GNU GPL, see 588 | <>. 589 | 590 | The GNU General Public License does not permit incorporating your program into 591 | proprietary programs. If your program is a subroutine library, you may consider it 592 | more useful to permit linking proprietary applications with the library. If this is 593 | what you want to do, use the GNU Lesser General Public License instead of this 594 | License. But first, please read 595 | <>. 596 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This file is the main file 3 | Copyright (C) 2024 Breno Martins 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | ''' 18 | 19 | import datetime 20 | import praw 21 | import prawcore 22 | import prawcore.exceptions 23 | import json 24 | import praw.exceptions 25 | import tools 26 | import multiprocessing 27 | import shutil 28 | import time 29 | import traceback 30 | import random 31 | import os 32 | import psutil 33 | import readline 34 | import mysql.connector 35 | import preparation as prep 36 | import argparse 37 | 38 | 39 | parser = argparse.ArgumentParser(prog='lucasbot', description='Bot do reddit.') 40 | 41 | parser.add_argument('-p') # Senha do banco de dados 42 | parser.add_argument("-b", default="Nn") # inicia imediatamente o bakcup ou não 43 | args = parser.parse_args() 44 | 45 | 46 | start = datetime.datetime.now().timestamp() 47 | 48 | # Timestamp do inicio do programa 49 | start1 = datetime.datetime.now().timestamp() 50 | 51 | # Carregamento dos arquivos json de configuração 52 | 53 | config = tools.config 54 | reasons = tools.reasons 55 | boot = tools.boot 56 | 57 | config_path = tools.config_path 58 | 59 | 60 | print(f"Bem-vindo!") 61 | 62 | # Carregar o banco de dados para pegar informações 63 | end1 = datetime.datetime.now().timestamp() 64 | start2 = datetime.datetime.now().timestamp() 65 | 66 | sql = tools.db_connect(args=args) 67 | 68 | apid = int(config['db']['api_id']) 69 | 70 | cursor = sql.cursor() 71 | cursor.execute('set global max_allowed_packet=67108864;') 72 | sql.commit() 73 | 74 | cursor.execute(f"SELECT * FROM users WHERE id={apid};") 75 | api_t = cursor.fetchall() 76 | 77 | # Gerar user agent 78 | 79 | uagent = f'<{os.name}>:<{config["info"]["name"]}>:<{config["info"]["version"]}> (by /u/{config["info"]["creator"]})' 80 | 81 | # Entrar no reddit 82 | 83 | try: 84 | reddit = praw.Reddit( 85 | user_agent=uagent, 86 | client_id=api_t[0][2], 87 | client_secret=api_t[0][3], 88 | username=api_t[0][1], 89 | password=api_t[0][4] 90 | ) 91 | except (prawcore.exceptions.TooManyRequests, praw.exceptions.RedditAPIException): 92 | print("Permissão negada ao conectar ao Reddit. Verifique as credenciais e tente novamente.") 93 | 94 | api = { 95 | "username": api_t[0][1] 96 | } 97 | 98 | 99 | # Função da thread principal 100 | def runtime(): 101 | 102 | # Parte do meio do comentário 103 | botxt = f"\n\n# {config['upper_text']}\n\nOlá, meu nome é {config['info']['character']} e eu vou contar os votos que as pessoas dão nesse post. Pra seu voto ser contado, " \ 104 | f"responda o post com uma dessas siglas nos comentários...\n\n" 105 | 106 | # Verifica quais são os votos no arquivo de configuração e adiciona no corpo do comentário 107 | votxt = ["", "", ""] 108 | for k, v in config["flairs"].items(): 109 | votxt[v[1]] += f"{k} - {v[2]}\n\n" 110 | 111 | # Adiciona também os votos especiais... 112 | botxt += votxt[0] + "**Votos especiais**\n\n" + votxt[1] + "\n\n" 113 | 114 | reddit.validate_on_submit = True 115 | 116 | # Loop principal da função 117 | while True: 118 | sql = tools.db_connect(args=args) 119 | 120 | cursor = sql.cursor() 121 | try: 122 | # Texto placeholder para a parte que diz o veredito atual 123 | ftxt = f"# Veredito atual:" \ 124 | f" Não processado ainda\n\n" 125 | subcount = 0 # Número da submissão atual 126 | submissons = reddit.subreddit(config["subreddit"]).new( 127 | limit=int(config["submissions"])) # Pega X submissões do feed do subreddit 128 | atime = datetime.datetime.now().timestamp() # Essa parte serve para o cálculo do tempo que roddou a função 129 | 130 | # Loop para iterar nas 131 | tmr_exceptions = 0 132 | for submission in submissons: 133 | try: 134 | # Verificar se o post já está na tabela 135 | cursor.execute(f"SELECT subid FROM submissions WHERE subid='{submission.id}';") 136 | if not cursor.fetchone(): 137 | # Inserir novo registro 138 | cursor.execute( 139 | f"INSERT INTO submissions (subreddit, subid, author, nuked) " 140 | f"VALUES ('{config['subreddit']}', '{submission.id}', '{submission.author}', 0)" 141 | ) 142 | sql.commit() 143 | time.sleep(config["sleep_time"]["main"]) 144 | 145 | sql.commit() 146 | flairchanges = [] 147 | edits = [] 148 | adds = [] 149 | 150 | subcount += 1 151 | tools.logger(tp=2, ex=f"Submissão atual: {subcount}") 152 | timestmp = datetime.datetime.now().timestamp() - config[ 153 | "break_time"] # Calcula o quaõ velho o post tem que ser para ser ignorado 154 | 155 | if submission.created_utc <= timestmp: 156 | break # quebra o loop se o tempo de agora - x dias for maior que o tempo que criado. 157 | # Pegar os splashes 158 | cursor.execute(f"SELECT text FROM splashes WHERE owner={apid};") 159 | 160 | splashes = [] 161 | for x in cursor.fetchall(): 162 | splashes.append(x[0]) 163 | 164 | joke = random.choice(splashes) # Escolhe qual a mensagem vai ficar no final 165 | etxt = f""" 166 | 167 | *{joke}* 168 | *{config['info']['name']} {config['info']['version']} - by JakeWisconsin.* 169 | *Veja meu código fonte: [Código fonte]({config['info']['github']}). Configurações para o subreddit: [Configurações]({config['info']['config_github']})*""" # A parte final do comentário 170 | 171 | # Gera o dicionário que contêm os votos 172 | assholecount = {} 173 | for flair in config["flairs"].keys(): 174 | if flair not in config["flairs_ignore"]: 175 | assholecount[flair] = 0 176 | 177 | # Pega a lista de ids usando a função getflietext() 178 | sublist = tools.getfiletext(open(f"{config['list_path']}/idlist", "r")) 179 | 180 | indx = -1 181 | # abrir a lista de corpos já salvos ou não 182 | 183 | # Salva as alterações no arquivo de corpos 184 | if submission.id not in sublist: # Se a submissão não tiver nos ids 185 | submission.reply( 186 | body="OP, por favor responda esse comentário com o motivo de você achar ser o babaca ou não para ajudar no julgamento. Caso queira remover os comentários do bot e o post depois, comente 'del' sem aspas (só válido para o autor do post!!)\n\n>!NOEDIT!<") 187 | botcomment = submission.reply( 188 | body=ftxt + botxt + etxt) # Responde a publicação com a soma das partes como placeholder 189 | try: 190 | botcomment.reply(body="# Texto original\n\n"+submission.selftext+"\n\n>!NOEDIT!<") 191 | except Exception: 192 | botcomment.reply(body="Texto muito longo.\n\n>!NOEDIT!<") 193 | tools.logger(0, sub_id=submission.id) 194 | botcomment.mod.distinguish(sticky=True) # Marca o comentário como MOD e o fixa 195 | botcomment.mod.approve() # Aprova o comentário 196 | sublist.append(submission.id) # Coloca o post na lista de ids 197 | submission.flair.select( 198 | config["flairs"]["NOT_CLASSIFIED"][0]) # Seleciona a flair de não classificado aind 199 | with open(f"{config['list_path']}/idlist", 'a') as f: 200 | f.write(submission.id + '\n') # Grava a nova lista de ids 201 | 202 | submission.comment_sort = 'new' # Filtra os comentários por novos 203 | submission.comments.replace_more(limit=None) 204 | comments = submission.comments.list() # E por fim pegas os comentários para calcular o julgamento 205 | 206 | # Variáveis para o cálculo 207 | highest = 0 208 | key = '' 209 | users = [] 210 | total = 0 211 | judgment = "" 212 | percent = 0 213 | rates = [x for x in assholecount.keys()] 214 | judges = config["vote_name"] 215 | num_coms = 0 216 | 217 | # Número de comentários 218 | for com in comments: 219 | num_coms += 1 220 | 221 | # Loop para iterar nos comentários 222 | invalid = 0 # Comentários inválidos 223 | for comment in comments: 224 | try: 225 | if comment.author != api["username"] and comment.author not in users \ 226 | and comment.author != submission.author: # Se o votante não for o autor, não tiver sido contado já ou não for o bot... 227 | # Vê as respostas dos comentários para achar o comentário de ignorar 228 | 229 | if True: # Removendo a censura 230 | comment_body = comment.body.split(' ') # O corpo do comentário é divido em palavras 231 | indx = -1 232 | # Aparentemente esse código não era tão inutil 233 | for sub in comment_body: 234 | indx += 1 235 | sub = sub.split("\n") 236 | comment_body[indx] = sub[0] 237 | try: 238 | comment_body.insert(indx + 1, sub[1]) 239 | except IndexError: 240 | pass 241 | rate = [] # Lista de palvras strippadas 242 | # Para palavra no comentário... 243 | for sub in comment_body: 244 | sub = sub.strip() # Méteodo strip na palavra.... 245 | replaces = config["replace_list"] 246 | for c in replaces: 247 | sub = sub.replace(c, "") # Remove caractéres especiais 248 | 249 | rate.append(sub) 250 | 251 | indx = -1 252 | for w in rate: # Para w na lista de palvras estripadas... 253 | indx += 1 254 | rate[indx] = w.upper() # Coloca a palavra EM MAIUSCULO 255 | 256 | # Da lista de votos possíveis, se um deles tiver ali, adiciona mais um no número de votos 257 | doesItCount = False 258 | for r in rates: 259 | if r in rate: 260 | assholecount[r] += 1 261 | doesItCount = True 262 | break 263 | 264 | # Adiciona os ignorados caso não esteja nas keys 265 | if not doesItCount: 266 | invalid += 1 267 | 268 | total = 0 269 | # Para k e v na lista de votos 270 | for k, v in assholecount.items(): 271 | total += v # Adiciona mais um no total 272 | if v >= highest: # E vê qual o maior 273 | highest = v 274 | key = k 275 | try: 276 | percent = highest / total # Caclula qual a poercentagem 277 | except ZeroDivisionError: 278 | percent = 1.00 279 | 280 | # Calcula o julgamento 281 | ind = rates.index(key) 282 | judgment = judges[ind] 283 | 284 | if percent < 0.50: # Se a porcentagem for menor quee 50%, nenhum teve a maioria 285 | judgment = "Nenhum voto atingiu a maioria" 286 | votetxt = f"{total} votos contados ao total" 287 | else: 288 | votetxt = f"{percent * 100:.2f}% de {total} votos" # Se não, atingiu 289 | 290 | # Agora, se o total for igual a zero não foi avaliado ainda =) 291 | if total == 0: 292 | judgment = "Não avaliado" 293 | votetxt = f"{total} votos contados ao total" 294 | ftxt = f"### " \ 295 | f"{judgment} ({votetxt})" 296 | users.append(comment.author) 297 | else: 298 | invalid += 1 299 | except Exception: 300 | tools.logger(5, ex=traceback.format_exc()) 301 | 302 | # Calcula todas as porcentagens 303 | percents = {} 304 | for k, v in assholecount.items(): 305 | try: 306 | percents[k] = f"{(int(v) / total) * 100:.2f}" 307 | except ZeroDivisionError: 308 | percents[k] = f"0.00" 309 | tools.logger(2, ex="Submissão analizada!") 310 | 311 | # A tabelinha 312 | votxt = f""" 313 | # Tabela de votos 314 | Voto | Quantidade | % 315 | :--:|:--:|:--: 316 | """ 317 | 318 | assholes = 0 # Número de votos babacas 319 | commoners = 0 # Número de votos "comuns" 320 | total_ac = 0 # Total de votos babacas e comuns 321 | # Calcula os babacas e põe informações na tabela 322 | for k, v in assholecount.items(): 323 | votxt += f"{k} | {v} | {percents[k]}%\n" 324 | if total >= 1: 325 | if k in config["asshole"] or k in config['not_asshole']: 326 | total_ac += v 327 | 328 | # Adicionar os comentários ignorados 329 | votxt += f"\n\n**Comentários inválidos: {invalid}**\n" 330 | if submission.approved: 331 | votxt += "\n*O post foi verificado e aprovado pela moderação, portanto votos FANFIC e OT são desconsiderados*.\n" 332 | 333 | # Pega a porcentagem de votos babacas e votos não babacas 334 | if total_ac >= 1: 335 | for k, v in assholecount.items(): 336 | if k in config["asshole"]: 337 | assholes += (v / total_ac) * 100 338 | elif k in config["not_asshole"]: 339 | commoners += (v / total_ac) * 100 340 | 341 | points = int(((assholes - commoners) + 100) * 5) # Por fim, calculado os pontos de babaquice 342 | 343 | # Adiciona no corpo do texto 344 | ftxt += f"\n# Nível de babaquice: {points / 10:.2f}%" 345 | timeval = "\n\nÚltima análise feita em: " \ 346 | f"{datetime.datetime.now().strftime('%d/%m/%Y às %H:%M')}\n\n " # Última analise... 347 | etxt = votxt + timeval + etxt # Junta várias partes do corpo do comentário 348 | 349 | current_flair = submission.link_flair_template_id 350 | try: 351 | selected_flair = config["flairs"][key][0] 352 | except KeyError: 353 | selected_flair = "" 354 | not_avaliable_flair = config["flairs"]["NOT_AVALIABLE"][0] 355 | inconclusive_flair = config["flairs"]["INCONCLUSIVE"][0] 356 | 357 | changed_flair = True 358 | 359 | if percent >= 0.5 and total > 0: 360 | 361 | # Verifica se a flair já não foi aplicada. Só aplica se for diferente 362 | if current_flair != selected_flair and selected_flair != "": 363 | submission.flair.select(selected_flair) # Seleciona a flair se tiver uma 364 | else: 365 | changed_flair = False 366 | 367 | if key in ["FANFIC", "OT"]: # Se o voto mais top tiver em um desses dois ai... 368 | if not submission.approved: 369 | # Remover se o numero de votos for maior que o numero especificado no config 370 | if total >= config["remove_votes"]: 371 | removes = open(f"{config['list_path']}/rid", "r").readlines() # Checa a lista de remoções 372 | 373 | indx = -1 374 | for sub in removes: 375 | indx += 1 376 | removes[indx] = sub.strip() 377 | 378 | # if submission.id not in removes and total > 3: # Se a submissão não tiver na lista de remoção e o total for maior que 1 (a lista ainda nn foi gravada) 379 | 380 | # Reporta a submissão e adiciona lista de remoções 381 | 382 | reason = reasons["FAKE_OT"] 383 | #submission.mod.remove(mod_note=f"{reason['note']}", spam=False) 384 | submission.report("Post suspeito de ser FANFIC/OT.") 385 | submission.reply(body=f"{reason['body']}") 386 | tools.logger(tp=4, sub_id=submission.id, reason="VIolação") 387 | open(f"{config['list_path']}/rid", "a").write(f"{submission.id}\n") 388 | else: 389 | submission.flair.select(config["flairs"]["NOT_AVALIABLE"][0]) 390 | else: 391 | if current_flair != not_avaliable_flair: 392 | submission.flair.select(not_avaliable_flair) 393 | ftxt = f"### " \ 394 | f"Post marcado como FANFIC/OT mas aprovado pela moderação" 395 | else: 396 | changed_flair = False 397 | # Se a porcaentagem está fora da média, seleciona a flair de fora da média 398 | elif percent < 0.5 and total > 0: 399 | if current_flair != inconclusive_flair: 400 | submission.flair.select(inconclusive_flair) 401 | else: 402 | changed_flair = False 403 | elif total == 0: # Se o total for exatamente zero, a de não disponível 404 | if current_flair != not_avaliable_flair: 405 | submission.flair.select(config["flairs"]["NOT_AVALIABLE"][0]) 406 | else: 407 | changed_flair = False 408 | 409 | if changed_flair: 410 | flairchanges += f"\n* Flair de https://www.reddit.com/{submission.id} é '{judgment}'" 411 | tools.logger(2, ex=f"Flair editada em {submission.id}") 412 | 413 | # Adiciona a justificativa no corpo do bot 414 | ebotxt = botxt 415 | ebotxt += f"\n\nDe acordo com u/{submission.author}, o motivo dele se achar ou não um babaca é esse:\n\n" 416 | for _ in range(0, 3): # Tentar 3 vezes para caso de erro 417 | time.sleep(config["sleep_time"]["main"]) 418 | try: 419 | reasoning = json.load(open(f"{config['list_path']}/reasoning/reasonings.json", "r")) 420 | areason = reasoning[submission.id] 421 | break 422 | except KeyError: 423 | areason = "Não justificado." 424 | break 425 | except json.JSONDecodeError: 426 | # O erro não interfere no programa aprentemente... 427 | areason = "Erro ao abrir o arquivo, favor falar para a moderação." 428 | 429 | for line in areason.split("\n"): 430 | ebotxt += f">{line}\n" 431 | 432 | for com in comments: 433 | if com.author == f"{api['username']}": 434 | bd = com.body.split("\n") 435 | fullbody = ftxt + ebotxt + etxt # Cola as partes do comentário 436 | if ">!NOEDIT!<" not in bd: # Se não tiver ">!NOEDIT!<" 437 | 438 | com.edit( 439 | body=fullbody) # Edita o comentário do placar 440 | tools.logger(1, sub_id=submission.id) 441 | edits += f"\n* Comentário do bot editado em https://www.reddit.com/{submission.id}\n" 442 | 443 | ftxt = f"# Veredito atual:" \ 444 | f" Não disponível \n\nÚltima atualização feita em: " \ 445 | f"{datetime.datetime.now().strftime('%d/%m/%Y às %H:%M')}\n\n " 446 | 447 | tmr_exceptions = 0 # Reseta o wait time se tudo deu certo 448 | except (prawcore.exceptions.TooManyRequests, praw.exceptions.RedditAPIException): 449 | sleep_time = 10 + tmr_exceptions # Tempo de espera total 450 | tools.logger(tp=5, ex=f"Too many requests! esperando {sleep_time} segundos...", bprint=False) 451 | 452 | if sleep_time > 600: 453 | tools.logger(tp=5, exx="O acesso a api está negado?", bprint=True) 454 | exit(-1) 455 | 456 | time.sleep(sleep_time) 457 | 458 | tmr_exceptions += 1 459 | 460 | btime = datetime.datetime.now().timestamp() 461 | tools.log_runtime(runtime, atime, btime) # Coloca o runtime total da função 462 | except Exception as e: 463 | tools.logger(5, ex=traceback.format_exc()) 464 | 465 | 466 | # Função de backup 467 | def backup(): 468 | already_run = False 469 | while True: 470 | atime = datetime.datetime.now().timestamp() 471 | try: 472 | current_time = datetime.datetime.now().strftime('%H:%M') 473 | backup_path = config['backup']['path'] 474 | # Só faz backup em determinados temopos 475 | if (current_time in config["backup"]["time"] and not already_run) or args.b in "SsYy": 476 | folder = f"{backup_path}/{datetime.datetime.now().strftime('%Y-%m-%d/%H-%M-%S')}" # Pega a pasta para salvar o backup 477 | src_list = [".", config_path, config["list_path"]] # O sources 478 | for src in src_list: 479 | shutil.copytree(src, f"{folder}/{src.split('/')[-1] if src != '.' else 'Main'}", 480 | ignore=shutil.ignore_patterns("venv", "__", "pyenv", "Backups"), dirs_exist_ok=True) # Copia a árvore de pastas 481 | #tools.logger(2, bprint=False, ex="Backup realizado") 482 | 483 | # Deletar os backups dos dias mais antigos 484 | folders = os.listdir(backup_path) 485 | max_days = config["backup"]["max_days"] 486 | 487 | if len(folders) > max_days: 488 | index = 0 489 | 490 | # Colocar o path nos indexes das pastas 491 | for f in folders: 492 | folders[index] = os.path.join(backup_path, f) 493 | index += 1 494 | 495 | # Tirar os mais recentes da lista de acordo com max_days 496 | for _ in range(0, max_days): 497 | del folders[-1] 498 | 499 | # Excluir os que restaram 500 | for f in folders: 501 | shutil.rmtree(f) 502 | 503 | already_run = True 504 | else: 505 | already_run = False 506 | except Exception: 507 | print(traceback.format_exc()) 508 | time.sleep(config["sleep_time"]["backup"]) 509 | btime = datetime.datetime.now().timestamp() 510 | 511 | 512 | # Limpador de logs 513 | def clearlog(non_automatic=False): 514 | if non_automatic: 515 | open(f'{config["list_path"]}/log', "w+").write("") 516 | return 517 | 518 | while True: 519 | open(f'{config["list_path"]}/log', "w+").write("") 520 | time.sleep(config["clear"]*24*60*60) 521 | 522 | 523 | # Verificador de paredes de texto 524 | def sub_filter(): 525 | reddit.validate_on_submit = True 526 | 527 | while True: 528 | atime = datetime.datetime.now().timestamp() 529 | try: 530 | subcount = 0 531 | submissons = reddit.subreddit(config["subreddit"]).new(limit=int(config["submissions"])) # Pega subs 532 | tmr_exceptions = 0 533 | for submission in submissons: 534 | is_removed = False 535 | try: 536 | timestmp = datetime.datetime.now().timestamp() - config[ 537 | "break_time"] # Calcula o quaõ velho o post tem que ser para ser ignorado 538 | 539 | if submission.created_utc <= timestmp: 540 | break # quebra o loop se o tempo de agora - x dias for maior que o tempo que criado. 541 | 542 | time.sleep(config["sleep_time"]["textwall"]) 543 | subcount += 1 544 | subid = submission.id 545 | 546 | sublist = tools.getfiletext(open(f"{config['list_path']}/rid", "r")) # Pega a lista de remoções 547 | indx = -1 548 | 549 | for i in sublist: 550 | indx += 1 551 | sublist[indx] = i.strip() 552 | 553 | 554 | keywords = tools.getfiletext(open(f"{config['list_path']}/keywords.txt", "r")) # Palavras de filtro 555 | 556 | subcount += 1 557 | 558 | sublist_com = tools.getfiletext(open(f"{config['list_path']}/cid", "r")) # Pega a lista de remoções 559 | indx = -1 560 | 561 | submission.comment_sort = 'new' # Filtra os comentários por novos 562 | submission.comments.replace_more(limit=None) 563 | comments = submission.comments.list() 564 | 565 | # Iteração pela lista de comentários 566 | for com in comments: 567 | time.sleep(config["sleep_time"]["filter_sub"]) 568 | if com.id not in sublist_com: 569 | for x in com.body.lower().replace("\n", " ").replace("\n\n", " ").split(" "): 570 | for letra in x: 571 | replace = config["replace_list"] 572 | if letra in replace: 573 | x = x.replace(letra, '') 574 | if x in keywords: 575 | # Se o filtro pegar, o comentário vai ser denunciado 576 | com.report(f"Filtro detectou: {x}") 577 | 578 | tools.logger(ex=x, sub_id=submission.id, com_id=com.id, tp=6) 579 | 580 | open(f"{config['list_path']}/cid", "a").write(f"{com.id}\n") 581 | 582 | time.sleep(config["sleep_time"]["filter_sub"]) 583 | if subid not in sublist and not submission.approved: # Se o submissão não tiver na lista de subs... 584 | remove_replies = [] # Todos os comentários de remoção do bot 585 | try: 586 | body = submission.selftext # Pega o corpo do texto 587 | except: 588 | body = "" 589 | 590 | # Coloca os valores padrões de parágrafos e frases para 1... 591 | paragraphs = 1 592 | sentences = 1 593 | 594 | # Determinar quantos parágrafos tem o texto 595 | index = -1 596 | paragraph_cond = False 597 | 598 | # Número de caracteres 599 | chars = 0 600 | 601 | if body != "": # Se o corpo for diferente de "" 602 | for i in body: 603 | index += 1 604 | chars += 1 605 | 606 | # Quantas frases tem 607 | if i in [".", "?", "!", " "]: 608 | sentences += 1 609 | 610 | paragraph_cond = False 611 | 612 | # Tentativa de contar os paragrafos de um jeito melhor 613 | split_body = body.split("\n\n") 614 | paragraphs = len(split_body) 615 | else: 616 | # Se não, é zero! 617 | paragraphs = 0 618 | sentences = 0 619 | 620 | owner = config["info"]["owner"] 621 | 622 | min_paragraphs = config["text_filter"]["min_paragraphs"] 623 | min_sentences = config["text_filter"]["min_sentences"] 624 | max_body = config["text_filter"]["max_body"] 625 | min_body = config["text_filter"]["min_body"] 626 | post_removed = False 627 | # Remove a publicação suspeita de parede de texto. 628 | if (paragraphs < min_paragraphs or 629 | sentences < min_sentences or 630 | len(body) > max_body or 631 | len(body) < min_body): 632 | 633 | reason = reasons['TEXTWALL'] 634 | submission.mod.remove(mod_note=reason['note'], spam=False) 635 | cursor.execute(f"UPDATE submissions SET nuked=1 WHERE subid='{post_id}';") 636 | post_removed = True 637 | reasonstr = f"Post caiu no filtro de parede de texto e por isso foi removido. Arrume e reposte. Confira os critérios analizados:\n\n" 638 | 639 | # Adicionar as condicionais e seus valores booleanos 640 | conds = [f"* Tem o número minimo de paragrafos? {'sim' if paragraphs >= min_paragraphs else 'não'} (Mínmo: {min_paragraphs})", 641 | f"* Tem o número minimo de frases? {'sim' if sentences >= min_sentences else 'não'} (Mínmo: {min_sentences})", 642 | f"* Menor que o número máximo de caractéres? {'sim' if len(body) <= max_body else 'não'} (Máximo: {max_body})", 643 | f"* Tem o número mínimo de caractéres? {'sim' if len(body) >= min_body else 'não'} (Mínimo: {min_body})"] 644 | for x in range(0, len(conds)): 645 | reasonstr += conds[x]+"\n" 646 | 647 | submission.reply(body=reasonstr + f"\n\nParágrafos: {paragraphs}\n\nFrases: {sentences}\n\nCaractéres: {chars}\n\nCaso tenha sido um erro, fale com a [moderação](https://www.reddit.com/message/compose?to=r%2F{config['subreddit']}).\n\n>!NOEDIT!<.") 648 | tools.logger(tp=4, sub_id=subid, reason="Parede de texto") 649 | 650 | 651 | open(f"{config['list_path']}/rid", "a").write(f"{subid}\n") 652 | 653 | # Olhar os regexes 654 | if config["text_filter"]["filter_human"]: 655 | # Idade 656 | remove = False 657 | reason_raw = f"O post foi removido pois ele não possui ?. Coloque a idade e o genero no post seguindo o padrão 'idade genero' e reposte. Coloque a idade como um número e o genero como uma dessas letras: 'H', 'M', 'NB'. Exemplo: 18 H." 658 | reason = "" 659 | if not tools.match("age", body): 660 | reason = reason_raw.replace("?", "idade") 661 | remove = True 662 | 663 | if not tools.match("gender", body): 664 | reason = reason_raw.replace("?", "gênero") 665 | remove = True 666 | 667 | if tools.match("phone", body): 668 | remove = True 669 | reason = f"Sua publicação foi removida pois ela possui um número de telefone, logo pode ser spam ou uma tentativa de doxxing." 670 | 671 | if tools.match("email", body): 672 | remove = True 673 | reason = f"Sua publicação foi removida pois ela possui um email, logo pode ser spam ou uma tentativa de doxxing." 674 | 675 | if tools.match("cpf", body): 676 | remove = True 677 | reason = f"Sua publicação foi removida pois ela possui um CPF." 678 | 679 | if tools.match("url", body): 680 | remove = True 681 | reason = f"Sua publicação foi removida pois ela possui um link. É proibido qualquer spam no subreddit." 682 | 683 | if remove: 684 | submission.mod.remove(mod_note="Sem idade", spam=False) 685 | post_removed = True 686 | submission.reply(body=reason+f"\n\nCaso tenha sido um erro, fale com a [moderação](https://www.reddit.com/message/compose?to=r%2F{config['subreddit']}).\n\n>!NOEDIT!<") 687 | open(f"{config['list_path']}/rid", "a").write(f"{subid}\n") 688 | 689 | karma_filter = config["text_filter"]["karma_checker"] 690 | 691 | if karma_filter["enabled"]: 692 | karma = submission.author.comment_karma + submission.author.link_karma 693 | if karma < karma_filter["min"]: 694 | sub_created = submission.created_utc 695 | now = datetime.datetime.now().timestamp() 696 | 697 | if now - sub_created < karma_filter["timeout"]: 698 | submission.mod.remove(mod_note="Sem karma", spam=False) 699 | submission.reply(body=f"Seu post foi removido pois você não possui karma suficiente para postar nesse subreddit. Envie um modmail para a [moderação](https://www.reddit.com/message/compose?to=r%2F{config['subreddit']}) para revisar o seu post.\n\n>!NOEDIT!<") 700 | post_removed = True 701 | else: 702 | if karma <= karma_filter["after_timeout_report_when"]: 703 | # Reportar 704 | submission.report(f"Post suspeito. Usuário possui {karma} de karma." 705 | ) 706 | post_removed = True 707 | 708 | open(f"{config['list_path']}/rid", "a").write(f"{subid}\n") 709 | if post_removed: 710 | # Responde o corpo do texto para debugging 711 | submission.reply(body="## Corpo do texto\n\n"+submission.selftext+"\n\n>!NOEDIT!<") 712 | tmr_exceptions = 0 713 | except (prawcore.exceptions.TooManyRequests, praw.exceptions.RedditAPIException): 714 | sleep_time = 10 + tmr_exceptions # Tempo de espera total 715 | tools.logger(tp=5, ex=f"Too many requests! esperando {sleep_time} segundos...", bprint=False) 716 | 717 | if sleep_time > 600: 718 | tools.logger(tp=5, exx="O acesso a api está negado?", bprint=True) 719 | exit(-1) 720 | btime = datetime.datetime.now().timestamp() 721 | tools.log_runtime(sub_filter, atime, btime) 722 | except Exception: 723 | tools.logger(tp=5, ex=traceback.format_exc()) 724 | 725 | 726 | def justification(): 727 | ''' 728 | Sistema que exige justifiação do post. Verifica depois de 1 hora de postado se o post foi justificado. 729 | :return: 730 | ''' 731 | reddit.validate_on_submit = True 732 | while True: 733 | atime = datetime.datetime.now().timestamp() 734 | try: 735 | subcount = 0 736 | submissons = reddit.subreddit(config["subreddit"]).new(limit=int(config["submissions"])) # Pega subs 737 | tmr_exceptions = 0 738 | for submission in submissons: 739 | try: 740 | timestmp = datetime.datetime.now().timestamp() - config[ 741 | "break_time"] # Calcula o quaõ velho o post tem que ser para ser ignorado 742 | 743 | if submission.created_utc <= timestmp: 744 | break # quebra o loop se o tempo de agora - x dias for maior que o tempo que criado. 745 | 746 | reasonings = json.load(open(f"{config['list_path']}/reasoning/reasonings.json", "r")) 747 | 748 | time.sleep(config["sleep_time"]["justification"]) 749 | subcount += 1 750 | subid = submission.id 751 | reason = "" 752 | 753 | # Contiunuar 754 | submission.comment_sort = 'new' # Filtra os comentários por novos 755 | submission.comments.replace_more(limit=None) 756 | comments = submission.comments.list() 757 | didOPans = False # se o op respondeu 758 | breakparent = False 759 | 760 | for com in comments: 761 | if com.author == api["username"]: # Se o autor do comentário for o bot. 762 | # Verificar se o autor do post respondeu 763 | comreplies = com.replies 764 | for reply in comreplies: # Para cada resposta do comentário 765 | if reply.author == submission.author: 766 | # Checa se não tem um caractere blacklistado, para evitar abusos 767 | blacklist = [] 768 | count = True 769 | for i in blacklist: 770 | for x in reply.body: 771 | if x == i: 772 | count = False 773 | break 774 | 775 | if not count: 776 | break 777 | if count: 778 | didOPans = True 779 | reason = reply.body # O motivo é o corpo da resposta 780 | breakparent = True 781 | break 782 | 783 | if breakparent: 784 | break 785 | 786 | # Para fins de evitar flood de remoções, verifica se o id não está na lista de ignorados 787 | igl = tools.getfiletext(open(f"{config['list_path']}/ignore_list", "r")) 788 | 789 | if didOPans: 790 | if subid in igl: 791 | reason = "Post postado antes de precisar justificar." 792 | open(f"{config['list_path']}/jid", "a").write(f"{subid}\n") 793 | 794 | # Salva o motivo 795 | reasonings[subid] = reason 796 | 797 | rstr = json.dumps(reasonings, indent=4) 798 | open(f"{config['list_path']}/reasoning/reasonings.json", "w").write(rstr) 799 | tmr_exceptions = 0 800 | except (prawcore.exceptions.TooManyRequests, praw.exceptions.RedditAPIException): 801 | sleep_time = 10 + tmr_exceptions # Tempo de espera total 802 | tools.logger(tp=5, ex=f"Too many requests! esperando {sleep_time} segundos...", bprint=False) 803 | 804 | if sleep_time > 600: 805 | tools.logger(tp=5, exx="O acesso a api está negado?", bprint=True) 806 | exit(-1) 807 | btime = datetime.datetime.now().timestamp() 808 | tools.log_runtime(justification, atime, btime) 809 | except Exception: 810 | tools.logger(tp=5, ex=traceback.format_exc()) 811 | 812 | 813 | def stat(): # Estatisticas do subreddit 814 | while True: 815 | sql = tools.db_connect(args=args) 816 | 817 | cursor = sql.cursor() 818 | try: 819 | 820 | 821 | add = False 822 | subr = reddit.subreddit(config["subreddit"]) 823 | reddit.validate_on_submit = True 824 | 825 | sql.commit() 826 | cursor.execute("SELECT max(id) FROM statistics") 827 | last_id = int(cursor.fetchall()[0][0]) 828 | 829 | # Checar se ja não foi checado e adicionar caso tenha passado uma hora no arquivo csv 830 | sql_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') 831 | 832 | subs = subr.subscribers 833 | 834 | cursor.execute(f"SELECT members FROM statistics WHERE id={last_id};") 835 | 836 | last_members = int(cursor.fetchall()[0][0]) 837 | growt = subs - last_members 838 | growt_perc = ((subs - last_members) / last_members) * 100 839 | 840 | cursor.execute( 841 | f"INSERT INTO statistics (`id`, `owner`, `datetime`, `members`, `growt`, `growt_percent`) VALUES ({last_id + 1}, {apid}, '{sql_time}', {subs}, {growt}, {growt_perc})") 842 | 843 | sql.commit() 844 | time.sleep(config["sleep_time"]["stat"]) 845 | # if add: 846 | # reddit.subreddit(f"{config['log_subreddit']}").submit(title=f"{date} {int(hour)-1 if int(hour) >= 1 else '23'}-{ctime} - GROWT", selftext=f"{subs} (CRESCIMENTO: {growt})") 847 | 848 | #tools.log_runtime(stat, atime, btime) 849 | 850 | except Exception: 851 | tools.logger(tp=5, ex=traceback.format_exc()) 852 | 853 | 854 | def check_deletion(): 855 | '''Verifica posts com solicitação de deleção via comentário "del" do autor''' 856 | while True: 857 | try: 858 | sql = tools.db_connect(args=args) 859 | cursor = sql.cursor() 860 | 861 | # Ler a lista de IDs de posts 862 | with open(f"{config['list_path']}/idlist", "r") as f: 863 | post_ids = [line.strip() for line in f.readlines()] 864 | 865 | # Deixa só os ultimos 1000 ids na lista 866 | if len(post_ids) > 1000: 867 | post_ids = post_ids[-1000:] 868 | 869 | for post_id in post_ids: 870 | # Verificar se o post está marcado como não nuked 871 | cursor.execute(f"SELECT author, nuked FROM submissions WHERE subid='{post_id}';") 872 | result = cursor.fetchone() 873 | 874 | if result and result[1] == 0: # Se nuked=False 875 | author = result[0] 876 | tmr_exceptions = 0 877 | try: 878 | # Buscar o post no Reddit 879 | submission = reddit.submission(id=post_id) 880 | submission.comments.replace_more(limit=None) 881 | comments = submission.comments.list() 882 | 883 | # Verificar comentários do autor com "del" 884 | for comment in comments: 885 | comment_body = [tools.smart_strip(x, config["replace_list"]).lower() for x in comment.body.split(" ")] 886 | comment_body = " ".join(comment_body) 887 | comment_body = comment_body.split(" ") 888 | 889 | if comment.author == author and config["nuking_word"] in comment_body: 890 | # Marcar como nuked 891 | cursor.execute(f"UPDATE submissions SET nuked=1 WHERE subid='{post_id}';") 892 | sql.commit() 893 | tools.logger(tp=2, ex=f"Post {post_id} marcado para deleção.") 894 | 895 | # Primeiro, remover os comentários do bot 896 | for commentb in comments: 897 | if commentb.author == api["username"]: 898 | commentb.mod.remove(spam=False) 899 | 900 | # Remove o post em questão 901 | submission.mod.remove(spam=False) 902 | 903 | # Remove os comentários feito pelo autor 904 | for commentc in comments: 905 | if commentc.author == author: 906 | commentc.mod.remove(spam=False) 907 | 908 | # Avisa o OP que o post foi deletado 909 | submission.reply(f"Post removido com sucesso. Tenha um bom dia, u/{author}.") 910 | 911 | # Envia um modmail para a moderação com o link do post 912 | reddit.subreddit(config["subreddit"]).submit(title="Post removido pelo usuário.", selftext=f"Esse post foi removido por u/{author}.\n\n[Link do post]({submission.permalink})") 913 | 914 | break 915 | 916 | tmr_exceptions = 0 917 | except (prawcore.exceptions.TooManyRequests, praw.exceptions.RedditAPIException): 918 | tmr_exceptions += 1 919 | sleep_time = 10 + tmr_exceptions # Tempo de espera total 920 | tools.logger(tp=5, ex=f"Too many requests! esperando {sleep_time} segundos...", bprint=False) 921 | 922 | if sleep_time > 600: 923 | tools.logger(tp=5, exx="O acesso a api está negado?", bprint=True) 924 | exit(-1) 925 | except prawcore.exceptions.NotFound: 926 | tools.logger(tp=4, ex=f"Post {post_id} não encontrado.") 927 | 928 | time.sleep(config["sleep_time"]["deletion_check"]) # Intervalo padrão 929 | except Exception as e: 930 | tools.logger(tp=5, ex=traceback.format_exc()) 931 | 932 | 933 | if __name__ == '__main__': 934 | tools.clear_console(), 935 | prep.begin(config) 936 | 937 | # Carrega as funções 938 | funcs = [[runtime], [sub_filter], [justification], [backup], [clearlog], [stat], [check_deletion]] 939 | 940 | # Inicializa os processos 941 | indx = 0 942 | processes = [] 943 | for x in funcs: 944 | processes.append(multiprocessing.Process(target=x[0], name=x[0].__name__)) 945 | 946 | pids = [os.getpid()] 947 | 948 | index = -1 949 | # E os bota para rodar de segundo plano 950 | func_total = 0 # total de milisegundos na inicialização da função 951 | 952 | # Antes de começar, printa o PID da thread principal 953 | print(f"main: {pids[0]}") 954 | 955 | for i in processes: 956 | func_start = datetime.datetime.now().timestamp() #ms da função 957 | 958 | index += 1 959 | i.start() 960 | pids.append(i.pid) # Salva os pids 961 | 962 | func_end = datetime.datetime.now().timestamp() # ms da função 963 | func_total += (func_end - func_start) * 1000 964 | 965 | print( 966 | f"Iniciado processo com o PID {i.pid} para a função {funcs[index][0].__name__}: {(func_end - func_start) * 1000:.0f} ms") 967 | 968 | # Termino do processo de inicializaçãp. 969 | 970 | pids_str = [str(x) for x in pids] 971 | open(f'{config["list_path"]}/pids', "w+").write("\n".join(pids_str)) 972 | open(f'{config["list_path"]}/main', "w+").write(str(os.getpid())) 973 | 974 | end2 = datetime.datetime.now().timestamp() 975 | 976 | total_main = (((end2 - start2) + ( 977 | end1 - start1)) * 1000) 978 | 979 | print( 980 | f"main: {total_main:.0f} ms. ({total_main - (func_total):.0f} ms de inicialização e {func_total:.0f} ms de preparação)") 981 | 982 | # Loop para os comandos (primeiro plano) 983 | while True: 984 | try: 985 | # Try para EOF e erros sem tratamento específico 986 | inp = input("=> ").upper().split(" ") 987 | if len(inp) >= 1: 988 | if inp[0] == "R": # Se o input do usuário for R, vai simplesmente reccarregar os valores. (Não testado) 989 | config = json.load(open('config.json', 'r')) 990 | sql.commit() 991 | print("Valores recarregados na memória.") 992 | elif inp[0] == "E": # E termina o programa. 993 | for i in processes: 994 | i.terminate() 995 | 996 | break 997 | elif inp[0] == "LEAVE": 998 | print("Saindo do modo de input.") 999 | break 1000 | elif inp[0] == "RESTART": # Reinicia o programa 1001 | for i in processes: 1002 | i.terminate() 1003 | 1004 | os.system(f"{config['python']} ./main.py -p {args.p}") 1005 | break 1006 | elif inp[0] == "MEMORY": # Calcula a memória utilizada pelos processos 1007 | while True: 1008 | try: 1009 | mem = 0 1010 | perc = 0 1011 | index = 0 1012 | cputotal = 0 1013 | r = 0 1014 | all_processes = psutil.process_iter() 1015 | 1016 | for process in all_processes: 1017 | if process.pid in pids: 1018 | perc += process.memory_percent() 1019 | memory_info = process.memory_info() 1020 | mem_qnt = memory_info.rss / 1024 / 1024 1021 | mem += mem_qnt 1022 | cpu = process.cpu_percent() 1023 | cputotal += cpu 1024 | 1025 | print( 1026 | f"{funcs[index][0].__name__ if r > 0 else 'main'} ({process.pid}): {mem_qnt:.0f} mb, {cpu:.2f}% CPU") 1027 | 1028 | if r > 0: 1029 | index += 1 1030 | 1031 | r += 1 1032 | 1033 | print(f"Total: {mem:.0f} mb ({perc:.2f}%), {cputotal:.2f}% CPU") 1034 | uinput = input("") 1035 | 1036 | if uinput != "": 1037 | break 1038 | os.system("clear") 1039 | except KeyboardInterrupt: 1040 | os.system(f"{config['python']} ./main.py") 1041 | break 1042 | 1043 | elif inp[0] == "LOGSTREAM": 1044 | while True: 1045 | user = input("") 1046 | tools.clear_console() 1047 | if user == "": 1048 | print(open(f"{config['list_path']}/log", "r").readlines()[-1]) 1049 | else: 1050 | break 1051 | elif inp[0] == "ADDSPLASH": 1052 | sql.commit() 1053 | cursor.execute(f"SELECT id FROM splashes WHERE owner={apid};") 1054 | lastid = int(cursor.fetchall()[-1][0]) 1055 | 1056 | try: 1057 | cursor.execute( 1058 | f"INSERT INTO splashes (`id`, `owner`, `text`) VALUES ({lastid + 1}, {apid}, '{str(input('Texto: '))}')") 1059 | except Exception as e: 1060 | print(traceback.format_exc()) 1061 | 1062 | sql.commit() 1063 | elif inp[0] == "INJECT": 1064 | # O comando inject faz injeções python ou sql 1065 | if config["debug"]["injectable"]: 1066 | while True: 1067 | if len(inp) > 0: 1068 | if inp[1] == 'PYTHON': 1069 | try: 1070 | eval(input("INJECT => ")) 1071 | except SyntaxError: 1072 | print("Saindo!") 1073 | break 1074 | except Exception as e: 1075 | print(traceback.format_exc()) 1076 | elif inp[1] == "SQL": 1077 | try: 1078 | sql.commit() 1079 | injection = input("INJECT => ") 1080 | 1081 | if injection != "EXIT;": 1082 | cursor.execute(injection) 1083 | output = cursor.fetchall() 1084 | print(output) 1085 | else: 1086 | break 1087 | except mysql.connector.Error as e: 1088 | print(f"{e}") 1089 | 1090 | else: 1091 | print("Não é possível usar esse comando se 'injectable' for False.") 1092 | elif inp[0] == "SWITCH": 1093 | if len(inp) > 1: 1094 | try: 1095 | agreement = False 1096 | 1097 | if inp[1] == "INJECTABLE": 1098 | uinp = input("Ao alterar esse valor, você concorda sobre os potenciais riscos de segurança. Digite 'Eu concordo' para aceitar.\n=> ") 1099 | if uinp == "Eu concordo": 1100 | if input("Senha do banco de dados: ") == args.p: 1101 | agreement = True 1102 | else: 1103 | agreement = True 1104 | 1105 | if agreement: 1106 | config["debug"][inp[1].lower()] = not config["debug"][inp[1].lower()] 1107 | print("Alterado valor temporariamente.") 1108 | else: 1109 | print("Recusado.") 1110 | 1111 | except KeyError: 1112 | print("Erro! chave não existnte.") 1113 | elif inp[0] == "LICENSE": 1114 | print(f""" 1115 | {config["info"]["name"]} Copyright (C) {datetime.datetime.now().year} {config["info"]["author"]} 1116 | This program comes with ABSOLUTELY NO WARRANTY; 1117 | This is free software, and you are welcome to redistribute it 1118 | under certain conditions; Digite 'ABOUTME' para mostrar a licença completa.""") 1119 | elif inp[0] == "ABOUTME": 1120 | license = open("./LICENSE.md", "r").readlines() 1121 | 1122 | for line in license: 1123 | time.sleep(0.05) 1124 | print(line, end="") 1125 | elif inp[0] == "CLEAR": 1126 | tools.clear_console() 1127 | elif inp[0] == "CLEARLOG": 1128 | clearlog(0, non_automatic=True) 1129 | elif inp[0] == "": 1130 | print("Nenhum comando a executar.") 1131 | else: 1132 | print(f"O comando {inp[0]} não é válido.") 1133 | except EOFError: 1134 | print("Fim do arquivo de input. Encerrando loop de comandos.") 1135 | break 1136 | 1137 | 1138 | # Loop while que acontece em caso de EOFError ou qualquer outro erro que termine o loop de comandos. 1139 | while True: 1140 | time.sleep(0.5) 1141 | else: 1142 | print("Esse programa não pode ser usado como biblioteca. Rode diretamente.") 1143 | 1144 | --------------------------------------------------------------------------------