#!/usr/bin/env python3 import configparser import os import sys import time from PyQt5.QtCore import Qt, QTimer, QUrl, pyqtSignal from PyQt5.QtWidgets import QApplication, QWidget, QCheckBox from PyQt5.QtWidgets import QLayout, QHBoxLayout, QVBoxLayout, QSizePolicy from PyQt5.QtWidgets import QLabel, QLineEdit, QPushButton, QProgressBar from PyQt5.QtGui import QPixmap, QImage from PyQt5.QtMultimedia import QSound from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply THIS_DIR = os.path.dirname(os.path.realpath(__file__)) class ExtendedQLabel(QLabel): clicked = pyqtSignal(int, str) image = False # QPixmap() imageR = False # QPixmap() resized def __init(self, parent): QLabel.__init(self, parent) def setImage(self, image): self.image = image self.imageR = self.image.scaled(self.size(), Qt.KeepAspectRatio) self.setPixmap(self.imageR) def removeImage(self): self.image = False self.imageR = False self.setPixmap(QPixmap()) def resizeEvent(self, evt=None): QLabel.resizeEvent(self, evt) if self.image: self.imageR = self.image.scaled(self.size(), Qt.KeepAspectRatio) self.setPixmap(self.imageR) class CaptchaGUI(QWidget): config = None # Configuration (9kwpyqt.ini) sound = False # QSound() timer = False # QTimer() for 30sec timing soundUrgent = False # QSound() for urgent message (low time) running = False startCredits = None currentCredits = 0 currentCaptchaID = None currentCommited = 0 currentSkipped = 0 currentQueued = 0 currentWorker = 0 offlinemessage = "Click \"Start\" to fetch next captcha." timeleft = None urgentSoundPlayed = False waitingoncaptcha = 0 def __init__(self, parent=None): super(CaptchaGUI, self).__init__(parent) self.setMinimumWidth(400) # Top: Credits and Stats self.accountLabel = QLabel("credits") self.accountLabel.setAlignment(Qt.AlignLeft) self.statsLabel = QLabel("online stats") self.statsLabel.setAlignment(Qt.AlignRight) self.LayoutStats = QHBoxLayout() self.LayoutStats.addWidget(self.accountLabel) self.LayoutStats.addWidget(self.statsLabel) self.LayoutStats.sizeConstraint = QLayout.SetMinimumSize # Middle: Captchaimage self.captchaImage = ExtendedQLabel("") self.captchaImage.setAlignment(Qt.AlignCenter) self.captchaImage.setMinimumHeight(400) self.captchaImage.setSizePolicy( QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding ) self.captchaBox = QHBoxLayout() self.captchaBox.addWidget(self.captchaImage) # Bottom: StartStop/Sound-Button self.captchaInputLine = QLineEdit() self.captchaTimer = QLabel("---") self.startstopButton = QPushButton("Start") self.startstopButton.setCheckable(True) self.soundCheckbox = QCheckBox("Sound") self.soundCheckbox.setChecked(True) self.soundCheckbox.setSizePolicy( QSizePolicy.Fixed, QSizePolicy.Fixed ) self.soundCheckbox.clicked.connect(self.toggleSound) self.startstopButton.clicked.connect(self.toggleRunning) self.LayoutSettings = QHBoxLayout() self.LayoutSettings.addWidget(self.soundCheckbox) self.LayoutSettings.addWidget(self.startstopButton) self.LayoutInputTimer = QHBoxLayout() self.LayoutInputTimer.addWidget(self.captchaInputLine) self.LayoutInputTimer.addWidget(self.captchaTimer) self.LayoutSubmit = QVBoxLayout() self.LayoutSubmit.addLayout(self.LayoutInputTimer) self.LayoutSubmit.addLayout(self.LayoutSettings) # Compile layout mainLayout = QVBoxLayout() mainLayout.addLayout(self.LayoutStats) mainLayout.addLayout(self.captchaBox) mainLayout.addLayout(self.LayoutSubmit) self.setLayout(mainLayout) self.setWindowTitle("Captcha 9kw PyQt") self.setWindowFlags(Qt.Dialog) if not self.readConfig(): self.captchaImage.setText("Config file not found. New file created.\nPlease edit the configuration file to enter your api key.\nYou need to restart this programm afterwards.") self.startstopButton.setEnabled(False) return if not self.config['DEFAULT']['API_KEY']: self.captchaImage.setText("API Key not found.\nPlease edit the configuration file to enter your api key.\nYou need to restart this programm afterwards.") self.startstopButton.setEnabled(False) return # Initialize network self.NetworkManager = QNetworkAccessManager() QTimer.singleShot(500, self.getCredits) QTimer.singleShot(500, self.getQueue) QTimer.singleShot(500, self.getCaptchaID) self.timer = QTimer() self.timer.setInterval(100) self.timer.timeout.connect(self.onTimerTick) self.timer.start() self.startstopButton.setFocus() ################################################## # Handle gui ################################################## def updateStats(self): if self.startCredits: credittext = "%dc (%s%d)" % (self.currentCredits, (self.currentCredits - self.startCredits > 0) and '+' or '', (self.currentCredits - self.startCredits)) else: credittext = "%dc" % (self.currentCredits) self.accountLabel.setText(credittext) labeltext = "%d Solved (%d skipped) | %d Worker, Queue of %d" % (self.currentCommited, self.currentSkipped, self.currentWorker, self.currentQueued) self.statsLabel.setText(labeltext) def keyPressEvent(self, event): key = event.key() if key == Qt.Key_Escape: self.skipCaptcha() elif key == Qt.Key_Enter or key == Qt.Key_Return: if self.startstopButton.hasFocus(): self.toggleRunning() else: self.submitCaptcha() def setCaptchaImage(self, image=None): pixmap = QPixmap() result = pixmap.loadFromData(image) if result: self.captchaImage.setImage(pixmap) self.captchaInputLine.setText("") self.captchaInputLine.setFocus() if self.soundCheckbox.isChecked(): self.sound.play() self.timeleft = time.time() + 30 self.timer.start() self.captchaImage.setText("") else: self.captchaImage.setText("Could not display image. Skip!") self.skipCaptcha() def removeCaptchaImage(self): self.captchaImage.removeImage() def toggleRunning(self, force_start=False, force_stop=False): if force_start: self.running = True elif force_stop: self.running = False else: self.running = not self.running self.captchaImage.setText("") self.offlinemessage = "Click \"Start\" to fetch next captcha." self.startstopButton.setText(self.running and "Stop" or "Start") self.startstopButton.setChecked(self.running) def toggleSound(self): self.config['DEFAULT']['Sound'] = self.soundCheckbox.isChecked() and 'yes' or 'no' self.writeConfig() ################################################## # Handle network ################################################## def getCredits(self): url = QUrl(self.apiurl+"action=usercaptchaguthaben") self.networkCredits = QNetworkRequest(url) self.networkCreditsReply = self.NetworkManager.get(self.networkCredits) self.networkCreditsReply.finished.connect(self.getCreditsFinished) def getCreditsFinished(self): content = str(self.networkCreditsReply.readAll(), encoding='utf-8') if content.isnumeric(): self.currentCredits = int(content) if not self.startCredits: self.startCredits = self.currentCredits self.updateStats() else: self.accountLabel.setText("Error?") self.networkCreditsReply.deleteLater() QTimer.singleShot(10000, self.getCredits) def getQueue(self): url = QUrl("http://www.9kw.eu/grafik/servercheck.txt") self.networkQueue = QNetworkRequest(url) self.networkQueueReply = self.NetworkManager.get(self.networkQueue) self.networkQueueReply.finished.connect(self.getQueueFinished) def getQueueFinished(self): # Should be name=value|name2=value2|name3=value3... content = str(self.networkQueueReply.readAll(), encoding='utf-8') datatmp = content.split('|') data = dict() for stat in datatmp: if '=' in stat: tmp = stat.split('=') data[tmp[0]] = tmp[1] self.currentQueued = self.currentWorker = 0 if 'queue' in data and data['queue'].isnumeric(): self.currentQueued = int(data['queue']) if 'workertext' in data and data['workertext'].isnumeric(): self.currentWorker = int(data['workertext']) self.updateStats() self.networkQueueReply.deleteLater() QTimer.singleShot(5000, self.getQueue) def getCaptchaID(self): if not self.running: self.captchaImage.setText(self.offlinemessage) QTimer.singleShot(500, self.getCaptchaID) self.startstopButton.setStyleSheet("QPushButton { background-color: green; }") return if self.waitingoncaptcha == 0: self.captchaImage.setText("Asking server for new captcha...") self.startstopButton.setStyleSheet("QPushButton { background-color: red; }") url = QUrl(self.apiurl+"action=usercaptchanew&text=yes&mouse=0&confirm=0") self.networkCaptchaID = QNetworkRequest(url) self.networkCaptchaIDReply = self.NetworkManager.get(self.networkCaptchaID) self.networkCaptchaIDReply.finished.connect(self.getCaptchaIDFinished) def getCaptchaIDFinished(self): content = str(self.networkCaptchaIDReply.readAll(), encoding='utf-8') self.networkCaptchaIDReply.deleteLater() if content == 'NO CAPTCHA': self.waitingoncaptcha += 1 status = "Server responded with 'NO CAPTCHA' "+str(self.waitingoncaptcha)+" times." self.captchaImage.setText(status) QTimer.singleShot(1000, self.getCaptchaID) elif content.isnumeric(): self.waitingoncaptcha = 0 self.captchaImage.setText("Downloading new captcha.") self.currentCaptchaID = int(content) self.getCaptchaIMG() else: self.waitingoncaptcha += 1 status = "Unknown Server response (getCaptchaID).\n"+str(content) self.captchaImage.setText(status) def getCaptchaIMG(self): url = QUrl(self.apiurl+"action=usercaptchashow&id="+str(self.currentCaptchaID)) self.networkCaptchaIMG = QNetworkRequest(url) self.networkCaptchaIMGReply = self.NetworkManager.get(self.networkCaptchaIMG) self.networkCaptchaIMGReply.finished.connect(self.getCaptchaIMGFinished) def getCaptchaIMGFinished(self): image = self.networkCaptchaIMGReply.readAll() self.setCaptchaImage(image) self.networkCaptchaIMGReply.deleteLater() def setCaptchaAnswer(self, answer, skip=False): if skip: url = QUrl(self.apiurl+"action=usercaptchaskip&id="+str(self.currentCaptchaID)) self.networkCaptchaAnswer = QNetworkRequest(url) self.networkCaptchaAnswerReply = self.NetworkManager.get(self.networkCaptchaAnswer) self.networkCaptchaAnswerReply.finished.connect(self.setCaptchaAnswerSkipped) else: url = QUrl(self.apiurl+"action=usercaptchacorrect&id="+str(self.currentCaptchaID)+"&antwort="+answer+"&extended=1") self.networkCaptchaAnswer = QNetworkRequest(url) self.networkCaptchaAnswerReply = self.NetworkManager.get(self.networkCaptchaAnswer) self.networkCaptchaAnswerReply.finished.connect(self.setCaptchaAnswerCommit) self.currentCaptchaID = None def setCaptchaAnswerCommit(self): message = str(self.networkCaptchaAnswerReply.readAll(), encoding='utf-8') self.networkCaptchaAnswerReply.deleteLater() if message: if message.startswith('OK') and '|' in message: parts = message.split('|') if parts[1].isnumeric(): self.currentCredits += int(parts[1]) self.currentCommited += 1 self.updateStats() self.getCaptchaID() def setCaptchaAnswerSkipped(self): message = str(self.networkCaptchaAnswerReply.readAll(), encoding='utf-8') self.networkCaptchaAnswerReply.deleteLater() if not message: # Server error? Retry! (Status 200 + no 'OK'?) self.networkCaptchaAnswerReply = self.NetworkManager.get(self.networkCaptchaAnswer) self.networkCaptchaAnswerReply.finished.connect(self.setCaptchaAnswerSkipped) else: self.currentSkipped += 1 self.updateStats() self.getCaptchaID() ################################################## # Handle logic ################################################## def readConfig(self): if self.config is not None: return False defaultconfig = {} defaultconfig['API_URL'] = 'http://www.9kw.eu/index.cgi' defaultconfig['API_KEY'] = '' defaultconfig['Sound'] = 'yes' defaultconfig['Soundfile'] = 'notify.wav' defaultconfig['SoundfileUrgent'] = 'warning.wav' defaultconfig['Autostart'] = 'no' self.config = configparser.ConfigParser(defaultconfig) try: with open(os.path.join(THIS_DIR, '9kwpyqt.ini')) as configfile: self.config.read_file(configfile) except FileNotFoundError: self.writeConfig() return False # Overwrite file to add potential new options self.writeConfig() # Use config settings self.apiurl = self.config['DEFAULT']['API_URL']+"?source=pythonapi&apikey="+self.config['DEFAULT']['API_KEY']+"&" self.sound = QSound(os.path.join(THIS_DIR, self.config['DEFAULT']['Soundfile'])) self.soundUrgent = QSound(os.path.join(THIS_DIR, self.config['DEFAULT']['SoundfileUrgent'])) self.soundCheckbox.setChecked( self.config.getboolean('DEFAULT', 'Sound') ) if self.config.getboolean('DEFAULT', 'Autostart'): self.toggleRunning(force_start=True) return True def writeConfig(self): with open(os.path.join(THIS_DIR, '9kwpyqt.ini'), 'w') as configfile: self.config.write(configfile) def skipCaptcha(self): self.submitCaptcha(skip=True) def submitCaptcha(self, skip=False): answer = self.captchaInputLine.text() if not answer: skip = True self.timeleft = None self.captchaInputLine.setText("") self.removeCaptchaImage() if self.currentCaptchaID: self.setCaptchaAnswer(answer, skip) def onTimerTick(self): if not self.timeleft: self.captchaTimer.setText("...") self.urgentSoundPlayed = False self.timer.stop() else: timing = self.timeleft - time.time() self.captchaTimer.setText(str(round(timing, 1))+"s") if self.soundCheckbox.isChecked() and not self.urgentSoundPlayed and timing <= 10: self.urgentSoundPlayed = True self.soundUrgent.play() if timing <= 0: self.toggleRunning(force_stop=True) self.offlinemessage = "30 seconds passed without input." self.timeleft = None self.skipCaptcha() def onQuit(self): if self.currentCaptchaID: self.running = False self.setCaptchaAnswer("", skip=True, queue=False) def main(): app = QApplication(sys.argv) screen = CaptchaGUI() screen.show() sys.exit(app.exec_()) if __name__ == '__main__': main()