initial commit
This commit is contained in:
+188
@@ -0,0 +1,188 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import dataclasses
|
||||
import datetime
|
||||
import json
|
||||
import typing
|
||||
from pathlib import Path
|
||||
from pprint import pprint
|
||||
from urllib.parse import urljoin
|
||||
|
||||
import bs4
|
||||
import httpx
|
||||
|
||||
|
||||
@dataclasses.dataclass()
|
||||
class SearchResult:
|
||||
posted_at: datetime.datetime
|
||||
title: str
|
||||
size_mb: float
|
||||
url: str
|
||||
|
||||
|
||||
class Auth(typing.Protocol):
|
||||
def authenticate(self) -> dict[str, str]:
|
||||
...
|
||||
|
||||
|
||||
@dataclasses.dataclass()
|
||||
class CookieFileAuth:
|
||||
file_path: Path
|
||||
|
||||
def authenticate(self) -> dict[str, str]:
|
||||
with self.file_path.open() as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
@dataclasses.dataclass()
|
||||
class CredentialAuth:
|
||||
username: str
|
||||
password: str
|
||||
|
||||
def authenticate(self) -> dict[str, str]:
|
||||
client = httpx.Client(base_url="https://pornolab.net/")
|
||||
res = client.post(
|
||||
"/forum/login.php",
|
||||
data={
|
||||
"login_username": self.username,
|
||||
"login_password": self.password,
|
||||
"login": "Вход",
|
||||
},
|
||||
follow_redirects=True,
|
||||
)
|
||||
res.raise_for_status()
|
||||
soup = bs4.BeautifulSoup(res.text, "html.parser")
|
||||
username_el = soup.select_one(".topmenu .med")
|
||||
if self.username not in username_el.text.strip():
|
||||
raise ValueError("failed to login")
|
||||
return dict(client.cookies)
|
||||
|
||||
|
||||
@dataclasses.dataclass()
|
||||
class PersistedCookieAuth:
|
||||
cookie_path: Path
|
||||
auth: Auth
|
||||
|
||||
def authenticate(self) -> dict[str, str]:
|
||||
cookie_auth = CookieFileAuth(self.cookie_path)
|
||||
try:
|
||||
return cookie_auth.authenticate()
|
||||
except (ValueError, FileNotFoundError):
|
||||
pass
|
||||
|
||||
cookies = self.auth.authenticate()
|
||||
with self.cookie_path.open("w") as f:
|
||||
json.dump(cookies, f)
|
||||
return cookies
|
||||
|
||||
|
||||
class PlabAuthMiddleware(httpx.Auth):
|
||||
def auth_flow(self, request: httpx.Request) -> typing.Generator[httpx.Request, httpx.Response, None]:
|
||||
# if no cookies in req
|
||||
# fill cookie
|
||||
# check response
|
||||
# reauth
|
||||
# fill cookie
|
||||
|
||||
res = yield request
|
||||
return super().auth_flow(request)
|
||||
|
||||
|
||||
class Plab:
|
||||
def __init__(self, auth: Auth):
|
||||
self.auth = auth
|
||||
self.client = httpx.Client(base_url="https://pornolab.net/")
|
||||
|
||||
def search(self, term: str) -> list[SearchResult]:
|
||||
self._ensure_session()
|
||||
res = self.client.post(
|
||||
"/forum/tracker.php",
|
||||
data={
|
||||
"max": "1",
|
||||
"to": "1",
|
||||
"nm": f'{term}',
|
||||
},
|
||||
follow_redirects=True,
|
||||
)
|
||||
res.raise_for_status()
|
||||
|
||||
if 'Введите ваше имя и пароль' in res.text:
|
||||
raise
|
||||
|
||||
soup = bs4.BeautifulSoup(res.text, "html.parser")
|
||||
rows = soup.select("#tor-tbl tbody tr")
|
||||
items = []
|
||||
for row in rows:
|
||||
title_link = row.select_one(f'[href*="viewtopic.php?t="]')
|
||||
if not title_link:
|
||||
continue
|
||||
size_mb = (
|
||||
int(row.select_one('[href*="dl.php?t="]').find_previous_sibling(name='u').text.strip()) / 1_048_576
|
||||
)
|
||||
date = datetime.datetime.fromtimestamp(int(row.select_one('[title="Добавлен"] u').text.strip()))
|
||||
items.append(
|
||||
SearchResult(
|
||||
url=urljoin("https://pornolab.net/forum/", title_link["href"]),
|
||||
title=title_link.text.strip(),
|
||||
size_mb=size_mb,
|
||||
posted_at=date,
|
||||
)
|
||||
)
|
||||
return items
|
||||
|
||||
def _ensure_session(self):
|
||||
if self.client.cookies:
|
||||
return
|
||||
self.client.cookies = self.auth.authenticate()
|
||||
|
||||
|
||||
def dump_for_alfred(items: list[SearchResult]):
|
||||
alfreds = []
|
||||
for it in items:
|
||||
alfreds.append(
|
||||
{
|
||||
"title": it.title,
|
||||
"subtitle": f"{it.size_mb:.1f}MB -- {it.posted_at.isoformat()}",
|
||||
"arg": it.url,
|
||||
"text": {
|
||||
"copy": it.url,
|
||||
"largetype": f"{it.title}\nPosted at: {it.posted_at.isoformat()}\nSize: {it.size_mb:.1f}MB",
|
||||
},
|
||||
"match": f"{it.title} {it.posted_at.isoformat()}",
|
||||
}
|
||||
)
|
||||
print(json.dumps({"items": alfreds}, indent=2))
|
||||
|
||||
|
||||
def parse_args():
|
||||
arger = argparse.ArgumentParser()
|
||||
arger.add_argument('term', help='Term to search for')
|
||||
arger.add_argument('--alfred', action='store_true', help='Dump for Alfred')
|
||||
return arger.parse_args()
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
|
||||
p = Plab(
|
||||
auth=PersistedCookieAuth(
|
||||
cookie_path=Path(__file__).parent / "plab.json",
|
||||
auth=CredentialAuth(
|
||||
username="zzzp",
|
||||
password="3c45HzLMgSB7M5hJ",
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
results = p.search(args.term)
|
||||
|
||||
if args.alfred:
|
||||
dump_for_alfred(results)
|
||||
else:
|
||||
for it in results:
|
||||
pprint(it)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user