Created
December 12, 2021 16:19
-
-
Save eslof/1affcaa7d413eada76725033c42cc596 to your computer and use it in GitHub Desktop.
Script I made for my mother to easily renew her oldest listings on bokborsen.se. Usage: python script.py email password
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import sys | |
import time | |
from requests import Session | |
import requests | |
import urllib.parse | |
from bs4 import BeautifulSoup | |
def get_oldest(s: requests.Session, request_number: int, book_count: int, timestamp: int) -> dict: | |
s.headers.update({"Referer": BookConfig.PAGE_URL}) | |
request_url = BookConfig.JSON_URL + urllib.parse.urlencode( | |
BookConfig.request_template(request_number, book_count, timestamp) | |
) | |
try: | |
response = s.get(request_url) | |
except requests.RequestException: | |
print("Connectivity error for oldest books.") | |
return quit() | |
if response.status_code not in (304, 200): | |
print("Unable to retrieve oldest books.") | |
return quit() | |
try: | |
json_data = response.json() | |
except (ValueError, IndexError, KeyError): | |
print("JSON parsing/format error for oldest books.") | |
return quit() | |
if "data" not in json_data or len(json_data["data"]) == 0: | |
print("No data found in oldest books request.") | |
return quit() | |
return json_data["data"] | |
def do_bump(s: requests.Session, ids: list) -> None: | |
s.headers.update({"Referer": BookConfig.PAGE_URL}) | |
try: | |
response = s.post(BumpConfig.ACTION_URL, get_bump_request(ids)) | |
except requests.RequestException: | |
print("Connectivity error for bump request.") | |
return quit() | |
if response.status_code not in (304, 200): | |
print("Unable to bump book.") | |
return quit() | |
def get_bump_request(ids: list) -> dict: | |
return { | |
"manage_product_ids": ",".join(ids), | |
"product_action[]": "bump", | |
"inc": "", | |
"dec": "" | |
} | |
def do_login(s: requests.session) -> None: | |
if "Referer" in s.headers: | |
del s.headers["Referer"] | |
try: | |
get_response = s.get(LoginConfig.PAGE_URL) | |
except requests.RequestException: | |
print("Connectivity error for login.") | |
return quit() | |
if get_response.status_code not in (304, 200): | |
print("Unable to get login page.") | |
return quit() | |
s.headers.update({"Referer": LoginConfig.PAGE_URL}) | |
bs_content = BeautifulSoup(get_response.content, "html.parser") | |
token = bs_content.find(LoginConfig.FORM_NAME, {"name": LoginConfig.TOKEN_NAME})["value"] | |
if not token: | |
print("Parsing failed for login page.") | |
try: | |
post_response = s.post(LoginConfig.PAGE_URL, get_login_request(token)) | |
except requests.RequestException: | |
print("Connectivity error for login post.") | |
return quit() | |
if post_response.status_code not in (304, 200): | |
print("Unable to successfully login post.") | |
return quit() | |
def get_login_request(token: str) -> dict: | |
request = LoginConfig.request_template(token) | |
request["email"] = sys.argv[1] | |
request["password"] = sys.argv[2] | |
return request | |
class BookConfig: | |
PAGE_URL = "https://www.bokborsen.se/user/product" | |
JSON_URL = PAGE_URL + "/list?" | |
@staticmethod | |
def request_template(request_number: int, count: int, timestamp: int) -> dict: | |
return { | |
"draw": request_number, | |
"columns[0][data]": "", | |
"columns[0][name]": "format", | |
"columns[0][searchable]": "true", | |
"columns[0][orderable]": "false", | |
"columns[0][search][value]": "", | |
"columns[0][search][regex]": "false", | |
"columns[1][data]": "external_sku", | |
"columns[1][name]": "", | |
"columns[1][searchable]": "true", | |
"columns[1][orderable]": "true", | |
"columns[1][search][value]": "", | |
"columns[1][search][regex]": "false", | |
"columns[2][data]": "created_at", | |
"columns[2][name]": "", | |
"columns[2][searchable]": "true", | |
"columns[2][orderable]": "true", | |
"columns[2][search][value]": "", | |
"columns[2][search][regex]": "false", | |
"columns[3][data]": "", | |
"columns[3][name]": "search", | |
"columns[3][searchable]": "true", | |
"columns[3][orderable]": "false", | |
"columns[3][search][value]": "", | |
"columns[3][search][regex]": "false", | |
"columns[4][data]": "usergenre_id", | |
"columns[4][name]": "", | |
"columns[4][searchable]": "true", | |
"columns[4][orderable]": "false", | |
"columns[4][search][value]": "", | |
"columns[4][search][regex]": "false", | |
"columns[5][data]": "price", | |
"columns[5][name]": "", | |
"columns[5][searchable]": "true", | |
"columns[5][orderable]": "true", | |
"columns[5][search][value]": "", | |
"columns[5][search][regex]": "false", | |
"columns[6][data]": "free_shipping", | |
"columns[6][name]": "", | |
"columns[6][searchable]": "true", | |
"columns[6][orderable]": "false", | |
"columns[6][search][value]": "", | |
"columns[6][search][regex]": "false", | |
"columns[7][data]": "status", | |
"columns[7][name]": "", | |
"columns[7][searchable]": "true", | |
"columns[7][orderable]": "true", | |
"columns[7][search][value]": "enabled", | |
"columns[7][search][regex]": "false", | |
"columns[8][data]": "id", | |
"columns[8][name]": "", | |
"columns[8][searchable]": "true", | |
"columns[8][orderable]": "false", | |
"columns[8][search][value]": "", | |
"columns[8][search][regex]": "false", | |
"columns[9][data]": "id", | |
"columns[9][name]": "", | |
"columns[9][searchable]": "true", | |
"columns[9][orderable]": "true", | |
"columns[9][search][value]": "", | |
"columns[9][search][regex]": "false", | |
"order[0][column]": "created_at", | |
"order[0][dir]": "asc", | |
"start": 0, | |
"length": count, | |
"search[value]": "", | |
"search[regex]": "false", | |
"_": timestamp | |
} | |
class BumpConfig: | |
ACTION_URL = "https://www.bokborsen.se/user/product/action" | |
@staticmethod | |
def request_template(ids: list) -> dict: | |
return { | |
"manage_product_ids": ",".join(ids), | |
"product_action[]": "bump", | |
"inc": "", | |
"dec": "" | |
} | |
class LoginConfig: | |
PAGE_URL = "https://www.bokborsen.se/auth/login" | |
FORM_NAME = "input" | |
TOKEN_NAME = "_token" | |
@staticmethod | |
def request_template(token: str): | |
return {"redirect": "", "email": "", "password": "", LoginConfig.TOKEN_NAME: token} | |
class MainConfig: | |
REQUEST_HEADERS = { | |
"Accept": "text/html," | |
"application/xhtml+xml," | |
"application/xml;q=0.9," | |
"image/avif,image/webp," | |
"image/apng," | |
"*/*;q=0.8," | |
"application/signed-exchange;v=b3;q=0.9", | |
"Accept-Encoding": "gzip, deflate, br", | |
"Accept-Language": "en-US,en;q=0.9,sv;q=0.8", | |
"Connection": "keep-alive", | |
"Host": "www.bokborsen.se", | |
"sec-ch-ua": "\"Google Chrome\";v=\"87\"," | |
" \" Not;A Brand\";v=\"99\"," | |
" \"Chromium\";v=\"87\"", | |
"sec-ch-ua-mobile": "?1", | |
"Sec-Fetch-Dest": "document", | |
"Sec-Fetch-Mode": "navigate", | |
"Sec-Fetch-Site": "none", | |
"Sec-Fetch-User": "?1", | |
"Upgrade-Insecure-Requests": "1", | |
"User-Agent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N)" | |
" AppleWebKit/537.36 (KHTML, like Gecko)" | |
" Chrome/87.0.4280.141 Mobile Safari/537.36 " | |
} | |
def main(): | |
with Session() as s: | |
s.headers = MainConfig.REQUEST_HEADERS | |
do_login(s) | |
s.get(BookConfig.PAGE_URL) # doesn't have to be here, just keeping up appearances | |
bump_count = 0 | |
while bump_count <= 0 or bump_count > 25: | |
print("Hur många böcker att förnya? (1-25)") | |
try: | |
bump_count = int(input()) | |
except ValueError: | |
continue | |
book_count = 10 if bump_count <= 10 else 25 | |
target_books = get_oldest(s, 1, book_count, int(time.time())) | |
book_ids = [] | |
for i in range(bump_count): | |
book_ids.append(str(target_books[i]["id"])) | |
do_bump(s, book_ids) | |
if __name__ == "__main__": | |
if len(sys.argv) != 3: | |
print("Usage: python script.py email password") | |
quit() | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment