From 15785a653e92e919850e04c43373b8be60f24b9e Mon Sep 17 00:00:00 2001 From: marco <10120390+mac12m99@users.noreply.github.com> Date: Sun, 21 Nov 2021 13:34:24 +0100 Subject: [PATCH] WIP: recaptcha --- platformcode/platformtools.py | 4 +- platformcode/recaptcha.py | 193 +++++++++++++++------ resources/skins/Default/720p/Recaptcha.xml | 128 +------------- servers/maxstream.json | 6 +- servers/maxstream.py | 29 ++-- specials/globalsearch.py | 1 + 6 files changed, 156 insertions(+), 205 deletions(-) diff --git a/platformcode/platformtools.py b/platformcode/platformtools.py index 25afbdf5..2d5496e9 100644 --- a/platformcode/platformtools.py +++ b/platformcode/platformtools.py @@ -1138,8 +1138,8 @@ def show_video_info(*args, **kwargs): def show_recaptcha(key, referer): - from platformcode.recaptcha import Recaptcha - return Recaptcha("Recaptcha.xml", config.get_runtime_path()).Start(key, referer) + from platformcode.recaptcha import Kodi + return Kodi(key, referer).run() def alert_no_disponible_server(server): diff --git a/platformcode/recaptcha.py b/platformcode/recaptcha.py index d7265d43..9b6a55a0 100644 --- a/platformcode/recaptcha.py +++ b/platformcode/recaptcha.py @@ -1,77 +1,164 @@ # -*- coding: utf-8 -*- +import random +import time +from threading import Thread -from builtins import range import xbmcgui from core import httptools -from core import scrapertools -from platformcode import config -from platformcode import platformtools +from core import filetools +from platformcode import config, platformtools +from platformcode import logger +from lib.librecaptcha.recaptcha import ReCaptcha, Solver, DynamicSolver, MultiCaptchaSolver, Solution, \ + ImageGridChallenge lang = 'it' +tiles_pos = (75+390, 90+40) +grid_width = 450 +tiles_texture_focus = 'white.png' +tiles_texture_checked = 'Controls/check_mark.png' -class Recaptcha(xbmcgui.WindowXMLDialog): - def Start(self, key, referer): - self.referer = referer - self.key = key - self.headers = {'Referer': self.referer} - api_js = httptools.downloadpage("https://www.google.com/recaptcha/api.js?hl=" + lang).data - version = scrapertools.find_single_match(api_js, 'po.src\s*=\s*\'(.*?)\';').split("/")[5] - self.url = "https://www.google.com/recaptcha/api/fallback?k=" + self.key + "&hl=" + lang + "&v=" + version + "&t=2&ff=true" - self.doModal() - # Reload - if self.result == {}: - self.result = Recaptcha("Recaptcha.xml", config.get_runtime_path()).Start(self.key, self.referer) +class Kodi: + def __init__(self, key, referer): + self.rc = ReCaptcha( + api_key=key, + site_url=referer, + user_agent=httptools.get_user_agent(), + ) - return self.result + def run(self) -> str: + result = self.rc.first_solver() + while not isinstance(result, str) and result is not False: + solution = self.run_solver(result) + if solution: + result = self.rc.send_solution(solution) + logger.debug(result) + platformtools.dialog_notification("Captcha corretto", "Verifica conclusa") + return result - def update_window(self): - data = httptools.downloadpage(self.url, headers=self.headers).data - self.message = scrapertools.find_single_match(data, - '
(.*?)(?:|
)').replace( - "", "[B]").replace("", "[/B]") - self.token = scrapertools.find_single_match(data, 'name="c" value="([^"]+)"') - self.image = "https://www.google.com/recaptcha/api2/payload?k=%s&c=%s" % (self.key, self.token) - self.result = {} - self.getControl(10020).setImage(self.image) - self.getControl(10000).setText(self.message) - self.setFocusId(10005) + def run_solver(self, solver: Solver) -> Solution: + a = { + DynamicSolver: DynamicKodi, + MultiCaptchaSolver: MultiCaptchaKodi, + } + b = a[type(solver)] + c = b("Recaptcha.xml", config.get_runtime_path()) + c.solver = solver + return c.run() + +class SolverKodi(xbmcgui.WindowXMLDialog): def __init__(self, *args, **kwargs): - self.mensaje = kwargs.get("mensaje") - self.imagen = kwargs.get("imagen") + self.goal = "" + self.closed = False + self.result = None + self.image_path = '' + self.indices = {} + logger.debug() + + def show_image(self, image, goal): + self.image_path = config.get_temp_file(str(random.randint(1, 1000)) + '.png') + filetools.write(self.image_path, image) + self.goal = goal.replace('', '[B]').replace('', '[/B]') + self.doModal() def onInit(self): - #### Kodi 18 compatibility #### - if config.get_platform(True)['num_version'] < 18: - self.setCoordinateResolution(2) - self.update_window() + logger.debug(self.image_path) + self.getControl(10020).setImage(self.image_path, False) + self.getControl(10000).setText(self.goal) + self.setFocusId(10005) + for x in range(self.num_columns): + for y in range(self.num_rows): + self.addControl(xbmcgui.ControlRadioButton(int(tiles_pos[0] + x*grid_width/self.num_rows), int(tiles_pos[1] + y*grid_width/self.num_columns), + int(grid_width/self.num_rows), int(grid_width/self.num_columns), '', tiles_texture_focus, tiles_texture_focus, + focusTexture=tiles_texture_checked, noFocusTexture=tiles_texture_checked)) + +class MultiCaptchaKodi(SolverKodi): + """ + multicaptcha challenges present you with one large image split into a grid of tiles and ask you to select the tiles that contain a given object. + Occasionally, the image will not contain the object, but rather something that looks similar. + It is possible to select no tiles in this case, but reCAPTCHA may have been fooled by the similar-looking object and would reject a selection of no tiles. + """ + def run(self) -> Solution: + result = self.solver.first_challenge() + while not isinstance(result, Solution): + if not isinstance(result, ImageGridChallenge): + raise TypeError("Unexpected type: {}".format(type(result))) + indices = self.handle_challenge(result) + result = self.solver.select_indices(indices) + return result + + def handle_challenge(self, challenge: ImageGridChallenge): + goal = challenge.goal.plain + self.num_rows = challenge.dimensions.rows + self.num_columns = challenge.dimensions.columns + + num_tiles = challenge.dimensions.count + image = challenge.image + self.show_image(image, goal) + if self.closed: + return False + return self.result + def onClick(self, control): if control == 10003: - self.result = None + self.closed = True self.close() elif control == 10004: - self.result = {} + self.result = None self.close() elif control == 10002: - self.result = [int(k) for k in range(9) if self.result.get(k, False)] - post = { - "c": self.token, - "response": self.result - } - - data = httptools.downloadpage(self.url, post=post, headers=self.headers).data - from platformcode import logger - logger.debug(data) - self.result = scrapertools.find_single_match(data, '
.*?>([^<]+)<') - if self.result: - platformtools.dialog_notification("Captcha corretto", "Verifica conclusa") - self.close() - else: - self.result = {} - self.close() + self.result = [int(k) for k in range(9) if self.indices.get(k, False)] + self.close() else: - self.result[control - 10005] = not self.result.get(control - 10005, False) + index = control - 10005 + self.indices[control - 10005] = not self.indices.get(index, False) + + +class DynamicKodi(SolverKodi): + """ + dynamic challenges present you with a grid of different images and ask you to select the images that match the given description. + Each time you click an image, a new one takes its place. Usually, three images from the initial set match the description, + and at least one of the replacement images does as well. + """ + def run(self): + challenge = self.solver.get_challenge() + image = challenge.image + goal = challenge.goal.raw + self.num_rows = challenge.dimensions.rows + self.num_columns = challenge.dimensions.columns + num_tiles = challenge.dimensions.count + + self.show_image(image, goal) + if self.closed: + return False + return self.result + + def changeTile(self, path, index, delay): + from core.support import dbg + dbg() + time.sleep(delay) + tile = self.getControl(10005 + index) + self.addControl(xbmcgui.ControlImage(tile.getX(), tile.getY(), tile.getWidth(), tile.getHeigh(), path)) + + def onClick(self, control): + if control == 10003: + self.closed = True + self.close() + + elif control == 10004: + self.result = None + self.close() + + elif control == 10002: + self.result = self.solver.finish() + self.close() + else: + index = control - 10005 + tile = self.solver.select_tile(index) + path = config.get_temp_file(str(random.randint(1, 1000)) + '.png') + filetools.write(path, tile.image) + Thread(target=self.changeTile, args=(path, index, tile.delay)).start() diff --git a/resources/skins/Default/720p/Recaptcha.xml b/resources/skins/Default/720p/Recaptcha.xml index e25be3f5..acfe4d25 100644 --- a/resources/skins/Default/720p/Recaptcha.xml +++ b/resources/skins/Default/720p/Recaptcha.xml @@ -92,132 +92,6 @@ 450 450 - - 90 - 75 - 150 - 150 - white.png - white.png - Controls/check_mark.png - Controls/check_mark.png - 10002 - 10008 - 10007 - 10006 - - - 90 - 225 - 150 - 150 - white.png - white.png - Controls/check_mark.png - Controls/check_mark.png - 10003 - 10009 - 10005 - 10007 - - - 90 - 375 - 150 - 150 - white.png - white.png - Controls/check_mark.png - Controls/check_mark.png - 10004 - 10010 - 10006 - 10005 - - - 240 - 75 - 150 - 150 - white.png - white.png - Controls/check_mark.png - Controls/check_mark.png - 10005 - 10011 - 10010 - 10009 - - - 240 - 225 - 150 - 150 - white.png - white.png - Controls/check_mark.png - Controls/check_mark.png - 10006 - 10012 - 10008 - 10010 - - - 240 - 375 - 150 - 150 - white.png - white.png - Controls/check_mark.png - Controls/check_mark.png - 10007 - 10013 - 10009 - 10008 - - - 390 - 75 - 150 - 150 - white.png - white.png - Controls/check_mark.png - Controls/check_mark.png - 10008 - 10002 - 10013 - 10012 - - - 390 - 225 - 150 - 150 - white.png - white.png - Controls/check_mark.png - Controls/check_mark.png - 10009 - 10003 - 10011 - 10013 - - - 390 - 375 - 150 - 150 - white.png - white.png - Controls/check_mark.png - Controls/check_mark.png - 10010 - 10004 - 10012 - 10011 - - + \ No newline at end of file diff --git a/servers/maxstream.json b/servers/maxstream.json index 30ed5e50..a42d9381 100644 --- a/servers/maxstream.json +++ b/servers/maxstream.json @@ -4,12 +4,8 @@ "ignore_urls": [], "patterns": [ { - "pattern": "(https?://maxstream.video/uprot/[a-zA-Z0-9]+)", + "pattern": "(https?://maxstream.video/(?:[^/]+/)?([a-zA-Z0-9]+))", "url": "\\1" - }, - { - "pattern": "https?://maxstream.video/(?:e/|embed-|cast/)?([a-z0-9]+)", - "url": "http://maxstream.video/\\1" } ] }, diff --git a/servers/maxstream.py b/servers/maxstream.py index 3f939d7c..b02b73f2 100644 --- a/servers/maxstream.py +++ b/servers/maxstream.py @@ -9,22 +9,15 @@ import requests from core import httptools, scrapertools, support from lib import jsunpack from platformcode import logger, config, platformtools -if sys.version_info[0] >= 3: - import urllib.parse as urlparse -else: - import urlparse def test_video_exists(page_url): logger.debug("(page_url='%s')" % page_url) - global data - if 'uprot/' in page_url: - id = httptools.downloadpage(page_url, follow_redirects=False, cloudscraper=True).headers.get('location').split('/')[-1] - else: - id = page_url.split('/')[-1] - page_url = requests.head('http://lozioangie.altervista.org/max_anticaptcha.php?id=' + id).headers.get('location') - data = httptools.downloadpage(page_url, cloudscraper=True).data + global data, new_url + new_url = httptools.downloadpage(page_url, follow_redirects=False, cloudscraper=True).headers.get('location', page_url) + # page_url = requests.head('http://lozioangie.altervista.org/max_anticaptcha.php?id=' + id).headers.get('location') + data = httptools.downloadpage(new_url, cloudscraper=True).data if scrapertools.find_single_match(data, '(?]*>file was deleted'): return False, config.get_localized_string(70449) % "MaxStream" @@ -35,14 +28,14 @@ def test_video_exists(page_url): def get_video_url(page_url, premium=False, user="", password="", video_password=""): logger.debug("url=" + page_url) video_urls = [] - global data - if 'captcha' in data: - httptools.set_cookies(requests.get('http://lozioangie.altervista.org/maxcookie.php').json()) - data = httptools.downloadpage(page_url, cloudscraper=True).data + global data, new_url + # if 'captcha' in data: + # httptools.set_cookies(requests.get('http://lozioangie.altervista.org/maxcookie.php').json()) + # data = httptools.downloadpage(page_url, cloudscraper=True).data - # sitekey = scrapertools.find_multiple_matches(data, """data-sitekey=['"] *([^"']+)""") - # if sitekey: sitekey = sitekey[-1] - # captcha = platformtools.show_recaptcha(sitekey, page_url) if sitekey else '' + sitekey = scrapertools.find_multiple_matches(data, """data-sitekey=['"] *([^"']+)""") + if sitekey: sitekey = sitekey[-1] + captcha = platformtools.show_recaptcha(sitekey, new_url) if sitekey else '' # # possibleParam = scrapertools.find_multiple_matches(data, # r"""|>)""") diff --git a/specials/globalsearch.py b/specials/globalsearch.py index 5c24d1ba..cd17612a 100644 --- a/specials/globalsearch.py +++ b/specials/globalsearch.py @@ -657,6 +657,7 @@ class SearchWindow(xbmcgui.WindowXML): else: item.contentSerieName = self.RESULTS.getSelectedItem().getLabel() item.folder = False + logger.debug(item) Search(item, self.thActions) if close_action: self.close()