Skip to content

Instantly share code, notes, and snippets.

@charisTheo
Created December 1, 2021 12:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save charisTheo/7f494109392ddab97a7bb5adb0f06a5e to your computer and use it in GitHub Desktop.
Save charisTheo/7f494109392ddab97a7bb5adb0f06a5e to your computer and use it in GitHub Desktop.
This script migrates all messages and users from a Sendbird supergroup channel to a group channel
/*
* This script migrates all messages and users
* from a Sendbird supergroup channel to a group channel
* by creating a new group channel and deleting the old supergroup channel.
*
* Note: make sure you have chat history enabled for newly joined members
* Enable it by selecting an App from the Dashboard and toggle the checkbox under
* Settings > Chat > Group Channels > Chat history
*
* To use this script, you need to change APP_ID and API_TOKEN below
* You should already have a supergroup channel created and use its URL in SUPERGROUP_CHANNEL_URL
*/
import fetch from 'node-fetch';
const APP_ID = "<YOUR APP ID HERE>";
const API_TOKEN = "<YOUR API TOKEN HERE>";
const SUPERGROUP_CHANNEL_URL = "breathing_channel";
/**
* Docs: https://sendbird.com/docs/chat/v3/platform-api/guides/group-channel#2-view-a-channel
*
* @param {String} channelUrl
* @returns {Object | null} - channel object or null if error
*/
async function getSupergroupChannel(channelUrl) {
const requestUrl = `https://api-${APP_ID}.sendbird.com/v3/group_channels/${channelUrl}?show_member=true`;
try {
const response = await fetch(requestUrl, {
headers: {
'Api-Token': API_TOKEN
}
})
if (response.status !== 200) {
console.log(`\n❌ getSupergroupChannel - Failed request: ${response.status} - ${response.statusText}`)
return null
}
const channel = await response.json()
console.log(`\n✅ Retrieved a supergroup channel with URL: ${channel.channel_url}.`)
return channel
} catch (error) {
console.error(`\n❌ getSupergroupChannel Error: ${error}`)
return null
}
}
/**
* Adds a delay before creating the new channel
* which will be using the same URL as the previously deleted channel
* Docs: https://sendbird.com/docs/chat/v3/platform-api/guides/group-channel#2-create-a-channel
*
* @param {Object} oldChannel
* @returns {Object | null} - new channel object or null if error
*/
function createGroupChannel(oldChannel) {
return new Promise(resolve => {
const requestUrl = `https://api-${APP_ID}.sendbird.com/v3/group_channels`;
setTimeout(async () => {
try {
const invitation_status = oldChannel.members.reduce((acc, cur) => {
acc[cur.user_id] = 'joined'
return acc
}, {})
const response = await fetch(requestUrl, {
method: 'POST',
headers: {
'Api-Token': API_TOKEN,
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: oldChannel.name,
cover_url: oldChannel.cover_url,
channel_url: oldChannel.channel_url,
user_ids: oldChannel.members.map(member => member.user_id),
invitation_status,
// Additionally you can set here the hidden_status to 'hidden_allow_auto_unhide'
// and only show the channel in user's channel list after full migration is complete
is_super: false,
is_distinct: true
})
})
if (response.status !== 200) {
console.log(`\n❌ createGroupChannel - Failed request: ${response.status} - ${response.statusText}`)
resolve(null)
return
}
const channel = await response.json()
console.log(`\n🆕 Created a new group channel with URL: ${channel.channel_url}.`)
resolve(channel)
}
catch (error) {
console.error(`\n❌ createGroupChannel Error: ${error}`)
resolve(null)
}
}, 2000)
})
}
/**
* Extra: Use include_reactions=true in requestUrl to migrate reactions
* Docs: https://sendbird.com/docs/chat/v3/platform-api/guides/messages#2-list-messages
*
* @param {Object} channel
* @returns {Array<Object> | null} - array of message objects or null if error
*/
async function getGroupChannelMessages(channel) {
const requestUrl = `https://api-${APP_ID}.sendbird.com/v3/group_channels/${channel.channel_url}/messages?message_ts=${channel.last_message.created_at}&prev_limit=200`;
try {
const response = await fetch(requestUrl, {
headers: {
'Api-Token': API_TOKEN
}
})
if (response.status !== 200) {
console.log(`\n❌ getGroupChannelMessages - Failed request: ${response.status} - ${response.statusText}`)
return null
}
const { messages } = await response.json()
console.log(`\n💬 Retrieved ${messages.length} messages.`)
return messages
}
catch (error) {
console.error(`\n❌ getGroupChannelMessages Error: ${error}`)
return null
}
}
/**
* Using Migration API to migrate messages
* Note: The maximum number of migrated messages per call is 100
* Docs: https://sendbird.com/docs/chat/v3/platform-api/guides/migration
*
* @param {String} channelUrl
* @returns {Boolean} - true if success, false if error
*/
async function migrateMessagesToChannel(messages, channelUrl) {
if (!messages || !messages?.length) {
return false
}
const requestUrl = `https://api-${APP_ID}.sendbird.com/v3/migration/${channelUrl}`;
// TODO: do separate calls for admin and file messages
// Admin messages: https://sendbird.com/docs/chat/v3/platform-api/guides/migration#4-list-of-properties-for-admin-message
// const adminMessages = messages.filter(message => message.message_type === "ADMM")
// File messages: https://sendbird.com/docs/chat/v3/platform-api/guides/migration#4-list-of-properties-for-file-message
// const fileMessages = messages.filter(message => message.message_type === "FILE")
const userMessages = messages.reduce((acc, cur) => {
if (cur.type === "MESG") {
acc.push({
user_id: cur.user.user_id,
message_type: cur.type,
message: cur.message,
timestamp: cur.created_at,
})
}
return acc
}, [])
try {
const response = await fetch(requestUrl, {
method: 'POST',
headers: {
'Api-Token': API_TOKEN,
'Content-Type': 'application/json'
},
body: JSON.stringify({
messages: userMessages,
update_read_ts: false,
rewind_read_ts: false
})
})
if (response.status !== 200) {
console.log(`\n❌ migrateMessagesToChannel - Failed request: ${response.status} - ${response.statusText}`)
return false
}
console.log(`\n💬 Migrated ${messages.length} messages.`)
return true
}
catch (error) {
console.error(`\n❌ migrateMessages Error: ${error}`)
return false
}
}
/**
* Docs: https://sendbird.com/docs/chat/v3/platform-api/guides/group-channel#2-delete-a-channel
*
* @param {String} channelUrl
* @returns {Boolean} - true if success, false if error
*/
async function deletePreviousChannel(channelUrl) {
const requestUrl = `https://api-${APP_ID}.sendbird.com/v3/group_channels/${channelUrl}`;
try {
const response = await fetch(requestUrl, {
method: 'DELETE',
headers: {
'Api-Token': API_TOKEN,
'Content-Type': 'application/json'
}
})
if (response.status !== 200) {
console.log(`\n❌ deletePreviousChannel - Failed request: ${response.status} - ${response.statusText}`)
return false
}
console.log(`\n🗑 Deleted channel with URL: ${channelUrl}`)
return true
} catch (error) {
console.error(`\n❌ deletePreviousChannel - Error: ${error}`)
return false
}
}
(async function() {
const supergroupChannel = await getSupergroupChannel(SUPERGROUP_CHANNEL_URL)
if (!supergroupChannel) {
return
}
const messages = await getGroupChannelMessages(supergroupChannel)
const deletedPrevious = await deletePreviousChannel(SUPERGROUP_CHANNEL_URL)
if (!deletedPrevious) {
return
}
const groupChannel = await createGroupChannel(supergroupChannel)
if (!groupChannel) {
return
}
const migrated = await migrateMessagesToChannel(messages, groupChannel.channel_url)
if (!migrated) {
console.log(`\n❌ Migration failed`)
return
}
console.log(`\n✅ Migration completed`)
})()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment