- Reset master to upstream/main (16,697 commits) - Overlay 2,271 local-only files (skills, tools, workspace, configs, apps) - Restore IDENTITY.md and USER.md templates - Build verified, gateway running, Discord working Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
210 lines
7.3 KiB
Python
210 lines
7.3 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
DBA.dk bud/besked-tool — sender besked til sælger via Playwright.
|
|
Kræver at du er logget ind (gemmer session i ~/.openclaw/dba-session.json).
|
|
|
|
Brug:
|
|
python3 dba-bid.py <item-url-eller-id> "<besked>"
|
|
python3 dba-bid.py --login (åbn browser og log ind manuelt)
|
|
python3 dba-bid.py --check-login (test om session er aktiv)
|
|
|
|
Eksempel:
|
|
python3 dba-bid.py https://www.dba.dk/recommerce/forsale/item/16276129 \
|
|
"Hej, vil du tage 5000 kr? Kan afhente i weekenden."
|
|
"""
|
|
import sys, os, json, time, argparse
|
|
|
|
SESSION_FILE = os.path.expanduser('~/.openclaw/dba-session.json')
|
|
DBA_BASE = 'https://www.dba.dk'
|
|
|
|
|
|
def get_item_url(item_ref: str) -> str:
|
|
"""Konverterer item-id eller fuld URL til fuld URL."""
|
|
if item_ref.startswith('http'):
|
|
return item_ref
|
|
# Bare et ID
|
|
return f"{DBA_BASE}/recommerce/forsale/item/{item_ref}"
|
|
|
|
|
|
def login_interactive():
|
|
"""Åbn browser så brugeren kan logge ind, gem session bagefter."""
|
|
from playwright.sync_api import sync_playwright
|
|
|
|
print("Åbner DBA.dk — log ind og tryk Enter her bagefter...")
|
|
with sync_playwright() as p:
|
|
browser = p.chromium.launch(headless=False, slow_mo=100)
|
|
ctx = browser.new_context()
|
|
page = ctx.new_page()
|
|
page.goto(f"{DBA_BASE}/login/", wait_until='networkidle')
|
|
|
|
input("Tryk Enter efter du er logget ind...")
|
|
|
|
# Gem cookies
|
|
cookies = ctx.cookies()
|
|
with open(SESSION_FILE, 'w') as f:
|
|
json.dump(cookies, f, indent=2)
|
|
print(f"Session gemt ({len(cookies)} cookies) → {SESSION_FILE}")
|
|
browser.close()
|
|
|
|
|
|
def check_login() -> bool:
|
|
"""Tjek om session er aktiv."""
|
|
from playwright.sync_api import sync_playwright
|
|
|
|
if not os.path.exists(SESSION_FILE):
|
|
return False
|
|
|
|
with open(SESSION_FILE) as f:
|
|
cookies = json.load(f)
|
|
|
|
# Tjek om auth-cookies er der og ikke tomme
|
|
auth = [c for c in cookies if c.get('name') in ('id-jwt', 'schacc-session', 'login-webapp_jsession')]
|
|
if not auth or all(not c.get('value') for c in auth):
|
|
return False
|
|
|
|
with sync_playwright() as p:
|
|
browser = p.chromium.launch(headless=True)
|
|
ctx = browser.new_context()
|
|
ctx.add_cookies(cookies)
|
|
page = ctx.new_page()
|
|
page.goto(f"{DBA_BASE}/recommerce/forsale/search?q=test", wait_until='domcontentloaded', timeout=15000)
|
|
# Tjek om vi er logget ind (profilikon/knap)
|
|
logged_in = page.locator('[data-testid="profile-button"], [aria-label*="profil"], .user-avatar').count() > 0
|
|
browser.close()
|
|
return logged_in
|
|
|
|
|
|
def send_message(item_ref: str, message: str, dry_run: bool = False) -> dict:
|
|
"""Send besked til sælger på DBA."""
|
|
from playwright.sync_api import sync_playwright
|
|
|
|
item_url = get_item_url(item_ref)
|
|
|
|
if not os.path.exists(SESSION_FILE):
|
|
return {'success': False, 'error': 'Ikke logget ind. Kør: python3 dba-bid.py --login'}
|
|
|
|
with open(SESSION_FILE) as f:
|
|
cookies = json.load(f)
|
|
|
|
with sync_playwright() as p:
|
|
browser = p.chromium.launch(headless=True)
|
|
ctx = browser.new_context(
|
|
user_agent='Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/131 Safari/537.36'
|
|
)
|
|
ctx.add_cookies(cookies)
|
|
page = ctx.new_page()
|
|
|
|
try:
|
|
page.goto(item_url, wait_until='domcontentloaded', timeout=20000)
|
|
time.sleep(2)
|
|
|
|
# Find "Skriv til sælger" knap
|
|
contact_btn = page.locator(
|
|
'button:has-text("Skriv til sælger"), '
|
|
'button:has-text("Kontakt sælger"), '
|
|
'a:has-text("Skriv til sælger"), '
|
|
'[data-testid="contact-seller-button"]'
|
|
).first
|
|
|
|
if contact_btn.count() == 0:
|
|
# Tag screenshot til debug
|
|
page.screenshot(path='/tmp/dba-debug.png')
|
|
browser.close()
|
|
return {'success': False, 'error': 'Kunne ikke finde "Skriv til sælger" knap. Screenshot: /tmp/dba-debug.png'}
|
|
|
|
if dry_run:
|
|
browser.close()
|
|
return {'success': True, 'dry_run': True, 'message': message, 'url': item_url}
|
|
|
|
contact_btn.click()
|
|
time.sleep(2)
|
|
|
|
# Find tekstfelt og skriv besked
|
|
textarea = page.locator(
|
|
'textarea[name*="message"], textarea[placeholder*="besked"], '
|
|
'textarea[placeholder*="Message"], #message, [data-testid="message-input"]'
|
|
).first
|
|
|
|
if textarea.count() == 0:
|
|
page.screenshot(path='/tmp/dba-debug.png')
|
|
browser.close()
|
|
return {'success': False, 'error': 'Tekstfelt ikke fundet. Screenshot: /tmp/dba-debug.png'}
|
|
|
|
textarea.fill(message)
|
|
time.sleep(1)
|
|
|
|
# Find Send-knap
|
|
send_btn = page.locator(
|
|
'button[type="submit"]:has-text("Send"), '
|
|
'button:has-text("Send besked"), '
|
|
'[data-testid="send-message-button"]'
|
|
).first
|
|
|
|
if send_btn.count() == 0:
|
|
page.screenshot(path='/tmp/dba-debug.png')
|
|
browser.close()
|
|
return {'success': False, 'error': 'Send-knap ikke fundet. Screenshot: /tmp/dba-debug.png'}
|
|
|
|
send_btn.click()
|
|
time.sleep(2)
|
|
|
|
# Bekræft
|
|
page.screenshot(path='/tmp/dba-sent.png')
|
|
browser.close()
|
|
return {'success': True, 'message': message, 'url': item_url, 'screenshot': '/tmp/dba-sent.png'}
|
|
|
|
except Exception as e:
|
|
try:
|
|
page.screenshot(path='/tmp/dba-error.png')
|
|
except Exception:
|
|
pass
|
|
browser.close()
|
|
return {'success': False, 'error': str(e)}
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description='DBA.dk bud/besked tool')
|
|
parser.add_argument('item', nargs='?', help='Item URL eller ID')
|
|
parser.add_argument('message', nargs='?', help='Besked til sælger')
|
|
parser.add_argument('--login', action='store_true', help='Log ind interaktivt og gem session')
|
|
parser.add_argument('--check-login', action='store_true', help='Tjek om session er aktiv')
|
|
parser.add_argument('--dry-run', action='store_true', help='Test uden at sende')
|
|
parser.add_argument('--json', action='store_true', help='Output som JSON')
|
|
args = parser.parse_args()
|
|
|
|
if args.login:
|
|
login_interactive()
|
|
return
|
|
|
|
if args.check_login:
|
|
ok = check_login()
|
|
if args.json:
|
|
print(json.dumps({'logged_in': ok}))
|
|
else:
|
|
print('Logget ind ✓' if ok else 'Ikke logget ind ✗')
|
|
return
|
|
|
|
if not args.item or not args.message:
|
|
parser.print_help()
|
|
sys.exit(1)
|
|
|
|
result = send_message(args.item, args.message, dry_run=args.dry_run)
|
|
|
|
if args.json:
|
|
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
else:
|
|
if result['success']:
|
|
if result.get('dry_run'):
|
|
print(f"[DRY-RUN] Ville sende: {result['message']}")
|
|
print(f"Til: {result['url']}")
|
|
else:
|
|
print(f"Besked sendt til {result['url']}")
|
|
print(f"Screenshot: {result.get('screenshot','')}")
|
|
else:
|
|
print(f"Fejl: {result['error']}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|