Chat Stream
Get JKT48 live chat data from IDN streams using JKT48Connect API
Introduction
The JKT48Connect Chat Stream API provides access to real-time chat messages from JKT48 member live streams on IDN. Use this endpoint to fetch live chat data using username and slug obtained from the live data API.
Real-time Chat
Access live chat messages from ongoing streams.
User Interactions
Track viewer engagement and member responses.
Live Updates
Stream chat data in real-time for interactive applications.
Quick Start
Get Live Stream Data
First, fetch live data to get username and slug parameters.
const liveData = await getJKT48Live();
const { username, slug } = liveData.live_streams[0]; // Get from live stream
Fetch Chat Stream
curl "https://v2.jkt48connect.my.id/api/jkt48/chat-stream/{username}/{slug}?apikey=YOUR_API_KEY"
Process Chat Messages
Handle real-time chat data and user interactions.
Endpoint Details
Base URL: https://v2.jkt48connect.my.id
Endpoint: /api/jkt48/chat-stream/{username}/{slug}
Method: GET
Authentication: API Key required
Path Parameters:
username
(required): Member username from live dataslug
(required): Stream slug from live data
Query Parameters:
apikey
(required): Your API authentication keylimit
(optional): Number of messages to fetch (default: 50)since
(optional): Timestamp to fetch messages since
Example:
GET /api/jkt48/chat-stream/freya.jkt48/weekend-chat-session?apikey=YOUR_API_KEY HTTP/1.1
Host: v2.jkt48connect.my.id
Returns live chat messages and metadata:
{
"stream_info": {
"username": "freya.jkt48",
"slug": "weekend-chat-session",
"title": "Weekend Chat with Freya",
"status": "live",
"viewers": 1850,
"duration": "01:45:30"
},
"chat_messages": [
{
"id": "msg_001",
"user": {
"username": "fan_jkt48",
"display_name": "JKT48 Fan",
"avatar": "https://cdn.idntimes.com/avatar/fan_jkt48.jpg",
"badge": "subscriber"
},
"message": "Halo Freya! Semangat live nya!",
"timestamp": "2025-06-29T14:30:15.000Z",
"type": "chat",
"reactions": 5,
"is_pinned": false
},
{
"id": "msg_002",
"user": {
"username": "freya.jkt48",
"display_name": "Freya Jayawardana",
"avatar": "https://cdn.idntimes.com/avatar/freya.jpg",
"badge": "streamer"
},
"message": "Terima kasih semuanya! 💙",
"timestamp": "2025-06-29T14:31:00.000Z",
"type": "streamer_reply",
"reactions": 25,
"is_pinned": true
}
],
"stats": {
"total_messages": 1247,
"unique_users": 456,
"messages_per_minute": 15.3,
"top_emojis": ["💙", "😍", "👏", "🔥", "😂"]
}
}
Implementation Examples
const API_KEY = 'YOUR_API_KEY';
const BASE_URL = 'https://v2.jkt48connect.my.id';
async function getChatStream(username, slug, options = {}) {
const params = new URLSearchParams({
apikey: API_KEY,
...options
});
try {
const response = await fetch(
`${BASE_URL}/api/jkt48/chat-stream/${username}/${slug}?${params}`
);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
} catch (error) {
console.error('Failed to fetch chat stream:', error);
throw error;
}
}
// Get recent chat messages
async function getRecentMessages(username, slug, limit = 20) {
return await getChatStream(username, slug, { limit });
}
// Poll for new messages
async function pollChatMessages(username, slug, callback) {
let lastTimestamp = new Date().toISOString();
const poll = async () => {
try {
const data = await getChatStream(username, slug, {
since: lastTimestamp,
limit: 10
});
if (data.chat_messages.length > 0) {
callback(data.chat_messages);
lastTimestamp = data.chat_messages[0].timestamp;
}
} catch (error) {
console.error('Polling error:', error);
}
};
// Poll every 2 seconds
setInterval(poll, 2000);
poll(); // Initial call
}
// Format chat message
function formatChatMessage(message) {
const time = new Date(message.timestamp).toLocaleTimeString('id-ID');
const badge = message.user.badge === 'streamer' ? '👑' :
message.user.badge === 'subscriber' ? '⭐' : '';
return {
id: message.id,
time: time,
user: `${badge} ${message.user.display_name}`,
text: message.message,
reactions: message.reactions,
isPinned: message.is_pinned,
isStreamer: message.type === 'streamer_reply'
};
}
// Display live chat
async function displayLiveChat(username, slug) {
try {
const data = await getChatStream(username, slug);
console.log(`=== LIVE CHAT: ${data.stream_info.title} ===`);
console.log(`Viewers: ${data.stream_info.viewers} | Duration: ${data.stream_info.duration}`);
console.log(`Messages/min: ${data.stats.messages_per_minute}`);
console.log('Top emojis:', data.stats.top_emojis.join(' '));
console.log('\n--- RECENT MESSAGES ---');
data.chat_messages.forEach(msg => {
const formatted = formatChatMessage(msg);
const pin = formatted.isPinned ? '📌 ' : '';
console.log(`${pin}[${formatted.time}] ${formatted.user}: ${formatted.text}`);
if (formatted.reactions > 0) {
console.log(` ❤️ ${formatted.reactions} reactions`);
}
});
} catch (error) {
console.error('Error displaying chat:', error);
}
}
// Chat analytics
function analyzeChatData(chatData) {
const messages = chatData.chat_messages;
return {
totalMessages: messages.length,
uniqueUsers: new Set(messages.map(m => m.user.username)).size,
streamerMessages: messages.filter(m => m.type === 'streamer_reply').length,
averageReactions: messages.reduce((sum, m) => sum + m.reactions, 0) / messages.length,
mostActiveUser: getMostActiveUser(messages),
commonWords: getCommonWords(messages.map(m => m.message))
};
}
// Real-time chat monitor
class ChatMonitor {
constructor(username, slug) {
this.username = username;
this.slug = slug;
this.isMonitoring = false;
}
start(onMessage) {
if (this.isMonitoring) return;
this.isMonitoring = true;
pollChatMessages(this.username, this.slug, (messages) => {
messages.forEach(msg => {
const formatted = formatChatMessage(msg);
onMessage(formatted);
});
});
}
stop() {
this.isMonitoring = false;
}
}
import requests
import time
from datetime import datetime
from collections import Counter
API_KEY = 'YOUR_API_KEY'
BASE_URL = 'https://v2.jkt48connect.my.id'
def get_chat_stream(username, slug, **options):
"""Fetch chat stream data"""
params = {'apikey': API_KEY, **options}
url = f"{BASE_URL}/api/jkt48/chat-stream/{username}/{slug}"
try:
response = requests.get(url, params=params)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f"Error fetching chat stream: {e}")
raise
def get_recent_messages(username, slug, limit=20):
"""Get recent chat messages"""
return get_chat_stream(username, slug, limit=limit)
def format_chat_message(message):
"""Format chat message for display"""
time_str = datetime.fromisoformat(message['timestamp'].replace('Z', '+00:00')).strftime('%H:%M:%S')
badge = '👑' if message['user']['badge'] == 'streamer' else '⭐' if message['user']['badge'] == 'subscriber' else ''
return {
'id': message['id'],
'time': time_str,
'user': f"{badge} {message['user']['display_name']}",
'text': message['message'],
'reactions': message['reactions'],
'is_pinned': message['is_pinned'],
'is_streamer': message['type'] == 'streamer_reply'
}
def display_live_chat(username, slug):
"""Display live chat messages"""
try:
data = get_chat_stream(username, slug)
print(f"=== LIVE CHAT: {data['stream_info']['title']} ===")
print(f"Viewers: {data['stream_info']['viewers']} | Duration: {data['stream_info']['duration']}")
print(f"Messages/min: {data['stats']['messages_per_minute']}")
print(f"Top emojis: {' '.join(data['stats']['top_emojis'])}")
print("\n--- RECENT MESSAGES ---")
for msg in data['chat_messages']:
formatted = format_chat_message(msg)
pin = '📌 ' if formatted['is_pinned'] else ''
print(f"{pin}[{formatted['time']}] {formatted['user']}: {formatted['text']}")
if formatted['reactions'] > 0:
print(f" ❤️ {formatted['reactions']} reactions")
except Exception as e:
print(f"Error displaying chat: {e}")
def monitor_chat(username, slug, duration=60):
"""Monitor chat for specified duration"""
start_time = time.time()
last_timestamp = datetime.now().isoformat()
print(f"Monitoring chat for {duration} seconds...")
while time.time() - start_time < duration:
try:
data = get_chat_stream(username, slug, since=last_timestamp, limit=5)
if data['chat_messages']:
for msg in data['chat_messages']:
formatted = format_chat_message(msg)
print(f"🔴 [{formatted['time']}] {formatted['user']}: {formatted['text']}")
last_timestamp = data['chat_messages'][0]['timestamp']
time.sleep(3) # Wait 3 seconds
except Exception as e:
print(f"Monitoring error: {e}")
time.sleep(5)
def analyze_chat_data(chat_data):
"""Analyze chat statistics"""
messages = chat_data['chat_messages']
usernames = [msg['user']['username'] for msg in messages]
reactions = [msg['reactions'] for msg in messages]
return {
'total_messages': len(messages),
'unique_users': len(set(usernames)),
'streamer_messages': len([m for m in messages if m['type'] == 'streamer_reply']),
'average_reactions': sum(reactions) / len(reactions) if reactions else 0,
'most_active_user': Counter(usernames).most_common(1)[0] if usernames else None
}
# Usage example
if __name__ == "__main__":
# Example: Display chat for a live stream
display_live_chat("freya.jkt48", "weekend-chat-session")
package main
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"time"
)
const (
APIKey = "YOUR_API_KEY"
BaseURL = "https://v2.jkt48connect.my.id"
)
type ChatStreamData struct {
StreamInfo StreamInfo `json:"stream_info"`
ChatMessages []ChatMessage `json:"chat_messages"`
Stats ChatStats `json:"stats"`
}
type StreamInfo struct {
Username string `json:"username"`
Slug string `json:"slug"`
Title string `json:"title"`
Status string `json:"status"`
Viewers int `json:"viewers"`
Duration string `json:"duration"`
}
type ChatMessage struct {
ID string `json:"id"`
User ChatUser `json:"user"`
Message string `json:"message"`
Timestamp string `json:"timestamp"`
Type string `json:"type"`
Reactions int `json:"reactions"`
IsPinned bool `json:"is_pinned"`
}
type ChatUser struct {
Username string `json:"username"`
DisplayName string `json:"display_name"`
Avatar string `json:"avatar"`
Badge string `json:"badge"`
}
type ChatStats struct {
TotalMessages int `json:"total_messages"`
UniqueUsers int `json:"unique_users"`
MessagesPerMinute float64 `json:"messages_per_minute"`
TopEmojis []string `json:"top_emojis"`
}
func getChatStream(username, slug string, options map[string]string) (*ChatStreamData, error) {
params := url.Values{}
params.Add("apikey", APIKey)
for key, value := range options {
params.Add(key, value)
}
url := fmt.Sprintf("%s/api/jkt48/chat-stream/%s/%s?%s", BaseURL, username, slug, params.Encode())
client := &http.Client{Timeout: 30 * time.Second}
resp, err := client.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("API request failed with status: %d", resp.StatusCode)
}
var data ChatStreamData
err = json.NewDecoder(resp.Body).Decode(&data)
return &data, err
}
func formatChatMessage(message ChatMessage) map[string]interface{} {
timestamp, _ := time.Parse(time.RFC3339, message.Timestamp)
timeStr := timestamp.Format("15:04:05")
badge := ""
switch message.User.Badge {
case "streamer":
badge = "👑"
case "subscriber":
badge = "⭐"
}
return map[string]interface{}{
"id": message.ID,
"time": timeStr,
"user": fmt.Sprintf("%s %s", badge, message.User.DisplayName),
"text": message.Message,
"reactions": message.Reactions,
"is_pinned": message.IsPinned,
"is_streamer": message.Type == "streamer_reply",
}
}
func displayLiveChat(username, slug string) {
data, err := getChatStream(username, slug, nil)
if err != nil {
fmt.Printf("Error fetching chat stream: %v\n", err)
return
}
fmt.Printf("=== LIVE CHAT: %s ===\n", data.StreamInfo.Title)
fmt.Printf("Viewers: %d | Duration: %s\n", data.StreamInfo.Viewers, data.StreamInfo.Duration)
fmt.Printf("Messages/min: %.1f\n", data.Stats.MessagesPerMinute)
fmt.Printf("Top emojis: %v\n", data.Stats.TopEmojis)
fmt.Println("\n--- RECENT MESSAGES ---")
for _, msg := range data.ChatMessages {
formatted := formatChatMessage(msg)
pin := ""
if formatted["is_pinned"].(bool) {
pin = "📌 "
}
fmt.Printf("%s[%s] %s: %s\n",
pin,
formatted["time"],
formatted["user"],
formatted["text"])
if formatted["reactions"].(int) > 0 {
fmt.Printf(" ❤️ %d reactions\n", formatted["reactions"])
}
}
}
func monitorChat(username, slug string, duration time.Duration) {
fmt.Printf("Monitoring chat for %v...\n", duration)
start := time.Now()
lastTimestamp := time.Now().Format(time.RFC3339)
for time.Since(start) < duration {
options := map[string]string{
"since": lastTimestamp,
"limit": "5",
}
data, err := getChatStream(username, slug, options)
if err != nil {
fmt.Printf("Monitoring error: %v\n", err)
time.Sleep(5 * time.Second)
continue
}
if len(data.ChatMessages) > 0 {
for _, msg := range data.ChatMessages {
formatted := formatChatMessage(msg)
fmt.Printf("🔴 [%s] %s: %s\n",
formatted["time"],
formatted["user"],
formatted["text"])
}
lastTimestamp = data.ChatMessages[0].Timestamp
}
time.Sleep(3 * time.Second)
}
}
func main() {
// Example: Display chat for a live stream
displayLiveChat("freya.jkt48", "weekend-chat-session")
}
Real-time Integration
// Simple polling for new messages
async function startChatPolling(username, slug) {
let lastCheck = new Date().toISOString();
setInterval(async () => {
try {
const data = await getChatStream(username, slug, {
since: lastCheck,
limit: 10
});
if (data.chat_messages.length > 0) {
console.log(`📨 ${data.chat_messages.length} new messages`);
data.chat_messages.forEach(msg => {
console.log(`💬 ${msg.user.display_name}: ${msg.message}`);
});
lastCheck = data.chat_messages[0].timestamp;
}
} catch (error) {
console.error('Polling failed:', error);
}
}, 3000);
}
// Advanced chat monitoring
class LiveChatMonitor {
constructor(username, slug) {
this.username = username;
this.slug = slug;
this.subscribers = [];
}
subscribe(callback) {
this.subscribers.push(callback);
}
async start() {
const data = await getChatStream(this.username, this.slug);
// Notify subscribers of initial data
this.subscribers.forEach(callback => {
callback('init', data);
});
// Start polling for updates
this.poll();
}
async poll() {
// Implementation for continuous polling
}
}
// Chat analytics and insights
function generateChatAnalytics(chatData) {
const messages = chatData.chat_messages;
return {
engagement: {
messagesPerMinute: chatData.stats.messages_per_minute,
averageReactions: messages.reduce((sum, m) => sum + m.reactions, 0) / messages.length,
pinnedMessages: messages.filter(m => m.is_pinned).length
},
users: {
total: chatData.stats.unique_users,
subscribers: messages.filter(m => m.user.badge === 'subscriber').length,
activeUsers: getActiveUsers(messages)
},
content: {
topEmojis: chatData.stats.top_emojis,
streamerInteraction: messages.filter(m => m.type === 'streamer_reply').length,
sentiment: analyzeSentiment(messages.map(m => m.message))
}
};
}
Error Handling
async function safeChatStream(username, slug, options = {}) {
try {
const data = await getChatStream(username, slug, options);
// Validate response
if (!data.chat_messages || !Array.isArray(data.chat_messages)) {
throw new Error('Invalid chat data structure');
}
return data;
} catch (error) {
console.error(`Chat stream error for ${username}/${slug}:`, error);
return {
stream_info: { username, slug, status: 'error' },
chat_messages: [],
stats: { total_messages: 0, unique_users: 0 },
error: error.message
};
}
}
Integration Example
Combine live data and chat stream for complete experience:
// Get live streams and their chat
async function getLiveStreamsWithChat() {
const liveData = await getJKT48Live();
const streamsWithChat = await Promise.all(
liveData.live_streams.map(async (stream) => {
const chatData = await getChatStream(stream.username, stream.slug, { limit: 10 });
return {
...stream,
recentChat: chatData.chat_messages,
chatStats: chatData.stats
};
})
);
return streamsWithChat;
}
Get your API key from JKT48Connect and start building live chat applications!
How is this guide?
Last updated on