# -*- coding: utf-8 -*- # Copyright 2019-2025 Mike Fährmann # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 as # published by the Free Software Foundation. """Extractors for https://imgbb.com/""" from .common import Extractor, Message from .. import text, util, exception from ..cache import cache class ImgbbExtractor(Extractor): """Base class for imgbb extractors""" category = "imgbb" directory_fmt = ("{category}", "{user[name]:?//}{user[id]:? (/)/}", "{album[title]} ({album[id]})") filename_fmt = "{title} ({id}).{extension}" archive_fmt = "{user[id]} {id}" cookies_domain = ".imgbb.com" cookies_names = ("PHPSESSID", "LID") root = "https://ibb.co" def items(self): self.login() for image in self.posts(): url = image["url"] text.nameext_from_url(url, image) yield Message.Directory, "", image yield Message.Url, url, image def login(self): if self.cookies_check(self.cookies_names): return username, password = self._get_auth_info() if username: return self.cookies_update(self._login_impl(username, password)) @cache(maxage=365*86400, keyarg=1) def _login_impl(self, username, password): self.log.info("Logging in as %s", username) url = "https://imgbb.com/login" page = self.request(url).text token = text.extr(page, 'name="auth_token" value="', '"') headers = { "Referer": url, } data = { "auth_token" : token, "login-subject": username, "password" : password, } response = self.request(url, method="POST", headers=headers, data=data) if not response.history: raise exception.AuthenticationError() return self.cookies def _pagination(self, page, url, params): seek, pos = text.extract(page, 'data-seek="', '"') tokn, pos = text.extract(page, 'PF.obj.config.auth_token="', '"', pos) resc, pos = text.extract(page, "CHV.obj.resource=", "};", pos) self.kwdict["user"] = util.json_loads(resc + "}").get("user") data = None while True: for obj in text.extract_iter(page, "data-object='", "'"): post = util.json_loads(text.unquote(obj)) image = post["image"] image["filename"], image["name"] = \ image["name"], image["filename"] image["id"] = post["id_encoded"] image["title"] = post["title"] image["width"] = text.parse_int(post["width"]) image["height"] = text.parse_int(post["height"]) image["size"] = text.parse_int(image["size"]) yield image if data: if not data["seekEnd"] or params["seek"] == data["seekEnd"]: return params["seek"] = data["seekEnd"] params["page"] += 1 elif not seek or 'class="pagination-next"' not in page: return else: params["action"] = "list" params["page"] = 2 params["seek"] = seek params["auth_token"] = tokn headers = { "Accept": "application/json, text/javascript, */*; q=0.01", "X-Requested-With": "XMLHttpRequest", "Origin": self.root, "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", } data = self.request_json( url, method="POST", headers=headers, data=params) page = data["html"] class ImgbbAlbumExtractor(ImgbbExtractor): """Extractor for imgbb albums""" subcategory = "album" pattern = r"(?:https?://)?ibb\.co/album/([^/?#]+)/?(?:\?([^#]+))?" example = "https://ibb.co/album/ID" def posts(self): album_id, qs = self.groups url = f"{self.root}/album/{album_id}" params = text.parse_query(qs) page = self.request(url, params=params).text extr = text.extract_from(page) self.kwdict["album"] = album = { "url": extr( 'property="og:url" content="', '"'), "title": text.unescape(extr( 'property="og:title" content="', '"')), "description": text.unescape(extr( 'property="og:description" content="', '"')), "id": extr( 'data-text="album-name" href="https://ibb.co/album/', '"'), "count": text.parse_int(extr( 'data-text="image-count">', "<")), } url = self.root + "/json" params["pathname"] = "/album/" + album["id"] return self._pagination(page, url, params) class ImgbbImageExtractor(ImgbbExtractor): subcategory = "image" pattern = r"(?:https?://)?ibb\.co/([^/?#]+)" example = "https://ibb.co/ID" def posts(self): url = f"{self.root}/{self.groups[0]}" page = self.request(url).text extr = text.extract_from(page) image = { "id" : extr('property="og:url" content="https://ibb.co/', '"'), "title" : text.unescape(extr( '"og:title" content="', ' hosted at ImgBB"')), "url" : extr('"og:image" content="', '"'), "width" : text.parse_int(extr('"og:image:width" content="', '"')), "height": text.parse_int(extr('"og:image:height" content="', '"')), "album" : extr("Added to "), "date" : self.parse_datetime_iso(extr('")[2]), } else: image["album"] = None return (image,) class ImgbbUserExtractor(ImgbbExtractor): """Extractor for imgbb user profiles""" subcategory = "user" directory_fmt = ("{category}", "{user[name]} ({user[id]})") pattern = r"(?:https?://)?([\w-]+)\.imgbb\.com/?(?:\?([^#]+))?" example = "https://USER.imgbb.com" def posts(self): user, qs = self.groups url = f"https://{user}.imgbb.com/" params = text.parse_query(qs) response = self.request(url, params=params, allow_redirects=False) if response.status_code < 300: params["pathname"] = "/" return self._pagination(response.text, url + "json", params) if response.status_code == 301: raise exception.NotFoundError("user") redirect = "HTTP redirect to " + response.headers.get("Location", "") if response.status_code == 302: raise exception.AuthRequired( ("username & password", "authenticated cookies"), "profile", redirect) raise exception.AbortExtraction(redirect)