npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

soop-extension

v1.3.1

Published

라이브 스트리밍 서비스 숲(soop)의 비공식 API 라이브러리

Readme

soop

npm version npm downloads license language createAt

라이브 스트리밍 서비스 숲(soop)의 비공식 API 라이브러리

라이브 스트리밍 서비스 SOOP의 비공식 API 라이브러리입니다.

현재 구현된 기능은 다음과 같습니다.

  • 방송 상태 및 상세 정보 조회
  • 로그인 (세션/쿠키 조회)
  • 채팅 송/수신
    • 채팅 송/수신 기능 사용 시 각별한 주의가 필요함
    • 채팅 송/수신 시 사용자 정보(팬클럽, 구독, 팬가입)정보가 채팅방에 노출됨
    • 악의적 목적으로 사용할 시 책임을 지지 않음을 밝힘

설치

Node 20.17.0 버전에서 개발되었습니다.

npm install soop-extension

예시

API 코드 예시

import { SoopChatEvent, SoopClient } from 'soop-extension'

(async function () {
    const streamerId = process.env.STREAMER_ID
    const client = new SoopClient();

    // 라이브 세부정보
    // 로그인 (쿠키 반환)
    // 아래와 같이 숲 ID, PASSWORD 문자열 입력 가능 (그대로 VCS 업로드 시 공개된 공간에 노출될 수 있음)
    // const cookie = await client.auth.signIn("USERID", "PASSWORD");
    const cookie  = await client.auth.signIn(process.env.USERID, process.env.PASSWORD);
    console.log(cookie)

    // 라이브 세부정보 (로그인 후)
    const liveDetailWithCookie = await client.live.detail(streamerId, cookie);
    console.log(liveDetailWithCookie);

    // 라이브 세부정보
    const liveDetailWithoutCookie = await client.live.detail(streamerId);
    console.log(liveDetailWithoutCookie);

    // 채널 정보
    const stationInfo = await client.channel.station(streamerId);
    console.log(stationInfo)
})();

웹소켓(채팅) 코드 예시

import { SoopChatEvent, SoopClient } from 'soop-extension'

