Skip to content

Instantly share code, notes, and snippets.

@eslof
Created December 12, 2021 16:19
Show Gist options
  • Save eslof/1affcaa7d413eada76725033c42cc596 to your computer and use it in GitHub Desktop.
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
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