@ -12,6 +12,7 @@ import { RocketChat } from 'meteor/rocketchat:lib';
import { Readable } from 'stream' ;
import path from 'path' ;
import s from 'underscore.string' ;
import fs from 'fs' ;
import TurndownService from 'turndown' ;
const turndownService = new TurndownService ( {
@ -43,11 +44,10 @@ export class HipChatEnterpriseImporter extends Base {
this . directMessages = new Map ( ) ;
}
prepare ( dataURI , sentContentType , fileName ) {
super . prepare ( dataURI , sentContentType , fileName ) ;
prepareUsingLocalFile ( fullFilePath ) {
const tempUsers = [ ] ;
const tempRooms = [ ] ;
const emails = [ ] ;
const tempMessages = new Map ( ) ;
const tempDirectMessages = new Map ( ) ;
const promise = new Promise ( ( resolve , reject ) => {
@ -72,10 +72,7 @@ export class HipChatEnterpriseImporter extends Base {
if ( info . base === 'users.json' ) {
super . updateProgress ( ProgressStep . PREPARING _USERS ) ;
for ( const u of file ) {
// if (!u.User.email) {
// // continue;
// }
tempUsers . push ( {
const userData = {
id : u . User . id ,
email : u . User . email ,
name : u . User . name ,
@ -83,7 +80,17 @@ export class HipChatEnterpriseImporter extends Base {
avatar : u . User . avatar && u . User . avatar . replace ( /\n/g , '' ) ,
timezone : u . User . timezone ,
isDeleted : u . User . is _deleted ,
} ) ;
} ;
if ( u . User . email ) {
if ( emails . indexOf ( u . User . email ) >= 0 ) {
userData . is _email _taken = true ;
} else {
emails . push ( u . User . email ) ;
}
}
tempUsers . push ( userData ) ;
}
} else if ( info . base === 'rooms.json' ) {
super . updateProgress ( ProgressStep . PREPARING _CHANNELS ) ;
@ -129,6 +136,8 @@ export class HipChatEnterpriseImporter extends Base {
userId : m . UserMessage . sender . id ,
text : m . UserMessage . message . indexOf ( '/me ' ) === - 1 ? m . UserMessage . message : ` ${ m . UserMessage . message . replace ( /\/me / , '_' ) } _ ` ,
ts : new Date ( m . UserMessage . timestamp . split ( ' ' ) [ 0 ] ) ,
attachment : m . UserMessage . attachment ,
attachment _path : m . UserMessage . attachment _path ,
} ) ;
} else if ( m . NotificationMessage ) {
const text = m . NotificationMessage . message . indexOf ( '/me ' ) === - 1 ? m . NotificationMessage . message : ` ${ m . NotificationMessage . message . replace ( /\/me / , '_' ) } _ ` ;
@ -140,6 +149,8 @@ export class HipChatEnterpriseImporter extends Base {
alias : m . NotificationMessage . sender ,
text : m . NotificationMessage . message _format === 'html' ? turndownService . turndown ( text ) : text ,
ts : new Date ( m . NotificationMessage . timestamp . split ( ' ' ) [ 0 ] ) ,
attachment : m . NotificationMessage . attachment ,
attachment _path : m . NotificationMessage . attachment _path ,
} ) ;
} else if ( m . TopicRoomMessage ) {
roomMsgs . push ( {
@ -174,6 +185,23 @@ export class HipChatEnterpriseImporter extends Base {
} ) ;
this . extract . on ( 'finish' , Meteor . bindEnvironment ( ( ) => {
// Check if any of the emails used are already taken
if ( emails . length > 0 ) {
const conflictingUsers = RocketChat . models . Users . find ( { 'emails.address' : { $in : emails } } ) ;
conflictingUsers . forEach ( ( conflictingUser ) => tempUsers . forEach ( ( newUser ) => conflictingUser . emails . forEach ( ( email ) => {
if ( email && email . address === newUser . email ) {
if ( conflictingUser . username !== newUser . username ) {
newUser . is _email _taken = true ;
newUser . do _import = false ;
return false ;
}
}
return true ;
} ) ) ) ;
}
// 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 } ) ;
@ -240,7 +268,7 @@ export class HipChatEnterpriseImporter extends Base {
return ;
}
const selectionUsers = tempUsers . map ( ( u ) => new SelectionUser ( u . id , u . username , u . email , u . isDeleted , false , true ) ) ;
const selectionUsers = tempUsers . map ( ( u ) => new SelectionUser ( u . id , u . username , u . email , u . isDeleted , false , u . do _import !== false , u . is _email _taken === true ) ) ;
const selectionChannels = tempRooms . map ( ( r ) => new SelectionChannel ( r . id , r . name , r . isArchived , true , r . isPrivate ) ) ;
const selectionMessages = this . importRecord . count . messages ;
@ -249,64 +277,17 @@ export class HipChatEnterpriseImporter extends Base {
resolve ( new Selection ( this . name , selectionUsers , selectionChannels , selectionMessages ) ) ;
} ) ) ;
// Wish I could make this cleaner :(
const split = dataURI . split ( ',' ) ;
const read = new this . Readable ;
read . push ( new Buffer ( split [ split . length - 1 ] , 'base64' ) ) ;
read . push ( null ) ;
read . pipe ( this . zlib . createGunzip ( ) ) . pipe ( this . extract ) ;
const rs = fs . createReadStream ( fullFilePath ) ;
rs . pipe ( this . zlib . createGunzip ( ) ) . pipe ( this . extract ) ;
} ) ;
return promise ;
}
startImport ( importSelection ) {
super . startImport ( importSelection ) ;
const started = Date . now ( ) ;
// Ensure we're only going to import the users that the user has selected
for ( const user of importSelection . users ) {
for ( const u of this . users . users ) {
if ( u . id === user . user _id ) {
u . do _import = user . do _import ;
}
}
}
this . collection . update ( { _id : this . users . _id } , { $set : { users : this . users . users } } ) ;
// Ensure we're only importing the channels the user has selected.
for ( const channel of importSelection . channels ) {
for ( const c of this . channels . channels ) {
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 ( ( ) => {
super . updateProgress ( ProgressStep . IMPORTING _USERS ) ;
try {
// Import the users
for ( const u of this . users . users ) {
this . logger . debug ( ` Starting the user import: ${ u . username } and are we importing them? ${ u . do _import } ` ) ;
if ( ! u . do _import ) {
continue ;
}
_importUser ( u , startedByUserId ) {
Meteor . runAsUser ( startedByUserId , ( ) => {
let existantUser ;
if ( u . email ) {
RocketChat . models . Users . findOneByEmailAddress ( u . email ) ;
}
// If we couldn't find one by their email address, try to find an existing user by their username
if ( ! existantUser ) {
existantUser = RocketChat . models . Users . findOneByUsername ( u . username ) ;
}
const existantUser = RocketChat . models . Users . findOneByUsername ( u . username ) ;
if ( existantUser ) {
// since we have an existing user, let's try a few things
@ -314,11 +295,15 @@ export class HipChatEnterpriseImporter extends Base {
RocketChat . models . Users . update ( { _id : u . rocketId } , { $addToSet : { importIds : u . id } } ) ;
} else {
const user = { email : u . email , password : Random . id ( ) } ;
// if (u.is_email_taken && u.email) {
// user.email = user.email.replace('@', `+rocket.chat_${ Math.floor(Math.random() * 10000).toString() }@`);
// }
if ( ! user . email ) {
delete user . email ;
user . username = u . username ;
}
try {
const userId = Accounts . createUser ( user ) ;
Meteor . runAsUser ( userId , ( ) => {
Meteor . call ( 'setUsername' , u . username , { joinDefaultChannelsSilenced : true } ) ;
@ -338,15 +323,80 @@ export class HipChatEnterpriseImporter extends Base {
RocketChat . models . Users . update ( { _id : userId } , { $addToSet : { importIds : u . id } } ) ;
u . rocketId = userId ;
} ) ;
} catch ( e ) {
this . addUserError ( u . id , e ) ;
}
}
super . addCountCompleted ( 1 ) ;
} ) ;
}
startImport ( importSelection ) {
super . startImport ( importSelection ) ;
const started = Date . now ( ) ;
// Ensure we're only going to import the users that the user has selected
for ( const user of importSelection . users ) {
for ( const u of this . users . users ) {
if ( u . id === user . user _id ) {
u . do _import = user . do _import ;
}
}
}
this . collection . update ( { _id : this . users . _id } , { $set : { users : this . users . users } } ) ;
// Import the channels
// Ensure we're only importing the channels the user has selected.
for ( const channel of importSelection . channels ) {
for ( const c of this . channels . channels ) {
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 ( ( ) => {
try {
super . updateProgress ( ProgressStep . IMPORTING _USERS ) ;
this . _importUsers ( startedByUserId ) ;
super . updateProgress ( ProgressStep . IMPORTING _CHANNELS ) ;
this . _importChannels ( startedByUserId ) ;
super . updateProgress ( ProgressStep . IMPORTING _MESSAGES ) ;
this . _importMessages ( startedByUserId ) ;
this . _importDirectMessages ( ) ;
// super.updateProgress(ProgressStep.FINISHING);
super . updateProgress ( ProgressStep . DONE ) ;
} catch ( e ) {
super . updateRecord ( { 'error-record' : JSON . stringify ( e , Object . getOwnPropertyNames ( e ) ) } ) ;
this . logger . error ( e ) ;
super . updateProgress ( ProgressStep . ERROR ) ;
}
const timeTook = Date . now ( ) - started ;
this . logger . log ( ` HipChat Enterprise Import took ${ timeTook } milliseconds. ` ) ;
} ) ;
return super . getProgress ( ) ;
}
_importUsers ( startedByUserId ) {
for ( const u of this . users . users ) {
this . logger . debug ( ` Starting the user import: ${ u . username } and are we importing them? ${ u . do _import } ` ) ;
if ( ! u . do _import ) {
continue ;
}
this . _importUser ( u , startedByUserId ) ;
}
this . collection . update ( { _id : this . users . _id } , { $set : { users : this . users . users } } ) ;
}
_importChannels ( startedByUserId ) {
for ( const c of this . channels . channels ) {
if ( ! c . do _import ) {
continue ;
@ -362,46 +412,58 @@ export class HipChatEnterpriseImporter extends Base {
// Find the rocketchatId of the user who created this channel
let creatorId = startedByUserId ;
for ( const u of this . users . users ) {
if ( u . id === c . creator && u . do _import ) {
if ( u . id === c . creator && u . do _import && u . rocketId ) {
creatorId = u . rocketId ;
break ;
}
}
// Create the channel
Meteor . runAsUser ( creatorId , ( ) => {
try {
const roomInfo = Meteor . call ( c . isPrivate ? 'createPrivateGroup' : 'createChannel' , c . name , [ ] ) ;
c . rocketId = roomInfo . rid ;
} catch ( e ) {
this . logger . error ( ` Failed to create channel, using userId: ${ creatorId } ; ` , e ) ;
}
} ) ;
if ( c . rocketId ) {
RocketChat . models . Rooms . update ( { _id : c . rocketId } , { $set : { ts : c . created , topic : c . topic } , $addToSet : { importIds : c . id } } ) ;
}
}
super . addCountCompleted ( 1 ) ;
} ) ;
}
this . collection . update ( { _id : this . channels . _id } , { $set : { channels : this . channels . channels } } ) ;
}
// Import the Messages
super . updateProgress ( ProgressStep . IMPORTING _MESSAGES ) ;
for ( const [ ch , messagesMap ] of this . messages . entries ( ) ) {
const hipChannel = this . getChannelFromRoomIdentifier ( ch ) ;
if ( ! hipChannel . do _import ) {
continue ;
_importAttachment ( msg , room , sender ) {
if ( msg . attachment _path ) {
const details = {
message _id : ` ${ msg . id } -attachment ` ,
name : msg . attachment . name ,
size : msg . attachment . size ,
userId : sender . _id ,
rid : room . _id ,
} ;
this . uploadFile ( details , msg . attachment . url , sender , room , msg . ts ) ;
}
}
const room = RocketChat . models . Rooms . findOneById ( hipChannel . rocketId , { fields : { usernames : 1 , t : 1 , name : 1 } } ) ;
Meteor . runAsUser ( startedByUserId , ( ) => {
for ( const [ msgGroupData , msgs ] of messagesMap . entries ( ) ) {
super . updateRecord ( { messagesstatus : ` ${ ch } / ${ msgGroupData } . ${ msgs . messages . length } ` } ) ;
for ( const msg of msgs . messages ) {
_importSingleMessage ( msg , ch , msgGroupData , room ) {
if ( isNaN ( msg . ts ) ) {
this . logger . warn ( ` Timestamp on a message in ${ ch } / ${ msgGroupData } is invalid ` ) ;
super . addCountCompleted ( 1 ) ;
continue ;
return ;
}
const creator = this . getRocketUserFromUserId ( msg . userId ) ;
if ( creator ) {
this . _importAttachment ( msg , room , creator ) ;
switch ( msg . type ) {
case 'user' :
RocketChat . sendMessage ( creator , {
@ -424,11 +486,27 @@ export class HipChatEnterpriseImporter extends Base {
super . addCountCompleted ( 1 ) ;
}
_importMessages ( startedByUserId ) {
for ( const [ ch , messagesMap ] of this . messages . entries ( ) ) {
const hipChannel = this . getChannelFromRoomIdentifier ( ch ) ;
if ( ! hipChannel . do _import ) {
continue ;
}
const room = RocketChat . models . Rooms . findOneById ( hipChannel . rocketId , { fields : { usernames : 1 , t : 1 , name : 1 } } ) ;
Meteor . runAsUser ( startedByUserId , ( ) => {
for ( const [ msgGroupData , msgs ] of messagesMap . entries ( ) ) {
super . updateRecord ( { messagesstatus : ` ${ ch } / ${ msgGroupData } . ${ msgs . messages . length } ` } ) ;
for ( const msg of msgs . messages ) {
this . _importSingleMessage ( msg , ch , msgGroupData , room ) ;
}
}
} ) ;
}
}
// Import the Direct Messages
_importDirectMessages ( ) {
for ( const [ directMsgRoom , directMessagesMap ] of this . directMessages . entries ( ) ) {
const hipUser = this . getUserFromDirectMessageIdentifier ( directMsgRoom ) ;
if ( ! hipUser || ! hipUser . do _import ) {
@ -472,7 +550,7 @@ export class HipChatEnterpriseImporter extends Base {
Meteor . runAsUser ( sender . _id , ( ) => {
if ( msg . attachment _path ) {
const details = {
message _id : msg . id ,
message _id : ` ${ msg . id } -attachment ` ,
name : msg . attachment . name ,
size : msg . attachment . size ,
userId : sender . _id ,
@ -495,24 +573,10 @@ export class HipChatEnterpriseImporter extends Base {
}
}
}
super . updateProgress ( ProgressStep . FINISHING ) ;
super . updateProgress ( ProgressStep . DONE ) ;
} catch ( e ) {
super . updateRecord ( { 'error-record' : JSON . stringify ( e , Object . getOwnPropertyNames ( e ) ) } ) ;
this . logger . error ( e ) ;
super . updateProgress ( ProgressStep . ERROR ) ;
}
const timeTook = Date . now ( ) - started ;
this . logger . log ( ` HipChat Enterprise Import took ${ timeTook } milliseconds. ` ) ;
} ) ;
return super . getProgress ( ) ;
}
getSelection ( ) {
const selectionUsers = this . users . users . map ( ( u ) => new SelectionUser ( u . id , u . username , u . email , false , false , true ) ) ;
const selectionUsers = this . users . users . map ( ( u ) => new SelectionUser ( u . id , u . username , u . email , u . isDeleted === true , false , u . do _import !== false , u . is _email _taken === true ) ) ;
const selectionChannels = this . channels . channels . map ( ( c ) => new SelectionChannel ( c . id , c . name , false , true , c . isPrivate ) ) ;
const selectionMessages = this . importRecord . count . messages ;