本文へ移動
cccskills
無料GitHub で公開

12306-android-adb

12306-specific knowledge for booking train tickets via the Android app. Covers UC WebView virtual list behavior, proven booking flow, and common pitfalls. Uses appium-android-adb bridge OR direct uiautomator2 for all interaction.

インストール方法を見る

含まれるファイル(1)

  • SKILL.md8.4 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

12306 Train Booking (Android)

12306-specific knowledge, workflows, and pitfalls. The app uses Alipay Nebula UC WebView for the train list and booking pages, which has unique automation challenges.

🚨 Before Any 12306 Action

Ensure the app is running and on the correct page:

# Start Appium bridge (if using bridge_daemon.py approach):
bash ~/.openclaw/workspace/skills/appium-android-adb/start_bridge.sh

# Or use uiautomator2 directly (more reliable for WebView):
python3 -c "import uiautomator2 as u2; d = u2.connect(); d.app_start('com.MobileTicket')"

⚠️ Critical: UC WebView Virtual List Behavior

The train list uses a virtual list inside the UC WebView. This has extreme implications:

What DOES work

MethodTargetReliability
uiautomator2 text('预订').click()"预订" book buttons✅ Always works (proper persistent bounds)
uiautomator2 text('查询车票').click()Native query button✅ Always works
uiautomator2 text('选择乘车人').click()Native passenger selector✅ Always works
uiautomator2.click(x, y) on native viewsDate tabs, bottom buttons✅ Always works
Screenshot + OCR + ADB tapAny visible element✅ Works with good OCR
Appium element.click() on "预订" buttons"预订" with proper bounds✅ Works

What DOES NOT work

MethodTargetWhy
Clicking collapsed train name textTrain entry at y=407 or y=2255 (h=6)Virtual list collapses ALL non-current items
ADB input tapAny UC WebView contentUC WebView filters synthetic touch events
W3C Actions swipeUC WebView scrollTouch events filtered
mobile: scrollGestureUC WebView elementReturns False consistently
d.textContains('G 7 0 0 4').click()Train name in collapsed zoneMultiple trains share collapsed bounds
tap_bounds on collapsed elementTrain name at y=2255 with h=6Event goes to WebView's virtual position, not visual

Key Insight: "预订" Buttons Have Proper Bounds