(async function () {
    const streamerId = process.env.STREAMER_ID
    const client = new SoopClient();

    const soopChat = client.chat({
        streamerId: streamerId,
        login: { userId: process.env.USERID, password: process.env.PASSWORD } // sendChat 기능을 사용하고 싶을 경우 세팅
    })

    // 연결 성공
    soopChat.on(SoopChatEvent.CONNECT, response => {
        if(response.username) {
            console.log(`[${response.receivedTime}] ${response.username} is connected to ${response.streamerId}`)
        } else {
            console.log(`[${response.receivedTime}] Connected to ${response.streamerId}`)
        }
        console.log(`[${response.receivedTime}] SYN packet: ${response.syn}`)
    })

    // 채팅방 입장
    soopChat.on(SoopChatEvent.ENTER_CHAT_ROOM, response => {
        console.log(`[${response.receivedTime}] Enter to ${response.streamerId}'s chat room`)
        console.log(`[${response.receivedTime}] SYN/ACK packet: ${response.synAck}`)
    })

    // 채팅방 공지
    soopChat.on(SoopChatEvent.NOTIFICATION, response => {
        console.log('-'.repeat(50))
        console.log(`[${response.receivedTime}]`)
        console.log(response.notification.replace(/\r\n/g, '\n'))
        console.log('-'.repeat(50))
    })

    // 일반 채팅
    soopChat.on(SoopChatEvent.CHAT, response => {
        console.log(`[${response.receivedTime}] ${response.username}(${response.userId}): ${response.comment}`)
    })

    // 이모티콘 채팅
    soopChat.on(SoopChatEvent.EMOTICON, response => {
        console.log(`[${response.receivedTime}] ${response.username}(${response.userId}): ${response.emoticonId}`)
    })

    // 텍스트/음성 후원 채팅
    soopChat.on(SoopChatEvent.TEXT_DONATION, response => {
        console.log(`\n[${response.receivedTime}] ${response.fromUsername}(${response.from})님이 ${response.to}님에게 ${response.amount}개 후원`)
        if (Number(response.fanClubOrdinal) !== 0) {
            console.log(`[${response.receivedTime}] ${response.fanClubOrdinal}번째 팬클럽 가입을 환영합니다.\n`)
        } else {
            console.log(`[${response.receivedTime}] 이미 팬클럽에 가입된 사용자입니다.\n`)
        }
    })

    // 영상 후원 채팅
    soopChat.on(SoopChatEvent.VIDEO_DONATION, response => {
        console.log(`\n[${response.receivedTime}] ${response.fromUsername}(${response.from})님이 ${response.to}님에게 ${response.amount}개 후원`)
        if (Number(response.fanClubOrdinal) !== 0) {
            console.log(`[${response.receivedTime}] ${response.fanClubOrdinal}번째 팬클럽 가입을 환영합니다.\n`)
        } else {
            console.log(`[${response.receivedTime}] 이미 팬클럽에 가입된 사용자입니다.\n`)
        }
    })

    // 애드벌룬 후원 채팅
    soopChat.on(SoopChatEvent.AD_BALLOON_DONATION, response => {
        console.log(`\n[${response.receivedTime}] ${response.fromUsername}(${response.from})님이 ${response.to}님에게 ${response.amount}개 후원`)
        if (Number(response.fanClubOrdinal) !== 0) {
            console.log(`[${response.receivedTime}] ${response.fanClubOrdinal}번째 팬클럽 가입을 환영합니다.\n`)
        } else {
            console.log(`[${response.receivedTime}] 이미 팬클럽에 가입된 사용자입니다.\n`)
        }
    })

    // 구독 채팅
    soopChat.on(SoopChatEvent.SUBSCRIBE, response => {
        console.log(`\n[${response.receivedTime}] ${response.fromUsername}(${response.from})님이 ${response.to}님을 구독하셨습니다.`)
        console.log(`[${response.receivedTime}] ${response.monthCount}개월, ${response.tier}티어\n`)
    })

    // 퇴장 정보
    soopChat.on(SoopChatEvent.EXIT, response => {
        console.log(`\n[${response.receivedTime}] ${response.username}(${response.userId})이/가 퇴장하셨습니다\n`)
    })

    // 입장 정보
    soopChat.on(SoopChatEvent.VIEWER, response => {
        if(response.userId.length > 1) {
            console.log(`[${response.receivedTime}] 수신한 채팅방 사용자는 ${response.userId.length}명 입니다.`)
        } else {
            console.log(`[${response.receivedTime}] ${response.userId[0]}이/가 입장하셨습니다`)
        }
    })

    // 방송 종료
    soopChat.on(SoopChatEvent.DISCONNECT, response => {
        console.log(`[${response.receivedTime}] ${response.streamerId}의 방송이 종료됨`)
    })

    // 특정하지 못한 패킷
    soopChat.on(SoopChatEvent.UNKNOWN, packet => {
        console.log(packet)
    })

    // 패킷을 바이너리 형태로 확인
    soopChat.on(SoopChatEvent.RAW, packet => {
        console.log(packet)
    })

    // 본인이 송신한 채팅
    soopChat.on(SoopChatEvent.CHAT, response => {
        if( response.userId.includes(process.env.USERID) ) {
            console.log(`[${response.receivedTime}] ${response.username}(${response.userId}): ${response.comment}`)
        }
    })

    // 채팅 연결
    await soopChat.connect()

    // 채팅 송신
    // 바로 채팅 송신 시 채팅방 연결까지 대기 후 송신
    // 연속으로 채팅 송신 시 벤 및 송신 실패할 수 있으므로 송신 전 대기 시간 설정 필요
    await soopChat.sendChat("ㅎㅇ");
    setInterval(async () => {
        await soopChat.sendChat("신기하다");
    }, 5000)
})();