-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathllms.txt
More file actions
144 lines (108 loc) · 12.5 KB
/
Copy pathllms.txt
File metadata and controls
144 lines (108 loc) · 12.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# SOOPAPI - 비공식 SOOP 채팅 API
> SOOP(한국 라이브 스트리밍 플랫폼) 채팅 시스템과 상호작용하기 위한 비공식 Java 25+ 라이브러리.
> WebSocket 기반 실시간 채팅 연결, 92개 서버 이벤트의 타입 안전한 디코딩, 이벤트 기반 아키텍처를 제공합니다.
이 문서는 **요점 정리**입니다. 전체 API 시그니처·이벤트별 필드·프로토콜·내부 구조 등 상세 레퍼런스는
[llms-full.txt](llms-full.txt)에 있으며, 각 절 끝의 `→ llms-full.txt §...` 링크로 바로 이동할 수 있습니다.
## 핵심 정보
- GitHub: https://github.com/getCurrentThread/soopapi
- 버전: v0.15.0
- 라이선스: MIT
- 언어: Java 25 이상
- 빌드: Gradle 9.3.1
- 배포: JitPack (com.github.getCurrentThread:soopapi)
- 런타임 의존성: Gson
## 설치
```groovy
repositories { maven { url 'https://jitpack.io' } }
dependencies {
implementation 'com.github.getCurrentThread:soopapi:v0.15.0' // 최신 버전은 핵심 정보 참조
}
```
→ 상세: [llms-full.txt § 설치](llms-full.txt#설치)
## 빠른 시작 (권장: SOOPClient)
```java
try (SOOPClient client = new SOOPClient()) {
// 글로벌 리스너 — 이후 add()되는 모든 스트림에 자동 attach. 핸들러: (streamerId, event)
client.on(ChatEvent.CHAT_MESSAGE, (String bid, ChatMessageEvent e) ->
System.out.println("[" + bid + "] " + e.senderNickname() + ": " + e.message()));
client.on(ChatEvent.SEND_BALLOON, (String bid, SendBalloonEvent e) ->
System.out.println("[" + bid + "] " + e.senderNickname() + "님이 풍선 " + e.count() + "개!"));
client.add("streamerA"); // 등록 즉시 비동기 연결, bid 기준 dedup
client.add("streamerB");
client.connectAll().join(); // 모든 세션이 끝날 때까지 대기
}
```
- authCookie 없이 연결하면 **익명(읽기 전용)** 모드 — 수신은 되지만 `sendChat()`은 `AuthenticationException`.
- 19금 방송은 익명으로 들어갈 수 없음 — 세션이 재시도 없이 `DISCONNECTED(causedByError=true)`로 끝나고 `connectToChat()`·`ready()`는 `ConnectionException`(원인 `AdultBroadcastException`). 연령 인증된 계정의 `authCookie`를 넘기면 `detail()`이 채팅 접속 정보를 받고 채팅에도 연결됨(실제 계정으로 확인). 19금을 여는 것은 로그인 쿠키이고 `confirm_adult`는 늘 false(19금 확인을 사용자 대신 하지 않음).
- 인증/전송: `client.auth().signIn(id, pw)` → `AuthCookie` → `SOOPChatConfig.Builder().authCookie(cookie)` → `connectToChat()` 뒤 `ready()`가 완료되면 `sendChat(...)`(예: `chat.connectToChat(); chat.ready().thenRun(() -> chat.sendChat("hi"));`). 인증 연결의 ENTER_INFO는 `ready()` 완료 전에 송신 순서에 들어가므로 곧바로 보내도 그 뒤에 나갑니다. `JOIN_CHANNEL` 리스너에서 보내도 되지만 `JOIN_CHANNEL`은 재입장할 때마다 다시 발생합니다. null·공백 메시지와 `U+000C`·`U+001B`가 든 메시지는 `IllegalArgumentException`.
→ 직접 연결 · 인증+전송 · 연결 상태 이벤트 · 에러 핸들링 예제: [llms-full.txt § 빠른 시작](llms-full.txt#빠른-시작)
## API 핵심
**SOOPClient** (파사드, `AutoCloseable`) — 통합 진입점
- `add(streamerId)` / `add(config)` → 등록 + 즉시 비동기 연결 (bid dedup, 세션이 끝난 인스턴스면 새 세션)
- `remove(id)` · `get(id)` · `streamerIds()` · `clients()`
- `on/off(ChatEvent, (streamerId, event) -> …)` → 글로벌 리스너 (모든 스트림 + 이후 추가분에 자동 attach)
- `connectAll()` · `reconnect(id)` · `reconnectAll()` · `close()`
- `auth()` → SOOPAuth · `live()` → SOOPLive · `channel()` → SOOPChannel
**SOOPChatClient** (단일 채팅 연결, `AutoCloseable`)
- `on/once/off(ChatEvent, listener)` · `getEventEmitter()`
- `connectToChat()` (세션이 끝날 때 완료) · `connectAndAwait()` (블로킹) · `ready()` (채널에 들어가면 완료) · `disconnect()` (non-blocking) · `close()` (최종 종료) · `reconnect()` · `forceReconnect()`
- `sendChat(message)` · `sendWhisper(targetId, message)` (인증 필요) · `isConnected()` · `getConnectionStatus()` · `getBid()`
**HTTP API**: `SOOPAuth.signIn(id, pw)` → `AuthCookie` · `SOOPLive.detail(id[, bno[, cookie]])` → `LiveDetail` · `SOOPChannel.station(id)` → `StationInfo`
→ 전체 메서드 시그니처 · `SOOPChatConfig.Builder` 옵션 · record 필드: [llms-full.txt § API 레퍼런스](llms-full.txt#api-레퍼런스)
## 이벤트 시스템
모든 이벤트는 Java `record`이며 sealed 계층에 속합니다. 공통 필드: `eventType()`, `raw()`, `timestamp()`.
```
BaseEvent (sealed)
├── ChatBaseEvent 채팅 (메시지, 입퇴장)
├── DonationBaseEvent 후원 (풍선, 초콜릿, 구독)
├── SystemBaseEvent 시스템 (연결, 서버 상태)
├── ModerationBaseEvent 관리 (킥, 채금, 차단)
├── ItemBaseEvent 아이템 (구매, 드롭)
├── NotificationBaseEvent 알림 (공지, 미션)
└── UnknownEvent 알 수 없는 타입
```
- 서비스 코드 `0`~`128`의 서버 이벤트 92개 + 클라이언트 합성 이벤트 5개(`-2` RAW, `-3` DISCONNECTED, `-4` RECONNECTING, `-5` RECONNECTED, `-1` NONE_TYPE).
- `ChatEvent.fromCode(int)`는 미지의 코드에 대해 `NONE_TYPE`(센티넬)을 반환하고, 그런 패킷은 `NONE_TYPE` 리스너에 `UnknownEvent`로 전달됩니다(`code()` = 원래 서비스 코드, `originalMessage()`·`raw()` = 패킷 전체). `UnknownEvent`는 `SystemBaseEvent`가 아니라 `BaseEvent`를 직접 구현하므로 `UnknownEvent`나 `BaseEvent`로 받습니다.
- 목록·맵 필드(`BanWordEvent.banWordList`(`List<String>`), `ChatUserEvent.userList`, `AdminChatUserEvent.users`, `KickUserListEvent.kickedUsers`, `ChuserExtendEvent.userStatus`)는 수정할 수 없는 복사본입니다(`null` → 빈 목록·맵).
- v0.14.0에서 올라오면: `banWordList`가 `String[]`에서 `List<String>`으로, `NoneTypeEvent`·`NoneTypeDecoder` 제거 → [llms-full.txt § 변경 사항 (호환성)](llms-full.txt#변경-사항-호환성)
- 자주 쓰는 이벤트:
| 코드 | ChatEvent | Record | 설명 |
|------|-----------|--------|------|
| 5 | CHAT_MESSAGE | ChatMessageEvent | 채팅 메시지 |
| 9 | DIRECT_CHAT | DirectChatEvent | 귓속말 |
| 18 | SEND_BALLOON | SendBalloonEvent | 별풍선 후원 |
| 37 | CHOCOLATE | ChocolateEvent | 초콜릿 후원 |
| 108 | SEND_SUBSCRIPTION | SendSubscriptionEvent | 구독 선물 |
| 11 | KICK | KickEvent | 강제 퇴장 |
| 8 | SET_DUMB | SetDumbEvent | 채금 |
→ 전체 코드↔이벤트 표(범주별): [llms-full.txt § 전체 이벤트 목록](llms-full.txt#전체-이벤트-목록)
→ 이벤트별 record 필드 상세: [llms-full.txt § 이벤트 Record 필드 상세](llms-full.txt#이벤트-record-필드-상세)
→ 합성 패킷으로 검증한 필드 값 예시(30개): [llms-full.txt § Fixture 검증 데이터](llms-full.txt#fixture-검증-데이터-30개-이벤트-합성-패킷)
## 코드표 (지연 디코딩)
`...soopapi.code` 패키지가 원시 코드/플래그를 타입으로 **지연 디코딩**합니다. 원시 필드(String/int)는 그대로 유지되고, 이벤트 record의 접근자가 **호출 시점에만** 파싱합니다. 알 수 없는 코드는 센티넬(`UNKNOWN` / `UserLevel.EMPTY`)을 반환하며 예외를 던지지 않습니다.
- `UserLevel.parse("주|보조")` → `UserFlag`(주) · `UserFlag2`(보조) 집합(수정 불가). 두 그룹은 비트값이 겹쳐 enum을 분리(예: 16 = GUEST(주) vs GAMEGOD(보조)). 각 그룹은 부호 있는/없는 32비트 표기를 모두 받고, 부호 비트(`NOTITOPFAN = 1<<31`)는 `(mask & bit) == bit`로 안전 처리.
- 접근자: `ChatMessageEvent.senderLevel()`, `ManagerChatEvent.senderLevel()`, `LoginEvent.userLevel()`, `JoinChannelEvent.userLevel()`, `SetUserFlagEvent.oldLevel()/newLevel()`, `SetAdminFlagEvent.level()`, `SetNicknameEvent.level()`, `SetSubBjEvent.level()`, `ChatUserEntry.level()`, `AdminChatUserEntry.level()`.
- `ChatIceType.fromCode(int)`(레거시 0~4) + `ChatIceType.Flag.fromMask(int)`(v2 비트, 매핑 안 된 비트는 무시). 접근자: `IceModeEvent`/`IceModeExEvent`/`GetIceModeRelayEvent`의 `iceType()`, `iceFlags()`, `isIceFlagMode()`, 그리고 `freezeType`의 그룹 비트를 푸는 `IceModeExEvent`/`GetIceModeRelayEvent.freezeFlags()`.
- `ChatQuitStatus.fromCode(int)`(0~6). 접근자: `QuitChannelEvent.quitStatus()`.
→ 플래그 비트값 · 접근자 전체 목록: [llms-full.txt § 코드표 및 플래그 해석](llms-full.txt#코드표-및-플래그-해석)
## 연결 라이프사이클 & 에러 처리
- `SOOPClient.add()`는 등록과 동시에 비동기 연결을 시작하므로 별도 `connectToChat()`이 불필요합니다. 저수준 `SOOPChatClient`를 직접 쓸 때만 `connectToChat()`/`connectAndAwait()`를 사용합니다.
- `connectToChat()`은 세션을 시작하고, 반환된 future는 세션이 **끝날 때** 완료됩니다: `disconnect()`·서버 종료면 정상, 연결 실패·재시도 소진이면 `ConnectionException`. 진행 중이면 같은 future를 돌려줍니다.
- 서버가 연결을 닫으면 세션 종료(자동 재연결 없음). 네트워크 오류·1006·송신/핑 실패·채널 입장 응답 없음은 backoff(2초→최대 30초) 자동 재연결(`RECONNECTING`→`RECONNECTED`), 소진 시 `DISCONNECTED(causedByError=true)`.
- "연결됨"(`isConnected()`, `ready()`, `RECONNECTED`)은 서버가 채널 입장(JOIN)에 응답한 시점. 서버가 직후 재입장을 몇 초 무시할 수 있어 응답이 올 때까지 JOIN을 1초마다 다시 보냅니다.
- `ready()`: 이미 입장해 있으면 완료된 future, 연결 중·backoff 대기 중이면 다음 JOIN 응답에서 완료(`reconnect()`·`forceReconnect()`를 거쳐도 계속 대기). 그 전에 세션이 끝나면(disconnect·close·서버 종료·연결 실패·재시도 소진) `ConnectionException`, 세션이 없거나 `close()` 뒤면 `IllegalStateException`. 폴링하지 말고 받은 future 하나를 재사용합니다.
- `forceReconnect()`/`SOOPClient.reconnect(id)`는 세션을 유지하며 `RECONNECTING`→`RECONNECTED`만 emit(`DISCONNECTED` 없음. 새 연결이 실패하면 세션 종료). `disconnect()`는 어떤 상태에서도 세션을 끝내며 non-blocking, `close()`는 최종 종료.
- 이벤트는 스트림마다 도착 순서대로 한 번에 하나씩 전달, `DISCONNECTED`는 세션마다 정확히 한 번(직접 끝낸 경우 `isClientInitiated()`). 리스너 안에서 세션 future를 기다리면 교착(송신 결과·`reconnect()`·`ready()`는 lane 밖에서 완료되므로 기다려도 됨. 기다리는 동안 그 스트림의 이벤트 전달은 멈춤).
- 예외 계층: `SOOPChatException`(RuntimeException) ← `AuthenticationException`(← `AdultBroadcastException`) · `ConnectionException` · `EventEmitterException`(`getChatEvent()` 제공).
→ 상세: [llms-full.txt § connectToChat() 동작 방식](llms-full.txt#connecttochat-동작-방식) · [§ 에러 처리](llms-full.txt#에러-처리)
## 더 알아보기 (llms-full.txt)
기여자·심화용 레퍼런스는 전부 [llms-full.txt](llms-full.txt)에 있습니다.
- 아키텍처 개요(패키지 구조 · 컴포넌트 관계 · 메시지 파이프라인) → [§ 아키텍처 개요](llms-full.txt#아키텍처-개요-기여자용)
- 내부 구조 상세(ConnectionManager · SOOPConnection · WebSocketManager · Listener) → [§ 내부 구조 상세](llms-full.txt#내부-구조-상세)
- WebSocket 프로토콜(패킷 구조 · 명령 코드 · 구분자 · 핸드셰이크) → [§ WebSocket 프로토콜](llms-full.txt#websocket-프로토콜)
- 디코더 시스템(IMessageDecoder · 팩토리 · 디코딩 규칙) → [§ 디코더 시스템](llms-full.txt#디코더-시스템)
- 이벤트 시스템 내부(EventEmitter · once() 구현 · sealed 계층) → [§ 이벤트 시스템 내부](llms-full.txt#이벤트-시스템-내부)
- 테스트 · 빌드 시스템 · 기여 가이드 · 성능 고려사항 → [§ 테스트](llms-full.txt#테스트) · [§ 빌드 시스템](llms-full.txt#빌드-시스템) · [§ 기여 가이드](llms-full.txt#기여-가이드) · [§ 성능 고려사항](llms-full.txt#성능-고려사항)
## 면책 조항
이는 비공식 API이며 SOOP와 제휴되거나 승인되지 않았습니다. 사용에 따른 책임은 사용자에게 있습니다.
SOOP 플랫폼의 웹소켓 통신 방식이 변경되면 동작하지 않을 수 있습니다.