The ONLY reliable click targets in the UC WebView train list are the "预订" action buttons. Unlike the text content (train name, times, seat info) which gets collapsed by the virtual list, the "预订" button is:

  • Always rendered with proper bounds (h=87px)
  • Clickable via accessibility service
  • Positioned consistently ~195px below each train card's top
  • Unique per train card (find the one nearest your target train's y-position)

Booking Flow

Step 1: Home Page — Search Trains

dump the page → check for "查询车票" button
If on MainActivity:
  → tap "查询车票" (native button, always works)
  → wait for train list to load (H5Activity appears)
If on H5Activity (already on train list):
  → proceed to Step 2

Step 2: Find Target Train's "预订" Button

DO NOT try to click the train name or text content. Instead:

  1. Scroll the list until you see trains near your target departure time:

    d.swipe_ext('up', scale=0.3)   # scroll forward
    d.swipe_ext('down', scale=0.3)  # scroll backward
    
  2. Check for G7004 in the DOM (even collapsed):

    xml = d.dump_hierarchy()
    if 'G 7 0 0 4' in xml:
        # G7004 is in the list — look for its "预订" button
        pass
    
  3. Find the "预订" button associated with your train. "预订" buttons appear at regular y-intervals (~315px apart). The one closest to your target train's y-position belongs to that train:

    # Get all "预订" buttons with positions
    buttons = d(text='预订')
    for btn in buttons:
        pos = btn.info['bounds']
        y_center = (pos['top'] + pos['bottom']) // 2
        print(f"预订 at y={y_center}")
    
    # G7004 at y=1793 → the "预订" at y=1988 is its book button
    
  4. Click "预订":

    d(text='预订').click()          # clicks first one
    # OR click specific one by position:
    for btn in d(text='预订'):
        if 1900 < btn.info['bounds']['top'] < 2100:
            btn.click()
            break
    

Step 2b: OCR-Based Fallback (if DOM is unreliable)

If the WebView's DOM is too collapsed to find the right "预订":

import subprocess, pytesseract
from PIL import Image

# Take screenshot
subprocess.run(['adb', 'shell', 'screencap', '-p', '/sdcard/screen.png'], timeout=10)
subprocess.run(['adb', 'pull', '/sdcard/screen.png', '/tmp/screen.png'], timeout=10)

# OCR to find departure time
data = pytesseract.image_to_data(Image.open('/tmp/screen.png'),
    lang='chi_sim+eng', output_type=pytesseract.Output.DICT, config='--psm 6')

# Find "08:00" departure text on LEFT side (x < 300)
for i in range(len(data['text'])):
    if '08:00' in data['text'][i] and int(data['left'][i]) < 300:
        y = data['top'][i]
        # Tap "预订" position (~195px below departure time)
        import subprocess
        subprocess.run(['adb', 'shell', 'input', 'tap', '939', str(y + 195)])
        break

Step 3: Quiet Carriage Dialog (if appears)

After clicking "预订", a dialog may appear:

  • "选择该车次二等座可能候补到静音车厢席位,是否继续选择?" → tap "继续" or "选择"
  • "温馨提示" with quiet carriage rules → tap "选择" (bottom-right button)

Use OCR to find the confirm button position, then ADB tap it.

Step 4: Select Passenger

On order page, verify:
  → G7004 train info displayed
  → 二等座 selected (default)
  → Price ¥41 shown

If no passenger selected:
  → tap "选择乘车人" (native, works)
  → tap passenger name (e.g. "熊子慧")
  → tap "确认" (bottom button)

Step 5: Submit Order

tap "提交订单" button
  → verify: order submission confirmation
  → if "温馨提示" with "车票信息已过期" → go back, re-search
  → if "立即支付" → proceed to payment

Step 6: Payment (manual)

"立即支付" or "去支付" appears → user handles Alipay/WeChat

Train List Navigation (Scrolling)

The UC WebView's virtual list has three zones:

  • Top collapsed (y=407, h=6): Items scrolled past the top
  • Proper viewport (y=500-2200, h=60-130): Currently visible items with real bounds
  • Bottom collapsed (y=2255, h=6): Items below the viewport

Each scroll (scale=0.3) advances the list by approximately 2 train cards (~30-60 min of departures). To reach a specific train:

  1. Start from the earliest visible time
  2. Calculate approximate scrolls needed: (target_hour - current_hour) * 60 / 15 (each scroll ≈ 15 min of departures)
  3. Scroll, then check for target text in DOM (even collapsed)
  4. Once target text appears in DOM, look for its "预订" button

Decision Tree

dump / check current page:
  ├─ package != "com.MobileTicket"
  │   → app not running, start it
  ├─ ".ui.activity.MainActivity" in activity
  │   → HOME PAGE
  │   → verify stations are set → tap "查询车票"
  ├─ "H5Activity" in activity
  │   ├─ "提交订单" in text → ORDER CONFIRMATION
  │   │   → select passenger if needed → submit
  │   ├─ "预订" in text → TRAIN LIST (with "预订" buttons)
  │   │   → find target train → click its "预订" button
  │   ├─ "次列车" in text → TRAIN LIST (loading)
  │   │   → scroll until target appears → find "预订"
  │   └─ "立即支付" in text → PAYMENT
  │       → user handles payment
  └─ anything else → not 12306, investigate

Common Pitfalls

  1. NEVER click collapsed train names — they all share the same y=407 or y=2255 bounds. Click the "预订" button instead.
  2. "预订" buttons have proper bounds — they're the only reliable WebView targets.
  3. Screen may auto-lock — keep device awake: adb shell svc power stayon true
  4. Session conflicts — Appium and uiautomator2 can't run simultaneously (both need AccessibilityService).
  5. Page may time out — if no action for ~5 min, the list might need re-querying.
  6. Date tabs are native — tapping "05月31日 周日" at bounds [189,257][312,383] always works.
  7. Train numbers have spaces — search for G 7 0 0 4 not G7004.

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

0protocol

無料

Agents can sign plugins, rotate credentials without losing identity, and publicly attest to behavior.

日本語の概要は準備中です。原文の説明を表示しています。

Lord1Egypt/awesome-skill-forge22026年6月10日 更新

专为追求倍速增长的初建学校设计的教师选、育、用、留四环节体系。基于科德高中融合部真实管理经验,结合十部AI Agent架构,将人才管理从校长个人经验升级为可继承、可协同、可沉淀的组织能力。

日本語の概要は準備中です。原文の説明を表示しています。

Lord1Egypt/awesome-skill-forge22026年6月10日 更新

115 网盘智能管理:扫码登录、文件浏览、搜索、转存、离线下载、智能整理。适用于"登录 115"、"查看文件"、"搜索 xxx"、"转存链接"、"磁力链接"、"整理文件"等场景。

日本語の概要は準備中です。原文の説明を表示しています。

Lord1Egypt/awesome-skill-forge22026年6月10日 更新

Connect to 163/126/yeah.net (Coremail) email via IMAP, read inbox, search emails, and send emails via SMTP. Activate when user asks to check email, read mail, send email, or monitor inbox on 163/126/yeah.net accounts. Handles the Coremail-specific IMAP ID command requirement that causes "Unsafe Login" errors in standard clients.

日本語の概要は準備中です。原文の説明を表示しています。

Lord1Egypt/awesome-skill-forge22026年6月10日 更新

1password

無料

Set up and use 1Password CLI (op). Use when installing the CLI, enabling desktop app integration, signing in, and reading/injecting secrets for commands.

日本語の概要は準備中です。原文の説明を表示しています。

Lord1Egypt/awesome-skill-forge22026年6月10日 更新

Install, configure, and manage 1-SEC — an open-source, all-in-one cybersecurity platform (16 modules, single binary) on Linux servers and VPS instances. Use when the user asks to secure a server, install security monitoring, set up intrusion detection, harden a VPS, protect an AI agent host, or deploy endpoint defense. Covers installation, setup, enforcement presets, module configuration, alert management, and ongoing security operations.

日本語の概要は準備中です。原文の説明を表示しています。

Lord1Egypt/awesome-skill-forge22026年6月10日 更新

Lord1Egypt のスキルをすべて見る

このスキルの問題を報告する