사용자 스크립트
기본 제공 스크립트는 일반적인 흐름을 대부분 다룹니다. 그것으로 부족할 때 — 순서를 바꾸고 싶을 때, 기본 스크립트가 건드리지 않는 화면을 다뤄야 할 때, TikTok이나 Instagram이 아닌 앱을 자동화해야 할 때 — 원하는 언어로 직접 작성하면 TikMatrix가 기기를 넘겨줍니다.
요구 사항
사용자 스크립트는 Pro, Team, Business 플랜에서만 사용할 수 있습니다. Starter 플랜에서는 사용할 수 없습니다.
플랜의 기기 수가 곧 동시 실행 한도입니다. Pro 플랜(20대)은 기본 작업이든 사용자 스크립트든, 혹은 둘을 섞어서든 동시에 20대를 제어할 수 있습니다.
실행 방식 두 가지
독립 실행
프로그램은 여러분이 직접 실행합니다. TikMatrix는 기기만 빌려줍니다.
from tikmatrix import TikMatrix
client = TikMatrix()
for device in client.devices():
if device["busy"]:
continue
with client.device(device["serial"], label="my crawler") as d:
d.press("home")
print(d.info())
일회성 작업, 데이터 수집, 자체 스케줄러로 돌리고 싶은 작업에 적합합니다.
관리형 실행
프로그램을 TikMatrix에 등록하면 일반 작업과 똑같아집니다. 작업 큐, 플랜별 동시 실행 제한, 자동 재시도, 작업 로그, 스케줄 템플릿을 그대로 사용할 수 있습니다. TikMatrix는 프로그램을 시작하기 전에 기기를 임대하고 임대 ID를 환경 변수로 전달합니다.
from tikmatrix import TikMatrix
with TikMatrix.from_env() as d: # 기기는 이미 임대된 상태
d.click(text="Log in")
print("done") # 이 줄은 작업 로그에 남습니다
반복 실행, 예약 실행, 여러 기기에 걸친 실행에 적합합니다.
어느 쪽을 고를까
| 독립 실행 | 관리형 실행 | |
|---|---|---|
| 실행 주체 | 여러분 | TikMatrix 작업 큐 |
| 기기 임대 | 직접 획득 | 시작 시점에 이미 보유 |
| 재시도·예약·작업 로그 | 직접 구현 | 기본 제공 |
| 여러 기기에서 실행 | 직접 반복문 작성 | 기기당 작업 하나씩 병렬 배포 |
| 적합한 용도 | 탐색, 크롤러, 일회성 작업 | 반복하고 싶은 모든 것 |
먼저 독립 실행으로 흐름을 다듬은 뒤 같은 파일을 관리형 스크립트로 등록해도 됩니다. 바뀌는 건 TikMatrix.from_env() 한 줄뿐입니다.
시작하기
1. 클라이언트 라이브러리 설치
pip install requests
그런 다음 SDK 디렉터리의 tikmatrix.py를 스크립트 옆에 복사합니다. 이 라이브러리는 단일 파일이며 다른 의존성이 없습니다.
꼭 쓸 필요는 없습니다. API는 HTTP 위의 평범한 JSON이며, 원시 엔드포인트는 아래에 정리해 두었습니다.
2. 스크립트 작성
from tikmatrix import TikMatrix
client = TikMatrix()
with client.device("192.168.1.5:5555") as d:
d.press("home")
d.adb("shell", "am", "start", "-a", "android.settings.SETTINGS")
d.wait_for(text="Settings", timeout=15)
d.screenshot("settings.png")
TikMatrix를 켜 두고 기 기를 연결한 상태에서 실행하세요. 기기 정보 딕셔너리가 출력되면 연결은 정상입니다.
3. 등록하기 (관리형 전용)
기기 → 사용자 스크립트 → 스크립트 추가 로 이동합니다.
| 항목 | 의미 |
|---|---|
| 이름 | 스크립트 목록과 작업 로그에 표시됩니다 |
| 명령 | 실행할 프로그램 줄. 예: python C:/scripts/my_flow.py |
| 작업 디렉터리 | 선택 사항. 프로그램이 시작될 위치 |
| 플랫폼 | 아래 플랫폼 모드 참고 |
| 타임아웃 | 몇 초 후 스크립트를 종료하고 작업을 실패 처리할지. 기본값 1800 |
| 추가 환경 변수 | 선택 사항. 프로그램 환경에 병합되는 JSON 객체 |
| 활성화 | 삭제하지 않고 끌 수 있습니다. 비활성 스크립트는 배포되지 않습니다 |
이후 스크립트 행의 ▶ 를 누르고 기기를 고르면 기본 스크립트와 완전히 동일하게 실행됩니다.
AI 어시스턴트는 평범한 말로 된 설명으로 사용자 스크립트를 작성하고 한 번에 등록까지 해줍니다. 디스크에 무언가 기록되기 전에 파일 전체를 보여줍니다.
기기 임대
한 기기는 한 번에 하나만 제어할 수 있습니다. 임대는 TikMatrix에게 "이 기기는 사용 중"이라고 알리는 것이며, 그 결과:
- 작업 큐가 같은 화면에 작업을 배포하지 않고,
- 여러분의 JSON-RPC 호출이 기본 스크립트와 똑같이 에이전트 상태를 보고하므로, 워치독은 침묵하는 에이전트가 아니라 바쁜 에이전트를 보게 됩니다.
임대는 플랜의 기기 슬롯도 하나 차지합니다.
임대는 만료됩니다 — 기본 120초, 최대 600초. Python 라이브러리는 백그라운드 스레드에서 자동 갱신하고 with 블록이 끝날 때 해제하므로, 스크립트가 죽어도 앱을 재시작할 때까지 붙잡고 있는 대신 몇 초 안에 기기가 풀립니다. API를 직접 호출한다면 하트비트를 직접 보내야 합니다.
살아 있는 모든 임대는 설정 → Developer API → 활성 기기 세션 에서 확인하고 강제 해제할 수 있습니다.
플랫폼 모드
등록된 스크립트는 대상 플랫폼을 선언합니다.
Generic — 기기가 그대로 넘어옵니다. 앱을 실행하지 않고, 계정을 전환하지 않으며, 입력기를 확인하지도 않고, 끝난 뒤에 무엇도 닫지 않습니다. TikMatrix가 자동화하지 않는 앱을 자동화할 때 사용합니다.
TikTok / Instagram / Threads — 프로그램이 시작되기 전에 앱을 열고 올바른 계정을 유효화하며, 끝나면 앱을 닫습니다. 기본 스크립트와 동일합니다. 해석된 패키지는 TIKMATRIX_PACKAGE로 알 수 있습니다. 기본 스크립트에 없는 단계를 더할 때 사용합니다.
Threads에서는 Settings → Switch accounts 시트를 통해 계정이 전환되며, 그 후 프로필 페이지에서 핸들이 읽혀집니다. 기기에 로그인하지 않은 계정을 지정하는 작업은 현재 활성화되어 있는 계정으로 실행되는 대신 실패합니다.
환경 변수
관리형 스크립트가 받는 값:
| 변수 | 의미 |
|---|---|
TIKMATRIX_API_BASE | 서버 URL. 예: http://127.0.0.1:50809 |
TIKMATRIX_SESSION_ID | 이미 여러분을 대신해 보유 중인 임대 |
TIKMATRIX_SERIAL | 이 작업이 배포된 기기 |
TIKMATRIX_PACKAGE | 해석된 앱 패 키지 |
TIKMATRIX_PLATFORM | tiktok, instagram, threads, 또는 generic |
TikMatrix.from_env()가 이 값들을 대신 읽어줍니다.
독립 실행 스크립트는 이 값들을 받지 못합니다. 기기를 직접 임대하세요.
추가 환경 변수에 넣은 내용은 그 위에 덮어써서 병합됩니다. 파일을 고치지 않고 등록된 스크립트 하나에 실행별 설정을 넘기는 일반적인 방법입니다.
Python 라이브러리 레퍼런스
TikMatrix — 연결
| 호출 | 하는 일 |
|---|---|
TikMatrix(base_url=None, timeout=30.0) | 연결. TIKMATRIX_API_BASE, 이어서 http://127.0.0.1:50809로 폴백 |
client.devices() | 온라인 기기. 각 항목에 serial, real_serial, busy |
client.sessions() | 살아 있는 모든 임대(다른 프로세스 것 포함) |
client.device(serial, label=..., ttl_secs=120) | 기기를 임대하고 Device 반환 |
TikMatrix.from_env() | 관리형 스크립트가 시작될 때 받은 기기를 이어받음 |
Device — 기기
| 호출 | 하는 일 |
|---|---|
d.info() | UIAutomator2 기기 정보 |
d.window_size() | (너비, 높이) |
d.screenshot(path=None) | PNG 바이트. path를 주면 저장 |
d.hierarchy() | 현재 UI 트리(XML) |
d.find(text=, resource_id=, description=, class_name=) | 일치하는 노드. 각각 bounds와 center 포함 |
d.exists(**criteria) | 일치하는 것이 있는지 |
d.wait_for(timeout=10.0, interval=1.0, **criteria) | 나타날 때까지 대기 후 반환 |
d.click(timeout=10.0, **criteria) | 요소를 기다린 뒤 중앙을 탭 |
d.click_xy(x, y) | 좌표 탭 |
d.swipe(sx, sy, ex, ey, steps=20) | 스와이프 |
d.press(key) | back, home, recent, enter … |
d.input_text(text) | 내장 빠른 입력기로 포커스된 입력란에 입력 |
d.jsonrpc(method, params=None, timeout=10) | 임의의 UIAutomator2 메서드 |
d.adb(*args, timeout_ms=None) | ADB 명령 실행 |
d.release() | 임대 해제. with를 쓰면 자동 |
find는 덤프한 UI 트리를 대상으로 매칭하므로, 셀렉터가 빗나갔을 때 print(d.hierarchy())로 실제로 무엇을 검색했는지 볼 수 있습니다. 기기 화면의 요소 검사기는 같은 트리를 시각적으로 보여주며, resource-id를 찾는 가장 빠른 방법인 경우가 많습니다.
input_text에는 ADB가 필요합니다내장 입력기로 브로드캐스트를 보내는데, 이는 adb shell을 거칩니다. 사용 전에 ADB 접근을 켜세요. 켜지 않으면 403으로 실패합니다.
오류
라이브러리는 두 가지 예외를 던지며, 둘 다 RuntimeError의 하위 클래스입니다.
| 예외 | 발생 시점 |
|---|---|
DeviceBusyError | HTTP 409 — 기기가 이미 임대 중이거나 플랜에 남은 기기 슬롯이 없음 |
TikMatrixError | 그 외 전부: 플랜 부족, 임대 만료, ADB 비활성, 셀렉터 불일치 등 |
from tikmatrix import TikMatrix, TikMatrixError, DeviceBusyError
client = TikMatrix()
try:
with client.device("192.168.1.5:5555") as d:
d.click(text="Log in", timeout=20)
except DeviceBusyError:
print("그 기기는 다른 곳에서 쓰는 중입니다 — 다른 기기를 쓰세요")
except TikMatrixError as exc:
print("실패:", exc)
관리형 스크립트에서는 예외를 그대로 밖으로 흘려보내는 편이 보통 옳습니다. 0이 아닌 종료 코드가 작업을 실패로 표시하고, 트레이스백이 작업 로그에 남습니다.
HTTP 엔드포인트
기기 조작에는 살아 있는 임대를 가리키는 x-session-id 헤더가 필요합니다. API 키는 없습니다. 로컬 API의 나머지와 마찬가지로 이 엔드포인트들은 인증을 하지 않으며, 네트워크에서 이 컴퓨터에 도달할 수 있다는 것 자체가 접근 제어입니다. CORS 헤더를 전혀 보내지 않으므로 브라우저 페이지가 아니라 프로그램(curl, Python, 서버 측 코드)에서 호출하세요.
| 메서드 | 경로 | 용도 |
|---|---|---|
GET | /api/v1/rpc/devices | 온라인 기기와 사용 중 여부 목록 |
POST | /api/v1/rpc/session | 기기 임대 → session_id |
POST | /api/v1/rpc/session/{id}/heartbeat | 임대 연장 |
DELETE | /api/v1/rpc/session/{id} | 임대 해제 |
GET | /api/v1/rpc/session | 살아 있는 임대 목록 |
POST | /api/v1/rpc/jsonrpc | UIAutomator2 메서드 호출 |
POST | /api/v1/rpc/adb | ADB 명령 실행 |
GET | /api/v1/rpc/hierarchy?serial= | 현재 UI 트리(XML) |
GET | /api/v1/rpc/screenshot?serial= | 현재 화면(PNG) |
JSON 응답은 로컬 API의 나머지와 동일한 봉투 구조를 씁니다 — {"code": 0, "message": "success", "data": ...}, 실패 시 code가 0이 아닙니다. hierarchy와 screenshot은 원시 본문을 반환합니다.
예시
# 기기 임대
curl -X POST http://127.0.0.1:50809/api/v1/rpc/session \
-H "Content-Type: application/json" \
-d '{"serial":"192.168.1.5:5555","label":"curl test","ttl_secs":120}'
# {"code":0,"message":"success","data":{"session_id":"ff3ae079-...","serial":"192.168.1.5:5555", ...}}
# 제어
curl -X POST http://127.0.0.1:50809/api/v1/rpc/jsonrpc \
-H "x-session-id: ff3ae079-..." \
-H "Content-Type: application/json" \
-d '{"serial":"192.168.1.5:5555","method":"deviceInfo","params":[]}'
# 작업하는 동안 임대 유지
curl -X POST http://127.0.0.1:50809/api/v1/rpc/session/ff3ae079-.../heartbeat \
-H "Content-Type: application/json" \
-d '{"ttl_secs":120}'
# 반납
curl -X DELETE http://127.0.0.1:50809/api/v1/rpc/session/ff3ae079-...
오류
| 상태 코드 | 의미 |
|---|---|
| 403 | 플랜이 Pro 미만, 임대 없음, 임대 만료, 또는 ADB 접근이 꺼짐 |
| 409 | 기기가 이미 임대 중이거나 플랜에 남은 기기 슬롯이 없음 |
다른 언어로 작성하기
여기에 Python 전용인 것은 없습니다. HTTP 요청을 보낼 수 있는 런타임이면 무엇이든 됩니다. 관리형 모드의 계약은 "환경 변수 세 개를 읽고, 성공하면 0으로 종료한다"뿐입니다.
// my_flow.js — 등록 명령: node C:/scripts/my_flow.js
const base = process.env.TIKMATRIX_API_BASE || "http://127.0.0.1:50809";
const serial = process.env.TIKMATRIX_SERIAL;
const session = process.env.TIKMATRIX_SESSION_ID;
async function jsonrpc(method, params = []) {
const res = await fetch(`${base}/api/v1/rpc/jsonrpc`, {
method: "POST",
headers: { "content-type": "application/json", "x-session-id": session },
body: JSON.stringify({ serial, method, params }),
});
const body = await res.json();
if (!res.ok || body.code !== 0) throw new Error(body.message || res.statusText);
return body.data;
}
console.log(await jsonrpc("deviceInfo"));
인터프리터가 PATH에 없다면 명령에 전체 경로를 적으세요. 예: C:/Program Files/nodejs/node.exe C:/scripts/my_flow.js
API로 사용자 스크립트 실행하기
등록된 스크립트는 작업 관리 API로도 시작할 수 있어서, 한 스크립트가 후속 작업을 큐에 넣을 수 있습니다.
curl -X POST http://127.0.0.1:50809/api/v1/task \
-H "Content-Type: application/json" \
-d '{
"serials": ["192.168.1.5:5555"],
"script_name": "custom_script",
"script_config": {
"custom_script_id": 1,
"custom_script_platform": "generic"
}
}'
custom_script_id는 등록한 스크립트의 ID입니다.
ADB 접근
/api/v1/rpc/adb는 스크립트에 기기 셸을 제공합니다. 미디어 푸시, APK 설치, 시스템 설정 변경에 필요합니다. 다만 API 키가 없는 엔드포인트 위의 완전한 셸이므로 기본적으로 꺼진 채로 배포됩니다. 이것이 필요한 스크립트가 생겼을 때 설정 → Developer API → ADB 명령 허용 에서 켜세요. /rpc/jsonrpc를 통한 UI 자동화는 켜지 않아도 동작합니다.
꺼져 있는 동안 /api/v1/rpc/adb는 403을 반환하며 나머지 API는 정상 동작합니다. 스크립트가 실행한 모든 ADB 명령은 로그 파일에 기록됩니다.
오래 버티는 스크립트 작성법
- 정해진 시간을 자지 말고 화면을 기다리세요.
d.wait_for(...)는 요소가 나타나는 즉시 반환합니다. 고정 sleep은 필요 이상으로 느리거나, 상태가 나쁜 날에는 너무 짧습니다. - 탭하기 전에 확인하세요. 동의 대화상자나 "나중에" 같은 안내에
d.exists(...)를 한 번 쓰는 비용은 트리 덤프 한 번이고, 허공을 탭해 망칠 실행 한 번을 살립니다. - 한 일을 출력하세요. 관리형 모드에서 stdout이 곧 작업 로그이며, 아무도 지켜보지 않은 실행에 대한 유일한 기록입니다.
- 재실행이 안전하도록 만드세요. 재시도는 프로그램 전체를 처음부터 다시 돌립니다. 게시하는 스크립트라면 처음부터 시작한다고 가정하지 말고 이미 게시했는지 확인해야 합니다.
- 스크립트 하나에 일 하나. 동시 실행은 기기 단위이므로, 열 대에 작은 작업 열 개를 돌리는 편이 한 스크립트가 열 대를 반복 순회하는 것보다 훨씬 빨리 끝납니다.
참고 사항과 제한
- 명령은 셸을 거치지 않고 직접 실행되므로
&&와|는 연산자가 아니라 인자로 취급됩니다. 셸 동작이 필요하면cmd /c "..."(Windows) 또는sh -c "..."(macOS)를 등록하세요. - 공백이 포함된 경로는 따옴표로 감싸세요:
"C:/Program Files/Python/python.exe" my_script.py - 타임아웃을 넘긴 스크립트는 강제 종료되고 작업은 실패 처리됩니다.
- 0이 아닌 종료 코드는 작업을 실패로 표시합니다. 스크립트가 stdout과 stderr에 쓴 내용은 모두 작업 로그에 들어갑니다.
- 스크립트는 TikMatrix와 동일한 권한으로 실행됩니다. 직접 작성했거나 신뢰하는 프로그램만 등록하세요.
문제 해결
API access requires Pro or higher plan (403)
이 컴퓨터의 라이선스가 Starter이거나 비활성입니다. 설정 → 라이선스 를 확인하세요.
127.0.0.1:50809 연결 거부
TikMatrix가 실행 중이 아니거나 다른 사용자 계정에서 실행 중입니다. 서버는 앱이 열려 있는 동안에만 존재합니다.
임대 시도마다 409 기기가 실제로 사용 중이거나(설정 → Developer API → 활성 기기 세션 확인), 플랜의 기기 슬롯이 실행 중인 작업으로 모두 찬 상태입니다.
긴 단계 도중에 임대가 만료됨
기본 TTL은 120초이고 라이브러리가 백그라운드에서 갱신하므로, 보통 이는 스크립트가 메인 스레드를 TTL보다 오래 붙잡았다는 뜻입니다. ttl_secs를 올리거나(최대 600) 오래 걸리는 작업을 그 스레드 밖으로 옮기세요.
d.adb(...)가 403으로 실패
ADB 접근이 꺼져 있습니다. 설정 → Developer API → ADB 명령 허용 에서 켜세요.
셀렉터가 전혀 일치하지 않음
print(d.hierarchy())는 find가 실제로 검색한 트리를 보여줍니다. 텍스트는 정확히 일치해야 하므로 끝의 공백이나 현지화된 문구가 흔한 원인입니다. text보다 resource_id로 매칭하는 편이 안정적입니다.
작업은 실패인데 기기는 멀쩡해 보임 작업 로그를 읽어보세요. 0이 아닌 종료는 — 성공적으로 끝난 뒤 던져진 미처리 예외라도 — 자동화 자체가 잘 동작했더라도 작업을 실패 처리합니다.
다음 단계
- 로컬 API 개요 — 인증과 응답 형식
- 작업 관리 API — 작업 생성·조회·재시도·중지
- AI 어시스턴트 — 모델이 스크립트를 작성하고 등록하도록 하기
- GitHub의 SDK와 예제