parent
ec0683dd0c
commit
ea872897c1
@ -1,3 +0,0 @@ |
||||
Importer.addImporter 'slack', Importer.Slack, |
||||
name: 'Slack' |
||||
mimeType: 'application/zip' |
||||
@ -0,0 +1,5 @@ |
||||
/* globals Importer */ |
||||
Importer.addImporter('slack', Importer.Slack, { |
||||
name: 'Slack', |
||||
mimeType: 'application/zip' |
||||
}); |
||||
@ -1,373 +0,0 @@ |
||||
Importer.Slack = class Importer.Slack extends Importer.Base |
||||
constructor: (name, descriptionI18N, mimeType) -> |
||||
super(name, descriptionI18N, mimeType) |
||||
@userTags = [] |
||||
@bots = {} |
||||
@logger.debug('Constructed a new Slack Importer.') |
||||
|
||||
prepare: (dataURI, sentContentType, fileName) => |
||||
super(dataURI, sentContentType, fileName) |
||||
|
||||
{image, contentType} = RocketChatFile.dataURIParse dataURI |
||||
zip = new @AdmZip(new Buffer(image, 'base64')) |
||||
zipEntries = zip.getEntries() |
||||
|
||||
tempChannels = [] |
||||
tempUsers = [] |
||||
tempMessages = {} |
||||
for entry in zipEntries |
||||
do (entry) => |
||||
if entry.entryName.indexOf('__MACOSX') > -1 |
||||
#ignore all of the files inside of __MACOSX |
||||
@logger.debug("Ignoring the file: #{entry.entryName}") |
||||
else if entry.entryName == 'channels.json' |
||||
@updateProgress Importer.ProgressStep.PREPARING_CHANNELS |
||||
tempChannels = JSON.parse entry.getData().toString() |
||||
tempChannels = tempChannels.filter (channel) -> channel.creator? |
||||
else if entry.entryName == 'users.json' |
||||
@updateProgress Importer.ProgressStep.PREPARING_USERS |
||||
tempUsers = JSON.parse entry.getData().toString() |
||||
|
||||
for user in tempUsers when user.is_bot |
||||
@bots[user.profile.bot_id] = user |
||||
|
||||
else if not entry.isDirectory and entry.entryName.indexOf('/') > -1 |
||||
item = entry.entryName.split('/') #random/2015-10-04.json |
||||
channelName = item[0] #random |
||||
msgGroupData = item[1].split('.')[0] #2015-10-04 |
||||
if not tempMessages[channelName] |
||||
tempMessages[channelName] = {} |
||||
# Catch files which aren't valid JSON files, ignore them |
||||
try |
||||
tempMessages[channelName][msgGroupData] = JSON.parse entry.getData().toString() |
||||
catch |
||||
@logger.warn "#{entry.entryName} is not a valid JSON file! Unable to import it." |
||||
|
||||
# Insert the users record, eventually this might have to be split into several ones as well |
||||
# if someone tries to import a several thousands users instance |
||||
usersId = @collection.insert { 'import': @importRecord._id, 'importer': @name, 'type': 'users', 'users': tempUsers } |
||||
@users = @collection.findOne usersId |
||||
@updateRecord { 'count.users': tempUsers.length } |
||||
@addCountToTotal tempUsers.length |
||||
|
||||
# Insert the channels records. |
||||
channelsId = @collection.insert { 'import': @importRecord._id, 'importer': @name, 'type': 'channels', 'channels': tempChannels } |
||||
@channels = @collection.findOne channelsId |
||||
@updateRecord { 'count.channels': tempChannels.length } |
||||
@addCountToTotal tempChannels.length |
||||
|
||||
# Insert the messages records |
||||
@updateProgress Importer.ProgressStep.PREPARING_MESSAGES |
||||
messagesCount = 0 |
||||
for channel, messagesObj of tempMessages |
||||
do (channel, messagesObj) => |
||||
if not @messages[channel] |
||||
@messages[channel] = {} |
||||
for date, msgs of messagesObj |
||||
messagesCount += msgs.length |
||||
@updateRecord { 'messagesstatus': "#{channel}/#{date}" } |
||||
|
||||
if Importer.Base.getBSONSize(msgs) > Importer.Base.MaxBSONSize |
||||
for splitMsg, i in Importer.Base.getBSONSafeArraysFromAnArray(msgs) |
||||
messagesId = @collection.insert { 'import': @importRecord._id, 'importer': @name, 'type': 'messages', 'name': "#{channel}/#{date}.#{i}", 'messages': splitMsg } |
||||
@messages[channel]["#{date}.#{i}"] = @collection.findOne messagesId |
||||
else |
||||
messagesId = @collection.insert { 'import': @importRecord._id, 'importer': @name, 'type': 'messages', 'name': "#{channel}/#{date}", 'messages': msgs } |
||||
@messages[channel][date] = @collection.findOne messagesId |
||||
|
||||
@updateRecord { 'count.messages': messagesCount, 'messagesstatus': null } |
||||
@addCountToTotal messagesCount |
||||
|
||||
if tempUsers.length is 0 or tempChannels.length is 0 or messagesCount is 0 |
||||
@logger.warn "The loaded users count #{tempUsers.length}, the loaded channels #{tempChannels.length}, and the loaded messages #{messagesCount}" |
||||
@updateProgress Importer.ProgressStep.ERROR |
||||
return @getProgress() |
||||
|
||||
selectionUsers = tempUsers.map (user) -> |
||||
return new Importer.SelectionUser user.id, user.name, user.profile.email, user.deleted, user.is_bot, !user.is_bot |
||||
selectionChannels = tempChannels.map (channel) -> |
||||
return new Importer.SelectionChannel channel.id, channel.name, channel.is_archived, true, false |
||||
|
||||
@updateProgress Importer.ProgressStep.USER_SELECTION |
||||
return new Importer.Selection @name, selectionUsers, selectionChannels |
||||
|
||||
startImport: (importSelection) => |
||||
super(importSelection) |
||||
start = Date.now() |
||||
|
||||
for user in importSelection.users |
||||
for u in @users.users when u.id is user.user_id |
||||
u.do_import = user.do_import |
||||
@collection.update { _id: @users._id }, { $set: { 'users': @users.users }} |
||||
|
||||
for channel in importSelection.channels |
||||
for c in @channels.channels when c.id is channel.channel_id |
||||
c.do_import = channel.do_import |
||||
@collection.update { _id: @channels._id }, { $set: { 'channels': @channels.channels }} |
||||
|
||||
startedByUserId = Meteor.userId() |
||||
Meteor.defer => |
||||
@updateProgress Importer.ProgressStep.IMPORTING_USERS |
||||
for user in @users.users when user.do_import |
||||
do (user) => |
||||
Meteor.runAsUser startedByUserId, () => |
||||
existantUser = RocketChat.models.Users.findOneByEmailAddress user.profile.email |
||||
if not existantUser |
||||
existantUser = RocketChat.models.Users.findOneByUsername user.name |
||||
|
||||
if existantUser |
||||
user.rocketId = existantUser._id |
||||
RocketChat.models.Users.update { _id: user.rocketId }, { $addToSet: { importIds: user.id } } |
||||
@userTags.push |
||||
slack: "<@#{user.id}>" |
||||
slackLong: "<@#{user.id}|#{user.name}>" |
||||
rocket: "@#{existantUser.username}" |
||||
else |
||||
if user.profile.email |
||||
userId = Accounts.createUser { email: user.profile.email, password: Date.now() + user.name + user.profile.email.toUpperCase() } |
||||
else |
||||
userId = Accounts.createUser { username: user.name, password: Date.now() + user.name, joinDefaultChannelsSilenced: true } |
||||
Meteor.runAsUser userId, () => |
||||
Meteor.call 'setUsername', user.name, {joinDefaultChannelsSilenced: true} |
||||
url = null |
||||
if user.profile.image_original |
||||
url = user.profile.image_original |
||||
else if user.profile.image_512 |
||||
url = user.profile.image_512 |
||||
|
||||
try |
||||
Meteor.call 'setAvatarFromService', url, undefined, 'url' |
||||
catch error |
||||
this.logger.warn "Failed to set #{user.name}'s avatar from url #{url}" |
||||
|
||||
# Slack's is -18000 which translates to Rocket.Chat's after dividing by 3600 |
||||
if user.tz_offset |
||||
Meteor.call 'userSetUtcOffset', user.tz_offset / 3600 |
||||
|
||||
RocketChat.models.Users.update { _id: userId }, { $addToSet: { importIds: user.id } } |
||||
|
||||
if user.profile.real_name |
||||
RocketChat.models.Users.setName userId, user.profile.real_name |
||||
#Deleted users are 'inactive' users in Rocket.Chat |
||||
if user.deleted |
||||
Meteor.call 'setUserActiveStatus', userId, false |
||||
#TODO: Maybe send emails? |
||||
user.rocketId = userId |
||||
@userTags.push |
||||
slack: "<@#{user.id}>" |
||||
slackLong: "<@#{user.id}|#{user.name}>" |
||||
rocket: "@#{user.name}" |
||||
@addCountCompleted 1 |
||||
@collection.update { _id: @users._id }, { $set: { 'users': @users.users }} |
||||
|
||||
@updateProgress Importer.ProgressStep.IMPORTING_CHANNELS |
||||
for channel in @channels.channels when channel.do_import |
||||
do (channel) => |
||||
Meteor.runAsUser startedByUserId, () => |
||||
existantRoom = RocketChat.models.Rooms.findOneByName channel.name |
||||
if existantRoom or channel.is_general |
||||
if channel.is_general and channel.name isnt existantRoom?.name |
||||
Meteor.call 'saveRoomSettings', 'GENERAL', 'roomName', channel.name |
||||
channel.rocketId = if channel.is_general then 'GENERAL' else existantRoom._id |
||||
RocketChat.models.Rooms.update { _id: channel.rocketId }, { $addToSet: { importIds: channel.id } } |
||||
else |
||||
users = [] |
||||
for member in channel.members when member isnt channel.creator |
||||
user = @getRocketUser member |
||||
if user? |
||||
users.push user.username |
||||
|
||||
userId = startedByUserId |
||||
for user in @users.users when user.id is channel.creator and user.do_import |
||||
userId = user.rocketId |
||||
|
||||
Meteor.runAsUser userId, () => |
||||
returned = Meteor.call 'createChannel', channel.name, users |
||||
channel.rocketId = returned.rid |
||||
|
||||
# @TODO implement model specific function |
||||
roomUpdate = |
||||
ts: new Date(channel.created * 1000) |
||||
|
||||
if not _.isEmpty channel.topic?.value |
||||
roomUpdate.topic = channel.topic.value |
||||
|
||||
if not _.isEmpty(channel.purpose?.value) |
||||
roomUpdate.description = channel.purpose.value |
||||
|
||||
RocketChat.models.Rooms.update { _id: channel.rocketId }, { $set: roomUpdate, $addToSet: { importIds: channel.id } } |
||||
|
||||
@addCountCompleted 1 |
||||
@collection.update { _id: @channels._id }, { $set: { 'channels': @channels.channels }} |
||||
|
||||
missedTypes = {} |
||||
ignoreTypes = { 'bot_add': true, 'file_comment': true, 'file_mention': true } |
||||
@updateProgress Importer.ProgressStep.IMPORTING_MESSAGES |
||||
for channel, messagesObj of @messages |
||||
do (channel, messagesObj) => |
||||
Meteor.runAsUser startedByUserId, () => |
||||
slackChannel = @getSlackChannelFromName channel |
||||
if slackChannel?.do_import |
||||
room = RocketChat.models.Rooms.findOneById slackChannel.rocketId, { fields: { usernames: 1, t: 1, name: 1 } } |
||||
for date, msgs of messagesObj |
||||
@updateRecord { 'messagesstatus': "#{channel}/#{date}.#{msgs.messages.length}" } |
||||
for message in msgs.messages |
||||
msgDataDefaults = |
||||
_id: "slack-#{slackChannel.id}-#{message.ts.replace(/\./g, '-')}" |
||||
ts: new Date(parseInt(message.ts.split('.')[0]) * 1000) |
||||
|
||||
if message.type is 'message' |
||||
if message.subtype? |
||||
if message.subtype is 'channel_join' |
||||
if @getRocketUser(message.user)? |
||||
RocketChat.models.Messages.createUserJoinWithRoomIdAndUser room._id, @getRocketUser(message.user), msgDataDefaults |
||||
else if message.subtype is 'channel_leave' |
||||
if @getRocketUser(message.user)? |
||||
RocketChat.models.Messages.createUserLeaveWithRoomIdAndUser room._id, @getRocketUser(message.user), msgDataDefaults |
||||
else if message.subtype is 'me_message' |
||||
msgObj = |
||||
msg: "_#{@convertSlackMessageToRocketChat(message.text)}_" |
||||
_.extend msgObj, msgDataDefaults |
||||
RocketChat.sendMessage @getRocketUser(message.user), msgObj, room, true |
||||
else if message.subtype is 'bot_message' or message.subtype is 'slackbot_response' |
||||
botUser = RocketChat.models.Users.findOneById 'rocket.cat', { fields: { username: 1 }} |
||||
botUsername = if @bots[message.bot_id] then @bots[message.bot_id]?.name else message.username |
||||
msgObj = |
||||
msg: @convertSlackMessageToRocketChat(message.text) |
||||
rid: room._id |
||||
bot: true |
||||
attachments: message.attachments |
||||
username: if botUsername then botUsername else undefined |
||||
|
||||
_.extend msgObj, msgDataDefaults |
||||
|
||||
if message.edited? |
||||
msgObj.editedAt = new Date(parseInt(message.edited.ts.split('.')[0]) * 1000) |
||||
editedBy = @getRocketUser(message.edited.user) |
||||
if editedBy? |
||||
msgObj.editedBy = |
||||
_id: editedBy._id |
||||
username: editedBy.username |
||||
|
||||
if message.icons? |
||||
msgObj.emoji = message.icons.emoji |
||||
|
||||
RocketChat.sendMessage botUser, msgObj, room, true |
||||
else if message.subtype is 'channel_purpose' |
||||
if @getRocketUser(message.user)? |
||||
RocketChat.models.Messages.createRoomSettingsChangedWithTypeRoomIdMessageAndUser 'room_changed_description', room._id, message.purpose, @getRocketUser(message.user), msgDataDefaults |
||||
else if message.subtype is 'channel_topic' |
||||
if @getRocketUser(message.user)? |
||||
RocketChat.models.Messages.createRoomSettingsChangedWithTypeRoomIdMessageAndUser 'room_changed_topic', room._id, message.topic, @getRocketUser(message.user), msgDataDefaults |
||||
else if message.subtype is 'channel_name' |
||||
if @getRocketUser(message.user)? |
||||
RocketChat.models.Messages.createRoomRenamedWithRoomIdRoomNameAndUser room._id, message.name, @getRocketUser(message.user), msgDataDefaults |
||||
else if message.subtype is 'pinned_item' |
||||
if message.attachments |
||||
msgObj = |
||||
attachments: [ |
||||
"text" : @convertSlackMessageToRocketChat message.attachments[0].text |
||||
"author_name" : message.attachments[0].author_subname |
||||
"author_icon" : getAvatarUrlFromUsername(message.attachments[0].author_subname) |
||||
] |
||||
_.extend msgObj, msgDataDefaults |
||||
RocketChat.models.Messages.createWithTypeRoomIdMessageAndUser 'message_pinned', room._id, '', @getRocketUser(message.user), msgObj |
||||
else |
||||
#TODO: make this better |
||||
@logger.debug('Pinned item with no attachment, needs work.'); |
||||
#RocketChat.models.Messages.createWithTypeRoomIdMessageAndUser 'message_pinned', room._id, '', @getRocketUser(message.user), msgDataDefaults |
||||
else if message.subtype is 'file_share' |
||||
if message.file?.url_private_download isnt undefined |
||||
details = |
||||
message_id: "slack-#{message.ts.replace(/\./g, '-')}" |
||||
name: message.file.name |
||||
size: message.file.size |
||||
type: message.file.mimetype |
||||
rid: room._id |
||||
@uploadFile details, message.file.url_private_download, @getRocketUser(message.user), room, new Date(parseInt(message.ts.split('.')[0]) * 1000) |
||||
else |
||||
if not missedTypes[message.subtype] and not ignoreTypes[message.subtype] |
||||
missedTypes[message.subtype] = message |
||||
else |
||||
user = @getRocketUser(message.user) |
||||
if user? |
||||
msgObj = |
||||
msg: @convertSlackMessageToRocketChat message.text |
||||
rid: room._id |
||||
u: |
||||
_id: user._id |
||||
username: user.username |
||||
|
||||
_.extend msgObj, msgDataDefaults |
||||
|
||||
if message.edited? |
||||
msgObj.editedAt = new Date(parseInt(message.edited.ts.split('.')[0]) * 1000) |
||||
editedBy = @getRocketUser(message.edited.user) |
||||
if editedBy? |
||||
msgObj.editedBy = |
||||
_id: editedBy._id |
||||
username: editedBy.username |
||||
|
||||
RocketChat.sendMessage @getRocketUser(message.user), msgObj, room, true |
||||
|
||||
# Process the reactions |
||||
if RocketChat.models.Messages.findOneById(msgDataDefaults._id)? and message.reactions?.length > 0 |
||||
for reaction in message.reactions |
||||
for u in reaction.users |
||||
rcUser = @getRocketUser(u) |
||||
if rcUser? |
||||
Meteor.runAsUser rcUser._id, () => |
||||
Meteor.call 'setReaction', ":#{reaction.name}:", msgDataDefaults._id |
||||
|
||||
@addCountCompleted 1 |
||||
|
||||
if not _.isEmpty missedTypes |
||||
console.log 'Missed import types:', missedTypes |
||||
|
||||
@updateProgress Importer.ProgressStep.FINISHING |
||||
for channel in @channels.channels when channel.do_import and channel.is_archived |
||||
do (channel) => |
||||
Meteor.runAsUser startedByUserId, () => |
||||
Meteor.call 'archiveRoom', channel.rocketId |
||||
|
||||
@updateProgress Importer.ProgressStep.DONE |
||||
timeTook = Date.now() - start |
||||
@logger.log "Import took #{timeTook} milliseconds." |
||||
|
||||
return @getProgress() |
||||
|
||||
getSlackChannelFromName: (channelName) => |
||||
for channel in @channels.channels when channel.name is channelName |
||||
return channel |
||||
|
||||
getRocketUser: (slackId) => |
||||
for user in @users.users when user.id is slackId |
||||
return RocketChat.models.Users.findOneById user.rocketId, { fields: { username: 1, name: 1 }} |
||||
|
||||
convertSlackMessageToRocketChat: (message) => |
||||
if message? |
||||
message = message.replace /<!everyone>/g, '@all' |
||||
message = message.replace /<!channel>/g, '@all' |
||||
message = message.replace /<!here>/g, '@here' |
||||
message = message.replace />/g, '>' |
||||
message = message.replace /</g, '<' |
||||
message = message.replace /&/g, '&' |
||||
message = message.replace /:simple_smile:/g, ':smile:' |
||||
message = message.replace /:memo:/g, ':pencil:' |
||||
message = message.replace /:piggy:/g, ':pig:' |
||||
message = message.replace /:uk:/g, ':gb:' |
||||
message = message.replace /<(http[s]?:[^>]*)>/g, '$1' |
||||
for userReplace in @userTags |
||||
message = message.replace userReplace.slack, userReplace.rocket |
||||
message = message.replace userReplace.slackLong, userReplace.rocket |
||||
else |
||||
message = '' |
||||
return message |
||||
|
||||
getSelection: () => |
||||
selectionUsers = @users.users.map (user) -> |
||||
return new Importer.SelectionUser user.id, user.name, user.profile.email, user.deleted, user.is_bot, !user.is_bot |
||||
selectionChannels = @channels.channels.map (channel) -> |
||||
return new Importer.SelectionChannel channel.id, channel.name, channel.is_archived, true, false |
||||
|
||||
return new Importer.Selection @name, selectionUsers, selectionChannels |
||||
@ -0,0 +1,434 @@ |
||||
/* globals Importer */ |
||||
Importer.Slack = class extends Importer.Base { |
||||
constructor(name, descriptionI18N, mimeType) { |
||||
super(name, descriptionI18N, mimeType); |
||||
this.userTags = []; |
||||
this.bots = {}; |
||||
this.logger.debug('Constructed a new Slack Importer.'); |
||||
} |
||||
prepare(dataURI, sentContentType, fileName) { |
||||
super.prepare(dataURI, sentContentType, fileName); |
||||
const {image/*, contentType*/} = RocketChatFile.dataURIParse(dataURI); |
||||
const zip = new this.AdmZip(new Buffer(image, 'base64')); |
||||
const zipEntries = zip.getEntries(); |
||||
let tempChannels = []; |
||||
let tempUsers = []; |
||||
const tempMessages = {}; |
||||
zipEntries.forEach(entry => { |
||||
if (entry.entryName.indexOf('__MACOSX') > -1) { |
||||
return this.logger.debug(`Ignoring the file: ${ entry.entryName }`); |
||||
} |
||||
if (entry.entryName === 'channels.json') { |
||||
this.updateProgress(Importer.ProgressStep.PREPARING_CHANNELS); |
||||
tempChannels = JSON.parse(entry.getData().toString()).filter(channel => channel.creator != null); |
||||
return; |
||||
} |
||||
if (entry.entryName === 'users.json') { |
||||
this.updateProgress(Importer.ProgressStep.PREPARING_USERS); |
||||
tempUsers = JSON.parse(entry.getData().toString()); |
||||
return tempUsers.forEach(user => { |
||||
if (user.is_bot) { |
||||
this.bots[user.profile.bot_id] = user; |
||||
} |
||||
}); |
||||
} |
||||
if (!entry.isDirectory && entry.entryName.indexOf('/') > -1) { |
||||
const item = entry.entryName.split('/'); |
||||
const channelName = item[0]; |
||||
const msgGroupData = item[1].split('.')[0]; |
||||
tempMessages[channelName] = tempMessages[channelName] || {}; |
||||
try { |
||||
tempMessages[channelName][msgGroupData] = JSON.parse(entry.getData().toString()); |
||||
} catch (error) { |
||||
this.logger.warn(`${ entry.entryName } is not a valid JSON file! Unable to import it.`); |
||||
} |
||||
} |
||||
}); |
||||
|
||||
// Insert the users record, eventually this might have to be split into several ones as well
|
||||
// if someone tries to import a several thousands users instance
|
||||
const usersId = this.collection.insert({ 'import': this.importRecord._id, 'importer': this.name, 'type': 'users', 'users': tempUsers }); |
||||
this.users = this.collection.findOne(usersId); |
||||
this.updateRecord({ 'count.users': tempUsers.length }); |
||||
this.addCountToTotal(tempUsers.length); |
||||
|
||||
// Insert the channels records.
|
||||
const channelsId = this.collection.insert({ 'import': this.importRecord._id, 'importer': this.name, 'type': 'channels', 'channels': tempChannels }); |
||||
this.channels = this.collection.findOne(channelsId); |
||||
this.updateRecord({ 'count.channels': tempChannels.length }); |
||||
this.addCountToTotal(tempChannels.length); |
||||
|
||||
// Insert the messages records
|
||||
this.updateProgress(Importer.ProgressStep.PREPARING_MESSAGES); |
||||
|
||||
let messagesCount = 0; |
||||
Object.keys(tempMessages).forEach(channel => { |
||||
const messagesObj = tempMessages[channel]; |
||||
this.messages[channel] = this.messages[channel] || {}; |
||||
Object.keys(messagesObj).forEach(date => { |
||||
const msgs = messagesObj[date]; |
||||
messagesCount += msgs.length; |
||||
this.updateRecord({ 'messagesstatus': '#{channel}/#{date}' }); |
||||
if (Importer.Base.getBSONSize(msgs) > Importer.Base.MaxBSONSize) { |
||||
const tmp = Importer.Base.getBSONSafeArraysFromAnArray(msgs); |
||||
Object.keys(tmp).forEach(i => { |
||||
const splitMsg = tmp[i]; |
||||
const messagesId = this.collection.insert({ 'import': this.importRecord._id, 'importer': this.name, 'type': 'messages', 'name': `${ channel }/${ date }.${ i }`, 'messages': splitMsg }); |
||||
this.messages[channel][`${ date }.${ i }`] = this.collection.findOne(messagesId); |
||||
}); |
||||
} else { |
||||
const messagesId = this.collection.insert({ 'import': this.importRecord._id, 'importer': this.name, 'type': 'messages', 'name': `${ channel }/${ date }`, 'messages': msgs }); |
||||
this.messages[channel][date] = this.collection.findOne(messagesId); |
||||
} |
||||
}); |
||||
}); |
||||
this.updateRecord({ 'count.messages': messagesCount, 'messagesstatus': null }); |
||||
this.addCountToTotal(messagesCount); |
||||
if ([tempUsers.length, tempChannels.length, messagesCount].some(e => e === 0)) { |
||||
this.logger.warn(`The loaded users count ${ tempUsers.length }, the loaded channels ${ tempChannels.length }, and the loaded messages ${ messagesCount }`); |
||||
console.log(`The loaded users count ${ tempUsers.length }, the loaded channels ${ tempChannels.length }, and the loaded messages ${ messagesCount }`); |
||||
this.updateProgress(Importer.ProgressStep.ERROR); |
||||
return this.getProgress(); |
||||
} |
||||
const selectionUsers = tempUsers.map(user => new Importer.SelectionUser(user.id, user.name, user.profile.email, user.deleted, user.is_bot, !user.is_bot)); |
||||
const selectionChannels = tempChannels.map(channel => new Importer.SelectionChannel(channel.id, channel.name, channel.is_archived, true, false)); |
||||
this.updateProgress(Importer.ProgressStep.USER_SELECTION); |
||||
return new Importer.Selection(this.name, selectionUsers, selectionChannels); |
||||
} |
||||
startImport(importSelection) { |
||||
super.startImport(importSelection); |
||||
const start = Date.now(); |
||||
Object.keys(importSelection.users).forEach(key => { |
||||
const user = importSelection.users[key]; |
||||
Object.keys(this.users.users).forEach(k => { |
||||
const u = this.users.users[k]; |
||||
if (u.id === user.user_id) { |
||||
u.do_import = user.do_import; |
||||
} |
||||
}); |
||||
}); |
||||
this.collection.update({ _id: this.users._id }, { $set: { 'users': this.users.users }}); |
||||
Object.keys(importSelection.channels).forEach(key => { |
||||
const channel = importSelection.channels[key]; |
||||
Object.keys(this.channels.channels).forEach(k => { |
||||
const c = this.channels.channels[k]; |
||||
if (c.id === channel.channel_id) { |
||||
c.do_import = channel.do_import; |
||||
} |
||||
}); |
||||
}); |
||||
this.collection.update({ _id: this.channels._id }, { $set: { 'channels': this.channels.channels }}); |
||||
const startedByUserId = Meteor.userId(); |
||||
Meteor.defer(() => { |
||||
this.updateProgress(Importer.ProgressStep.IMPORTING_USERS); |
||||
this.users.users.forEach(user => { |
||||
if (!user.do_import) { |
||||
return; |
||||
} |
||||
Meteor.runAsUser(startedByUserId, () => { |
||||
const existantUser = RocketChat.models.Users.findOneByEmailAddress(user.profile.email) || RocketChat.models.Users.findOneByUsername(user.name); |
||||
if (existantUser) { |
||||
user.rocketId = existantUser._id; |
||||
RocketChat.models.Users.update({ _id: user.rocketId }, { $addToSet: { importIds: user.id } }); |
||||
this.userTags.push({ |
||||
slack: `<@${ user.id }>`, |
||||
slackLong: `<@${ user.id }|${ user.name }>`, |
||||
rocket: `@${ existantUser.username }` |
||||
}); |
||||
} else { |
||||
const userId = user.profile.email ? Accounts.createUser({ email: user.profile.email, password: Date.now() + user.name + user.profile.email.toUpperCase() }) : Accounts.createUser({ username: user.name, password: Date.now() + user.name, joinDefaultChannelsSilenced: true }); |
||||
Meteor.runAsUser(userId, () => { |
||||
Meteor.call('setUsername', user.name, {joinDefaultChannelsSilenced: true}); |
||||
const url = user.profile.image_original || user.profile.image_512; |
||||
try { |
||||
Meteor.call('setAvatarFromService', url, undefined, 'url'); |
||||
} catch (error) { |
||||
this.logger.warn(`Failed to set ${ user.name }'s avatar from url ${ url }`); |
||||
console.log(`Failed to set ${ user.name }'s avatar from url ${ url }`); |
||||
} |
||||
// Slack's is -18000 which translates to Rocket.Chat's after dividing by 3600
|
||||
if (user.tz_offset) { |
||||
Meteor.call('userSetUtcOffset', user.tz_offset / 3600); |
||||
} |
||||
}); |
||||
|
||||
RocketChat.models.Users.update({ _id: userId }, { $addToSet: { importIds: user.id } }); |
||||
|
||||
if (user.profile.real_name) { |
||||
RocketChat.models.Users.setName(userId, user.profile.real_name); |
||||
} |
||||
//Deleted users are 'inactive' users in Rocket.Chat
|
||||
if (user.deleted) { |
||||
Meteor.call('setUserActiveStatus', userId, false); |
||||
} |
||||
//TODO: Maybe send emails?
|
||||
user.rocketId = userId; |
||||
this.userTags.push({ |
||||
slack: `<@${ user.id }>`, |
||||
slackLong: `<@${ user.id }|${ user.name }>`, |
||||
rocket: `@${ user.name }` |
||||
}); |
||||
|
||||
} |
||||
this.addCountCompleted(1); |
||||
}); |
||||
}); |
||||
this.collection.update({ _id: this.users._id }, { $set: { 'users': this.users.users }}); |
||||
this.updateProgress(Importer.ProgressStep.IMPORTING_CHANNELS); |
||||
this.channels.channels.forEach(channel => { |
||||
if (!channel.do_import) { |
||||
return; |
||||
} |
||||
Meteor.runAsUser (startedByUserId, () => { |
||||
const existantRoom = RocketChat.models.Rooms.findOneByName(channel.name); |
||||
if (existantRoom || channel.is_general) { |
||||
if (channel.is_general && existantRoom && channel.name !== existantRoom.name) { |
||||
Meteor.call('saveRoomSettings', 'GENERAL', 'roomName', channel.name); |
||||
} |
||||
channel.rocketId = channel.is_general ? 'GENERAL' : existantRoom._id; |
||||
RocketChat.models.Rooms.update({ _id: channel.rocketId }, { $addToSet: { importIds: channel.id } }); |
||||
} else { |
||||
const users = channel.members |
||||
.reduce((ret, member) => { |
||||
if (member !== channel.creator) { |
||||
const user = this.getRocketUser(member); |
||||
if (user && user.username) { |
||||
ret.push(user.username); |
||||
} |
||||
} |
||||
return ret; |
||||
}, []); |
||||
let userId = startedByUserId; |
||||
this.users.users.forEach(user => { |
||||
if (user.id === channel.creator && user.do_import) { |
||||
userId = user.rocketId; |
||||
} |
||||
}); |
||||
Meteor.runAsUser(userId, () => { |
||||
const returned = Meteor.call('createChannel', channel.name, users); |
||||
channel.rocketId = returned.rid; |
||||
}); |
||||
|
||||
// @TODO implement model specific function
|
||||
const roomUpdate = { |
||||
ts: new Date(channel.created * 1000) |
||||
}; |
||||
if (!_.isEmpty(channel.topic && channel.topic.value)) { |
||||
roomUpdate.topic = channel.topic.value; |
||||
} |
||||
if (!_.isEmpty(channel.purpose && channel.purpose.value)) { |
||||
roomUpdate.description = channel.purpose.value; |
||||
} |
||||
RocketChat.models.Rooms.update({ _id: channel.rocketId }, { $set: roomUpdate, $addToSet: { importIds: channel.id } }); |
||||
} |
||||
this.addCountCompleted(1); |
||||
}); |
||||
}); |
||||
this.collection.update({ _id: this.channels._id }, { $set: { 'channels': this.channels.channels }}); |
||||
const missedTypes = {}; |
||||
const ignoreTypes = { 'bot_add': true, 'file_comment': true, 'file_mention': true }; |
||||
this.updateProgress(Importer.ProgressStep.IMPORTING_MESSAGES); |
||||
Object.keys(this.messages).forEach(channel => { |
||||
const messagesObj = this.messages[channel]; |
||||
|
||||
Meteor.runAsUser(startedByUserId, () =>{ |
||||
const slackChannel = this.getSlackChannelFromName(channel); |
||||
if (!slackChannel || !slackChannel.do_import) { return; } |
||||
const room = RocketChat.models.Rooms.findOneById(slackChannel.rocketId, { fields: { usernames: 1, t: 1, name: 1 } }); |
||||
Object.keys(messagesObj).forEach(date => { |
||||
const msgs = messagesObj[date]; |
||||
msgs.messages.forEach(message => { |
||||
this.updateRecord({ 'messagesstatus': '#{channel}/#{date}.#{msgs.messages.length}' }); |
||||
const msgDataDefaults ={ |
||||
_id: `slack-${ slackChannel.id }-${ message.ts.replace(/\./g, '-') }`, |
||||
ts: new Date(parseInt(message.ts.split('.')[0]) * 1000) |
||||
}; |
||||
if (message.type === 'message') { |
||||
if (message.subtype) { |
||||
if (message.subtype === 'channel_join') { |
||||
if (this.getRocketUser(message.user)) { |
||||
RocketChat.models.Messages.createUserJoinWithRoomIdAndUser(room._id, this.getRocketUser(message.user), msgDataDefaults); |
||||
} |
||||
} else if (message.subtype === 'channel_leave') { |
||||
if (this.getRocketUser(message.user)) { RocketChat.models.Messages.createUserLeaveWithRoomIdAndUser(room._id, this.getRocketUser(message.user), msgDataDefaults); } |
||||
} else if (message.subtype === 'me_message') { |
||||
const msgObj = { |
||||
...msgDataDefaults, |
||||
msg: `_${ this.convertSlackMessageToRocketChat(message.text) }_` |
||||
}; |
||||
RocketChat.sendMessage(this.getRocketUser(message.user), msgObj, room, true); |
||||
} else if (message.subtype === 'bot_message' || message.subtype === 'slackbot_response') { |
||||
const botUser = RocketChat.models.Users.findOneById('rocket.cat', { fields: { username: 1 }}); |
||||
const botUsername = this.bots[message.bot_id] ? this.bots[message.bot_id].name : message.username; |
||||
const msgObj = { |
||||
...msgDataDefaults, |
||||
msg: this.convertSlackMessageToRocketChat(message.text), |
||||
rid: room._id, |
||||
bot: true, |
||||
attachments: message.attachments, |
||||
username: botUsername || undefined |
||||
}; |
||||
|
||||
if (message.edited) { |
||||
msgObj.editedAt = new Date(parseInt(message.edited.ts.split('.')[0]) * 1000); |
||||
const editedBy = this.getRocketUser(message.edited.user); |
||||
if (editedBy) { |
||||
msgObj.editedBy = { |
||||
_id: editedBy._id, |
||||
username: editedBy.username |
||||
}; |
||||
} |
||||
} |
||||
|
||||
if (message.icons) { |
||||
msgObj.emoji = message.icons.emoji; |
||||
} |
||||
RocketChat.sendMessage(botUser, msgObj, room, true); |
||||
} else if (message.subtype === 'channel_purpose') { |
||||
if (this.getRocketUser(message.user)) { |
||||
RocketChat.models.Messages.createRoomSettingsChangedWithTypeRoomIdMessageAndUser('room_changed_description', room._id, message.purpose, this.getRocketUser(message.user), msgDataDefaults); |
||||
} |
||||
} else if (message.subtype === 'channel_topic') { |
||||
if (this.getRocketUser(message.user)) { |
||||
RocketChat.models.Messages.createRoomSettingsChangedWithTypeRoomIdMessageAndUser('room_changed_topic', room._id, message.topic, this.getRocketUser(message.user), msgDataDefaults); |
||||
} |
||||
} else if (message.subtype === 'channel_name') { |
||||
if (this.getRocketUser(message.user)) { |
||||
RocketChat.models.Messages.createRoomRenamedWithRoomIdRoomNameAndUser(room._id, message.name, this.getRocketUser(message.user), msgDataDefaults); |
||||
} |
||||
} else if (message.subtype === 'pinned_item') { |
||||
if (message.attachments) { |
||||
const msgObj = { |
||||
...msgDataDefaults, |
||||
attachments: [{ |
||||
'text': this.convertSlackMessageToRocketChat(message.attachments[0].text), |
||||
'author_name' : message.attachments[0].author_subname, |
||||
'author_icon' : getAvatarUrlFromUsername(message.attachments[0].author_subname) |
||||
}] |
||||
}; |
||||
RocketChat.models.Messages.createWithTypeRoomIdMessageAndUser('message_pinned', room._id, '', this.getRocketUser(message.user), msgObj); |
||||
} else { |
||||
//TODO: make this better
|
||||
this.logger.debug('Pinned item with no attachment, needs work.'); |
||||
//RocketChat.models.Messages.createWithTypeRoomIdMessageAndUser 'message_pinned', room._id, '', @getRocketUser(message.user), msgDataDefaults
|
||||
} |
||||
} else if (message.subtype === 'file_share') { |
||||
if (message.file && message.file.url_private_download !== undefined) { |
||||
const details = { |
||||
message_id: `slack-${ message.ts.replace(/\./g, '-') }`, |
||||
name: message.file.name, |
||||
size: message.file.size, |
||||
type: message.file.mimetype, |
||||
rid: room._id |
||||
}; |
||||
this.uploadFile(details, message.file.url_private_download, this.getRocketUser(message.user), room, new Date(parseInt(message.ts.split('.')[0]) * 1000)); |
||||
} |
||||
} else if (!missedTypes[message.subtype] && !ignoreTypes[message.subtype]) { |
||||
missedTypes[message.subtype] = message; |
||||
} |
||||
} else { |
||||
const user = this.getRocketUser(message.user); |
||||
if (user) { |
||||
const msgObj = { |
||||
...msgDataDefaults, |
||||
msg: this.convertSlackMessageToRocketChat(message.text), |
||||
rid: room._id, |
||||
u: { |
||||
_id: user._id, |
||||
username: user.username |
||||
} |
||||
}; |
||||
|
||||
if (message.edited) { |
||||
msgObj.editedAt = new Date(parseInt(message.edited.ts.split('.')[0]) * 1000); |
||||
const editedBy = this.getRocketUser(message.edited.user); |
||||
if (editedBy) { |
||||
msgObj.editedBy = { |
||||
_id: editedBy._id, |
||||
username: editedBy.username |
||||
}; |
||||
} |
||||
} |
||||
|
||||
RocketChat.sendMessage(this.getRocketUser(message.user), msgObj, room, true); |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
// Process the reactions
|
||||
if (RocketChat.models.Messages.findOneById(msgDataDefaults._id) && message.reactions && message.reactions.length > 0) { |
||||
message.reactions.forEach(reaction => { |
||||
reaction.users.forEach(u => { |
||||
const rcUser = this.getRocketUser(u); |
||||
if (!rcUser) { return; } |
||||
Meteor.runAsUser(rcUser._id, () => Meteor.call('setReaction', `:${ reaction.name }:`, msgDataDefaults._id)); |
||||
}); |
||||
}); |
||||
} |
||||
this.addCountCompleted(1); |
||||
}); |
||||
}); |
||||
}); |
||||
}); |
||||
|
||||
|
||||
if (!_.isEmpty(missedTypes)) { |
||||
console.log('Missed import types:', missedTypes); |
||||
} |
||||
|
||||
this.updateProgress(Importer.ProgressStep.FINISHING); |
||||
|
||||
this.channels.channels.forEach(channel => { |
||||
if (channel.do_import && channel.is_archived) { |
||||
Meteor.runAsUser(startedByUserId, function() { |
||||
Meteor.call('archiveRoom', channel.rocketId); |
||||
}); |
||||
} |
||||
}); |
||||
this.updateProgress(Importer.ProgressStep.DONE); |
||||
|
||||
const timeTook = Date.now() - start; |
||||
|
||||
this.logger.log(`Import took ${ timeTook } milliseconds.`); |
||||
|
||||
}); |
||||
return this.getProgress(); |
||||
} |
||||
getSlackChannelFromName(channelName) { |
||||
return this.channels.channels.find(channel => channel.name === channelName); |
||||
} |
||||
getRocketUser(slackId) { |
||||
const user = this.users.users.find(user => user.id === slackId); |
||||
if (user) { |
||||
return RocketChat.models.Users.findOneById(user.rocketId, { fields: { username: 1, name: 1 }}); |
||||
} |
||||
} |
||||
convertSlackMessageToRocketChat(message) { |
||||
if (message != null) { |
||||
message = message.replace(/<!everyone>/g, '@all'); |
||||
message = message.replace(/<!channel>/g, '@all'); |
||||
message = message.replace(/<!here>/g, '@here'); |
||||
message = message.replace(/>/g, '>'); |
||||
message = message.replace(/</g, '<'); |
||||
message = message.replace(/&/g, '&'); |
||||
message = message.replace(/:simple_smile:/g, ':smile:'); |
||||
message = message.replace(/:memo:/g, ':pencil:'); |
||||
message = message.replace(/:piggy:/g, ':pig:'); |
||||
message = message.replace(/:uk:/g, ':gb:'); |
||||
message = message.replace(/<(http[s]?:[^>]*)>/g, '$1'); |
||||
for (const userReplace of Array.from(this.userTags)) { |
||||
message = message.replace(userReplace.slack, userReplace.rocket); |
||||
message = message.replace(userReplace.slackLong, userReplace.rocket); |
||||
} |
||||
} else { |
||||
message = ''; |
||||
} |
||||
return message; |
||||
} |
||||
getSelection() { |
||||
const selectionUsers = this.users.users.map(user => new Importer.SelectionUser(user.id, user.name, user.profile.email, user.deleted, user.is_bot, !user.is_bot)); |
||||
const selectionChannels = this.channels.channels.map(channel => new Importer.SelectionChannel(channel.id, channel.name, channel.is_archived, true, false)); |
||||
return new Importer.Selection(this.name, selectionUsers, selectionChannels); |
||||
} |
||||
}; |
||||
Loading…
Reference in new issue