#!/usr/bin/env python3 import sys import time from PyQt5.QtCore import Qt, QTimer, QUrl, pyqtSignal from PyQt5.QtWidgets import QApplication, QWidget 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 API_URL = "http://www.9kw.eu/index.cgi" API_KEY = "" soundfile = "notify.wav" class PixelInfo(): pixelA = 0 pixelR = 0 pixelG = 0 pixelB = 0 def __init__(self, argb): self.pixelB = argb & 0xff argb = argb >> 8 self.pixelG = argb & 0xff argb = argb >> 8 self.pixelR = argb & 0xff argb = argb >> 8 self.pixelA = argb & 0xff def isLower(self, r, g, b): return (self.pixelR < r and self.pixelG < g and self.pixelB < b) def isHigher(self, r, g, b): return (self.pixelR > r and self.pixelG > g and self.pixelB > b) def isExactly(self, r, g, b): return (self.pixelR == r and self.pixelG == g and self.pixelB == b) class ExtendedQLabel(QLabel): clicked = pyqtSignal(int, str) image = False # QPixmap() imageR = False # QPixmap() resized imageB = False # QImage() captchaType = False # int / detected captcha type 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.imageB = False self.captchaType = False self.setPixmap(self.imageR) def removeImage(self): self.image = False self.imageR = False self.imageB = False self.captchaType = 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) def mouseReleaseEvent(self, ev): if not self.image: return if not self.imageB: self.imageB = self.image.toImage() # get size of pixmap imgx = self.pixmap().size().width() imgy = self.pixmap().size().height() # get cursor position relative to pixmap ((0,0) = topleft) x = ev.pos().x() - self.size().width() / 2 + imgx/2 y = ev.pos().y() - self.size().height() / 2 + imgy/2 if not self.captchaType: # Which type of captcha is this image? if PixelInfo(self.imageB.pixel(90, 45)).isExactly(0,0,0) and \ PixelInfo(self.imageB.pixel( 5, 30)).isHigher(220,220,220) and \ PixelInfo(self.imageB.pixel(40, 5)).isHigher(220,220,220) and \ (PixelInfo(self.imageB.pixel(58, 30)).isHigher(220,220,220) or \ PixelInfo(self.imageB.pixel(71, 30)).isHigher(220,220,220)) and \ (PixelInfo(self.imageB.pixel(40, 59)).isHigher(220,220,220) or \ PixelInfo(self.imageB.pixel(40, 60)).isHigher(220,220,220)): self.captchaType = 1 elif PixelInfo(self.imageB.pixel( 16, 47)).isLower(20,20,20) and \ PixelInfo(self.imageB.pixel(144, 47)).isLower(20,20,20) and \ PixelInfo(self.imageB.pixel(269, 47)).isLower(20,20,20) and \ PixelInfo(self.imageB.pixel( 16, 172)).isLower(20,20,20) and \ PixelInfo(self.imageB.pixel(144, 172)).isLower(20,20,20) and \ PixelInfo(self.imageB.pixel(269, 172)).isLower(20,20,20) and \ PixelInfo(self.imageB.pixel( 16, 298)).isLower(20,20,20) and \ PixelInfo(self.imageB.pixel(144, 298)).isLower(20,20,20) and \ PixelInfo(self.imageB.pixel(269, 298)).isLower(20,20,20): self.captchaType = 2 else: self.captchaType = -1 if self.captchaType == 1: # check if on pixmap (upper 17.5% = infopanel) if x > imgx or y > imgy or x < 0 or y < 0.175*imgy: return xpiece = x // (imgx / 3) + 1 ypiece = (y-0.175*imgy) // (imgy*0.825 / 3) self.clicked.emit(3*ypiece+xpiece, ",") elif self.captchaType == 2: # check if on pixmap (upper 7% = infopanel) if x > imgx or y > imgy or x < 0 or y < 0.0735*imgy: return xpiece = x // (imgx / 3) + 1 ypiece = (y-0.0735*imgy) // (imgy*0.9265 / 3) self.clicked.emit(3*(2-ypiece)+xpiece, "") class CaptchaGUI(QWidget): sound = False # QSound() timer = False # QTimer() for 30sec timing running = False startCredits = None currentCredits = 0 currentCaptchaID = None currentCommited = 0 currentSkipped = 0 currentQueued = 0 currentWorker = 0 apiurl = API_URL+"?source=pythonapi&apikey="+API_KEY+"&" offlinemessage = "Click \"Start\" to fetch next captcha." 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.timerLabel.setStyleSheet("QLabel { color: darkred; }") self.LayoutStats = QHBoxLayout() self.LayoutStats.addWidget(self.accountLabel) # self.LayoutStats.addWidget(self.timerLabel) # self.LayoutStats.addWidget(self.timerProgress) 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.captchaImage.clicked.connect(self.captchaClicked) self.captchaBox = QHBoxLayout() self.captchaBox.addWidget(self.captchaImage) # Bottom: Input + Submit/Skip-Buttons self.captchaInputLine = QLineEdit() self.startstopButton = QPushButton("Start") self.startstopButton.setCheckable(True) # self.saveImageButton = QPushButton("Sa&ve Image") self.submitButton = QPushButton("&Submit") self.skipButton = QPushButton("S&kip") self.startstopButton.clicked.connect(self.toggleRunning) # self.saveImageButton.clicked.connect(self.saveImage) self.submitButton.clicked.connect(self.submitCaptcha) self.skipButton.clicked.connect(self.skipCaptcha) self.LayoutSubmitLine = QHBoxLayout() self.LayoutSubmitLine.addWidget(self.startstopButton) self.LayoutSubmitLine.addWidget(self.captchaInputLine) # self.LayoutSubmitLine.addWidget(self.saveImageButton) self.LayoutSubmitButtons = QHBoxLayout() self.LayoutSubmitButtons.addWidget(self.submitButton) self.LayoutSubmitButtons.addWidget(self.skipButton) self.LayoutSubmit = QVBoxLayout() self.LayoutSubmit.addLayout(self.LayoutSubmitLine) self.LayoutSubmit.addLayout(self.LayoutSubmitButtons) # Compile layout mainLayout = QVBoxLayout() mainLayout.addLayout(self.LayoutStats) mainLayout.addLayout(self.captchaBox) mainLayout.addLayout(self.LayoutSubmit) self.setLayout(mainLayout) self.setWindowTitle("Captcha 9kw PyQt") if not API_KEY: self.captchaImage.setText("API_KEY is missing.\nPlease edit this file and enter your key at the top.") self.startstopButton.setEnabled(False) return # load soundfile self.sound = QSound(soundfile) # 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.timeout.connect(self.onTimeout) self.timer.setSingleShot(True) self.startstopButton.setFocus() ################################################## # Handle gui ################################################## def captchaClicked(self, clickedpiece, separator): text = self.captchaInputLine.text() if len(text) > 0 and separator: text += separator text += str(clickedpiece) self.captchaInputLine.setText(text) def updateStats(self): if self.startCredits: credittext = "%d ¥ (%s%d)" % (self.currentCredits, (self.currentCredits - self.startCredits > 0) and '+' or '', (self.currentCredits - self.startCredits)) else: credittext = "%d ¥" % (self.currentCredits) self.accountLabel.setText(credittext) labeltext = "%d Commited | %d Skipped | %d Queue | %d Worker" % (self.currentCommited, self.currentSkipped, self.currentQueued, self.currentWorker) 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.skipButton.hasFocus(): self.skipCaptcha() elif 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.setFocus() self.sound.play() self.timer.start(30000) self.captchaImage.setText("") else: self.captchaImage.setText("Could not display image. Skip!") self.skipCaptcha() def removeCaptchaImage(self): self.captchaImage.removeImage() def toggleRunning(self): 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 saveImage(self): # if self.image: # self.image.save("captcha-"+str(time.time())+".jpg") ################################################## # 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): content = str(self.networkQueueReply.readAll(), encoding='utf-8') datatmp = content.split('|') data = dict() for stat in datatmp: tmp = stat.split('=') data[tmp[0]] = tmp[1] self.currentQueued = int(data['queue']) 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 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 print("New Captcha:", int(content)) self.currentCaptchaID = int(content) self.getCaptchaIMG() else: self.waitingoncaptcha += 1 print("Unknown error? ID not 'NO CAPTCHA' nor number") print(content) 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) def setCaptchaAnswerCommit(self): message = str(self.networkCaptchaAnswerReply.readAll(), encoding='utf-8') self.networkCaptchaAnswerReply.deleteLater() if not message: # TODO: Server error? Retry! (Status 200 + no 'OK'?) self.networkCaptchaAnswerReply = self.NetworkManager.get(self.networkCaptchaAnswer) self.networkCaptchaAnswerReply.finished.connect(self.setCaptchaAnswerCommit) else: self.currentCommited += 1 self.currentCaptchaID = None if message.startswith('OK') and '|' in message: parts = message.split('|') creds = int(parts[1]) self.currentCredits += creds 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.currentCaptchaID = None self.updateStats() self.getCaptchaID() ################################################## # Handle logic ################################################## def skipCaptcha(self): self.submitCaptcha(skip=True) def submitCaptcha(self, skip=False): answer = self.captchaInputLine.text() if not answer: skip = True self.timer.stop() self.captchaInputLine.setText("") self.removeCaptchaImage() if self.currentCaptchaID: self.setCaptchaAnswer(answer, skip) def onTimeout(self): self.running = False self.offlinemessage = "30 seconds passed without input." self.startstopButton.setText(self.running and "Stop" or "Start") self.startstopButton.setChecked(self.running) self.skipCaptcha() def onQuit(self): if self.currentCaptchaID: self.running = False self.setCaptchaAnswer("", skip=True, queue=False) def main(): # NOTE(andre): urlopen() will first try ipv6, if available # hacky way to remove IPv6: http://stackoverflow.com/questions/2014534/force-python-mechanize-urllib2-to-only-use-a-requests/6319043 # better way: disable IPv6, if your isp does not support it # import pudb; pudb.set_trace() app = QApplication(sys.argv) screen = CaptchaGUI() screen.show() sys.exit(app.exec_()) if __name__ == '__main__': main()