hipmost 1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: dad37853ecd2f590343ce5366c2f9c6560aa287ea6bd878c2eac1f51ce46d143
4
+ data.tar.gz: 47bd0029d1bf0a4ab79c1e57d098f0a394e1a2de5ddcf48727647764b03bf8d4
5
+ SHA512:
6
+ metadata.gz: 0e00a51e7a1a45b5bf446c193bf86e36bef54f29562a796cec9e4981199c58839f2910fc26c2497ef142271522f0937c8a5270d227d6f13c54bc85037c637523
7
+ data.tar.gz: 8d57d5844c80d8bddabf359d2b617a6fc828f3bd370e98973299b3f13945dd5ed21f6eadcf3c1ff8beda8fcfd5a0f08348ef0aa0ff86081addecf2e7a7e9c7b6
@@ -0,0 +1,13 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
12
+ data
13
+ data.jsonl
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.5.1
5
+ before_install: gem install bundler -v 1.16.2
@@ -0,0 +1,8 @@
1
+ # The Ruby Community Conduct Guideline
2
+
3
+ This document provides community guidelines for a safe, respectful, productive, and collaborative place for any person who is willing to contribute to the Ruby community. It applies to all "collaborative space," which is defined as community communications channels (such as mailing lists, submitted patches, commit comments, etc.).
4
+
5
+ - Participants will be tolerant of opposing views.
6
+ - Participants must ensure that their language and actions are free of personal attacks and disparaging personal remarks.
7
+ - When interpreting the words and actions of others, participants should always assume good intentions.
8
+ - Behaviour which can be reasonably considered harassment will not be tolerated.
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in hipmost.gemspec
6
+ gemspec
@@ -0,0 +1,49 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ hipmost (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ coderay (1.1.2)
10
+ diff-lcs (1.3)
11
+ method_source (0.8.2)
12
+ pry (0.10.4)
13
+ coderay (~> 1.1.0)
14
+ method_source (~> 0.8.1)
15
+ slop (~> 3.4)
16
+ pry-doc (0.12.0)
17
+ pry (~> 0.9)
18
+ yard (~> 0.9)
19
+ rake (10.5.0)
20
+ rspec (3.7.0)
21
+ rspec-core (~> 3.7.0)
22
+ rspec-expectations (~> 3.7.0)
23
+ rspec-mocks (~> 3.7.0)
24
+ rspec-core (3.7.1)
25
+ rspec-support (~> 3.7.0)
26
+ rspec-expectations (3.7.0)
27
+ diff-lcs (>= 1.2.0, < 2.0)
28
+ rspec-support (~> 3.7.0)
29
+ rspec-mocks (3.7.0)
30
+ diff-lcs (>= 1.2.0, < 2.0)
31
+ rspec-support (~> 3.7.0)
32
+ rspec-support (3.7.1)
33
+ slop (3.6.0)
34
+ yard (0.9.15)
35
+
36
+ PLATFORMS
37
+ ruby
38
+
39
+ DEPENDENCIES
40
+ bundler (~> 1.16)
41
+ hipmost!
42
+ method_source (>= 0.8.2)
43
+ pry (~> 0.10.3)
44
+ pry-doc (>= 0.8.0)
45
+ rake (~> 10.0)
46
+ rspec (~> 3.0)
47
+
48
+ BUNDLED WITH
49
+ 1.16.2
@@ -0,0 +1,55 @@
1
+ # How to migrate from Hipchat to Mattermost with Hipmost
2
+
3
+ Migrating from Hipchat to Mattermost is a bit of a process, but if you know what to do then it's fairly straightforward. We'll describe some steps of what to do in this document, taking from [Mattermost's own guidelines.](https://mattermost.com/atlassian/#hipchat-migration) *Credit goes to Mattermost for the content which they wrote.*
4
+
5
+ ## Step 1: Set up your Mattermost Instance
6
+
7
+ 1. Download the [latest version of Mattermost](https://about.mattermost.com/download/).
8
+ 2. [Deploy Mattermost](https://docs.mattermost.com/guides/administrator.html#installing-mattermost) in your environment using the configuration that meets your organization’s needs for performance and scalability.
9
+ 3. [Request a Trial](https://mattermost.com/trial) of Mattermost Enterprise for [advanced features](https://mattermost.com/pricing).
10
+
11
+ Feel free to [submit an issue](https://github.com/orbitalimpact/hipmost/issues/new) or visit Mattermost's [troubleshooting forum](https://forum.mattermost.org/t/how-to-use-the-troubleshooting-forum/150) for help.
12
+
13
+ ## Step 2: Export your data from HipChat
14
+
15
+ ### Situation 1:
16
+
17
+ If your Hipchat instance is hosted on hipchat.com (i.e., **not** self-hosted) then you need to request your data from Atlassian. Go to [this page,](https://support.atlassian.com/hipchat/) log in with your Hipchat account information, submit a ticket via the `Contact Support` button, and explain that you wish to obtain a copy of your team's data. After some days, they should give you an AES-encrypted tarball containing your data (the file will have the extension `.tar.gz.aes`). [Here is another document](https://confluence.atlassian.com/hipchatkb/unable-to-decrypt-file-while-importing-into-hipchat-server-756777042.html) explaining how to manually decrypt your tarball at the command line.
18
+
19
+ ### Situation 2:
20
+
21
+ These are the steps to follow if you're using Hipchat Server or Hipchat Data Center, as given by Mattermost.
22
+
23
+ (i.e., what to do if you **are** self-hosted)
24
+
25
+ If you’re able to upgrade HipChat Server or HipChat Data Center to the latest version, we recommend using Group Export Dashboard to export your data. If you’re unable to upgrade, see Command Line Interface procedure below.
26
+
27
+ *Using the Group Export Dashboard*:
28
+
29
+ 1. Log in to your Hipchat Server or HipChat Server instance (e.g., hipchat.yourcompany.com)
30
+ 2. Click on **Server Admin > Export**.
31
+ 3. Select the data to export.
32
+ 4. In the Password and Confirm Password fields, create a password to protect your archive files. (Store this password as it is not saved anywhere else.)
33
+ 5. Click Export. Once the export is done, you will receive an email with a link to download the file.
34
+
35
+ *If you’re unable to use the Group Export Dashboard, use the Command Line Interface to export:*
36
+
37
+ 1. Go to CLI.
38
+ 2. Enter `hipchat export --export -p your_password`
39
+ 3. Once the export is done, you will receive an email with a link to download the file.
40
+
41
+ *More detailed instructions can be found at https://confluence.atlassian.com/hipchatdc3/export-data-from-hipchat-data-center-913476832.html.*
42
+
43
+ ## Step 3: Use Hipmost to convert your data
44
+
45
+ After you've received your data, decrypted it, and extracted it to a folder, you may then use `hipmost` to convert your data for importation into Mattermost. If you name your folder `data`, then you can simply use `hipmost rooms import "Example Room" "Mattermost Team":"Mattermost Channel"`. Of course, substitute whatever the actual name of your room, channel, and team is in their respective fields. Furthermore, you may use the `-p` option to set a different path to your data. More usage options [in the `Usage` section of README.md.](./README.md#usage)
46
+
47
+ After you execute the `hipmost` command, you should have a file entitled `Room Name.jsonl` in the current directory which is ready to be imported into Mattermost.
48
+
49
+ ## Step 4: Import your data into Mattermost
50
+
51
+ 1. Follow the [Mattermost Bulk Load Tool](https://docs.mattermost.com/deployment/bulk-loading.html) guide to import your data into Mattermost.
52
+ a. Note: Efforts are underway to source scripts from the Mattermost community to further automate this step. If you’re interested in contributing, please contact Mattermost at [info@mattermost.com](info@mattermost.com), Twitter or Mattermost forums at https://forum.mattermost.org
53
+ 2. Alternatively, [contact Mattermost](https://mattermost.com/contact-us) for partner recommendations for your region to assist in your import.
54
+
55
+ If you encounter any troubles along the way, please feel free to [submit an issue](https://github.com/orbitalimpact/hipmost/issues/new) and let us know what problem you're having.
@@ -0,0 +1,36 @@
1
+ # Known Bugs
2
+ Unfortunately, there are a number of problems which exist that we are presently aware of. We have worked on this project in our spare time, in between work on actual projects. Since we worked on it for free and with our specific data in mind, we have accrued the following bugs and have been unable to devote the time and effort necessary to properly fix all of them (although, some of these bugs have workarounds). We would like to remedy this, yet we could certainly use help in the way of patches or donations. If this project has been helpful to your team or you'd like to improve it to help your team migrate, then please consider submitting patches or donating money so that we can devote more time to this project.
3
+
4
+ Of course, please do not assume that this list encompasses *all* of the bugs which you may encounter.
5
+
6
+ Items which are fairly easy to tackle and for which we would like help are marked with "**(help wanted)**"
7
+
8
+ Without further ado:
9
+
10
+ ## General improvements
11
+
12
+ - Verbose mode could be better (i.e., more verbose but not too verbose). **(help wanted)**
13
+ - The CLI could perhaps become easier and more intuitive. **(help wanted)**
14
+ - More thorough documentation is always a good thing. **(help wanted)**
15
+
16
+ ## What gets converted
17
+
18
+ - Messages which are of the type `TopicRoomMessage`, `ArchiveRoomMessage`, `GuestAccessMessage` or `NotificationMessage` are skipped; only `UserMessage`'s and `PrivateUserMessage`'s are processed.
19
+ - Certain Hipchat [slash commands](https://confluence.atlassian.com/hipchat/keyboard-shortcuts-and-slash-commands-749385232.html#Keyboardshortcutsandslashcommands-Slashcommands) are not translated into Mattermost messages because they do not have Mattermost equivalents. This includes: `/clear`, `/me`, `s/`, `#color-hex`. However, we do attempt to translate some slash commands since they have Mattermost equivalents; namely, the formatting slash commands: `/code` and `/quote`.
20
+ - We do not currently handle any kind of conversion or importation of files uploaded to Hipchat. E.g., images, documents, media, etc. The reason being that our team relies on external file-hosting services to upload such files to. We found Hipchat's file-hosting to be flaky and not robust. **(help wanted)**
21
+ - Some posts may contain old usernames, i.e., a username which someone used in Hipchat that is different than the new username they've chosen for Mattermost. Unfortunately, this cannot be caught by the validator. **(help wanted)**
22
+ - A workaround is to use `sed` or a text editor to find & replace the occurrences of the old username with the new one.
23
+
24
+ ## Potential errors
25
+
26
+ - Sometimes [user objects](https://docs.mattermost.com/deployment/bulk-loading.html#user-object) don't get generated. We're not exactly sure why this happens; unfortunately, it causes the validator to get upset and say something like: `Error importing post. User with username "john_doe" could not be found., SqlUserStore.GetByUsername: We couldn't find an existing account matching your username for this team.` **(help wanted)**
27
+ - The current workaround is to manually [create the missing user(s)](https://docs.mattermost.com/administration/command-line-tools.html#mattermost-user-create) in the system by hand.
28
+ - If a user object *is* generated, but a user's username is now different than what it used to be in Hipchat, Mattermost may complain about them having an insufficient password. E.g.: `User.IsValid: model.user.is_valid.pwd_lowercase_uppercase_number_symbol.app_error` **(help wanted)**
29
+ - A workaround is to remove that user object or to modify the user object to have the right username.
30
+
31
+ ## Questionable behavior
32
+
33
+ - If a file is generated whose name conflicts with an already existing file, then the already existing file will be overwritten.
34
+ - This may be considered a feature, depending on your opinion and use case.
35
+
36
+ If you feel like tackling any of these problems, please feel free to submit a pull request or file an issue for discussion. We welcome contributions and are happy to help.
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 Gabriel Rios
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,63 @@
1
+ # Hipmost
2
+
3
+ [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=AYNCCNVFYPKXW)
4
+
5
+ Hipmost is a tool to migrate your Hipchat history to Mattermost. It parses your Hipchat export and generates a file to be imported on a Mattermost server. After generating this file, please see [the Mattermost documentation](https://docs.mattermost.com/deployment/bulk-loading.html) for how to import it on your server.
6
+
7
+ ## Installation
8
+
9
+ $ gem install hipmost
10
+
11
+ ## Getting started
12
+
13
+ See [HOWTO.md](./HOWTO.md) for a step-by-step guide for the entire process. It covers everything from how to get your data from Hipchat, and finally, how to get that data into Mattermost.
14
+
15
+ ## Usage
16
+
17
+ Usage: hipmost [options] [command]
18
+
19
+ Commands:
20
+
21
+ public (AKA: `room' or `rooms')
22
+ Form: public [import|list] [room names] - Import or list public Hipchat rooms
23
+
24
+ [room names] must be at least one pair composed by "Hipchat room name" and "Mattermost team":"Mattermost channel".
25
+ The Mattermost team or channel can be the part visible in the URL path, such as "town-square", or it can be the plain-English name, such as "General"
26
+
27
+ --------
28
+
29
+ private (AKA: `direct')
30
+ Form: private [import|list] - Import or list private chats
31
+
32
+ --------
33
+
34
+ Examples:
35
+ $ hipmost room import "Orbital Impact" "Orbital Impact":"General"
36
+ $ hipmost public import "Orbital Impact" "Orbital Impact":"General" -p data_folder
37
+ $ hipmost private list
38
+ $ hipmost -v rooms import "Orbital Impact" "Orbital Impact":"General"
39
+
40
+ -p, --path [PATH] Path to Hipchat data folder (Default: "./data")
41
+ -v, --[no-]verbose Run verbosely
42
+
43
+ ## Known Bugs
44
+ See the [KNOWN-BUGS.md](./KNOWN-BUGS.md) file for discussion of known problems, workarounds, and potential improvements. This is also a good place to start if you're interested in contributing.
45
+
46
+ ## Contributing
47
+
48
+ Bug reports and pull requests are welcome. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Ruby code of conduct.](https://www.ruby-lang.org/en/conduct/)
49
+
50
+ Also, [here is a great reference for Hipchat's data format](https://confluence.atlassian.com/hipchatkb/exporting-from-hipchat-server-or-data-center-for-data-portability-950821555.html) and [here is a great reference for how Mattermost's data format.](https://docs.mattermost.com/deployment/bulk-loading.html#data-format) These are highly useful for aspiring contributors.
51
+
52
+ ## License
53
+
54
+ The gem is available as open source under the terms of the [MIT License.](https://opensource.org/licenses/MIT)
55
+
56
+ ## Code of Conduct
57
+
58
+ Everyone interacting in the Hipmost project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct.](./CODE_OF_CONDUCT.md)
59
+
60
+ ## Donation
61
+ If this project has helped you or your team, a donation would be appreciated and will help keep the project alive :)
62
+
63
+ [![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=AYNCCNVFYPKXW)
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "hipmost"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH << File.expand_path("../../lib", __FILE__)
3
+ require "rubygems" if RUBY_VERSION < "1.9"
4
+
5
+
6
+ require 'optparse'
7
+ require "hipmost"
8
+
9
+ options = { path: "./data" }
10
+ OptionParser.new do |opts|
11
+ opts.banner = <<-BANNER
12
+ Usage: hipmost [options] [command]
13
+
14
+ Commands:
15
+
16
+ public (AKA, room or rooms)
17
+ Form: public [import|list] [rooms] - Import or list public Hipchat rooms
18
+
19
+ [rooms] must be at least one pair composed by "Hipchat channel name" and "Mattermost team":"Mattermost channel"
20
+ The Mattermost team or channel can be the URL endpoint, such as "town-square", or the channel name, such as "General"
21
+
22
+ --------
23
+
24
+ private (AKA, direct)
25
+ Form: private [import|list] - Import or list private chats
26
+
27
+ --------
28
+
29
+ Examples:
30
+ $ hipmost room import "Orbital Impact" "Orbital Impact":"General"
31
+ $ hipmost public import "Orbital Impact" "Orbital Impact":"General" -p data_folder
32
+ $ hipmost private list
33
+ $ hipmost -v rooms import "Orbital Impact" "Orbital Impact":"General"
34
+
35
+ BANNER
36
+
37
+ opts.on("-p", "--path [PATH]", 'Path to Hipchat data folder (Default: "./data")') do |p|
38
+ options[:path] = p
39
+ end
40
+
41
+ opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
42
+ options[:verbose] = true
43
+ end
44
+ end.parse!
45
+
46
+ command = ARGV.shift
47
+
48
+ exit Hipmost::CLI.run(command, ARGV, options)
@@ -0,0 +1,31 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "hipmost/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "hipmost"
8
+ spec.version = Hipmost::VERSION
9
+ spec.authors = ["Gabriel Rios"]
10
+ spec.email = ["gabrielfalcaorios@gmail.com"]
11
+
12
+ spec.summary = %q{Migrate your hipchat history to Mattermost}
13
+ spec.description = %q{Migrate your hipchat history to Mattermost}
14
+ spec.license = "MIT"
15
+
16
+ # Specify which files should be added to the gem when it is released.
17
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
18
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
19
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
20
+ end
21
+ spec.bindir = "exe"
22
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
+ spec.require_paths = ["lib"]
24
+
25
+ spec.add_development_dependency "bundler", "~> 1.16"
26
+ spec.add_development_dependency "rake", "~> 10.0"
27
+ spec.add_development_dependency "rspec", "~> 3.0"
28
+ spec.add_development_dependency "pry", "~> 0.10.3"
29
+ spec.add_development_dependency "pry-doc", ">= 0.8.0"
30
+ spec.add_development_dependency "method_source", ">= 0.8.2"
31
+ end
@@ -0,0 +1,21 @@
1
+ require 'pathname'
2
+ require "hipmost/version"
3
+ require "hipmost/hipchat"
4
+ require "hipmost/mattermost"
5
+ require "hipmost/cmds"
6
+
7
+ module Hipmost
8
+ class CLI
9
+ def self.run(command, args, options)
10
+ case command.to_sym
11
+ when :public, :room, :rooms
12
+ Hipmost::Cmds::Room.new(**options).run(args)
13
+ when :private, :direct
14
+ Hipmost::Cmds::Private.new(**options).run(args)
15
+ else
16
+ puts "Invalid command"
17
+ exit 1
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,6 @@
1
+ module Hipmost
2
+ module Cmds
3
+ require_relative 'cmds/room'
4
+ require_relative 'cmds/private'
5
+ end
6
+ end
@@ -0,0 +1,55 @@
1
+ module Hipmost
2
+ module Cmds
3
+ class Private
4
+ def initialize(path:, verbose: false)
5
+ $path = Pathname.new(path).expand_path
6
+ @outpath = $path.join("..", "Private Chats.jsonl").expand_path
7
+ @verbose = verbose
8
+ end
9
+
10
+ def run(args)
11
+ subcommand = args.shift
12
+
13
+ if ["list", "import"].include?(subcommand)
14
+ else
15
+ puts "Command invalid for `private`, must be `import` or `list`"
16
+ exit 1
17
+ end
18
+
19
+ send(subcommand, args)
20
+ end
21
+
22
+ def list(_args)
23
+ puts "Listing Private chats" if @verbose
24
+ Hipchat.direct_channels.each do |members|
25
+ puts members.inspect
26
+ end
27
+
28
+ true
29
+ end
30
+
31
+ def import(args)
32
+ File.open(@outpath, "w") do |jsonl|
33
+ puts "Writing version header..." if @verbose
34
+ jsonl.puts %[{ "type": "version", "version": 1 }]
35
+
36
+ puts "Writing room members..." if @verbose
37
+ Hipchat.users.each do |_,user|
38
+ jsonl.puts(user.to_jsonl)
39
+ end
40
+
41
+ puts "Writing 1-on-1 room members..." if @verbose
42
+ Hipchat.direct_channels.each do |members|
43
+ puts members.inspect if @verbose
44
+ jsonl.puts(%[{ "type": "direct_channel", "direct_channel": { "members": #{members.inspect} }}])
45
+ end
46
+
47
+ puts "Writing 1-on-1 room posts (oldest to newest)..." if @verbose
48
+ Hipchat.direct_posts(jsonl, @verbose)
49
+ end
50
+
51
+ true
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,126 @@
1
+ module Hipmost
2
+ module Cmds
3
+ class Room
4
+ def initialize(path:, verbose: false)
5
+ $path = Pathname.new(path).expand_path
6
+ @verbose = verbose
7
+ end
8
+
9
+ def run(args)
10
+ subcommand = args.shift
11
+ @outpath = $path.join("..", "#{args[0]}.jsonl").expand_path
12
+
13
+ if ["list", "import"].include?(subcommand)
14
+ else
15
+ puts "Command invalid for `public`; must be `import` or `list`"
16
+ exit 1
17
+ end
18
+
19
+ send(subcommand, args)
20
+ end
21
+
22
+ def list(_args)
23
+ puts "Listing rooms..." if @verbose
24
+ Hipchat.rooms.each do |_,room|
25
+ puts room.display_name
26
+ end
27
+
28
+ true
29
+ end
30
+
31
+ def import(args)
32
+ rooms = args
33
+ rooms_size = rooms.size
34
+
35
+ if rooms_size.zero? || (rooms_size % 2).nonzero?
36
+ puts "Need a pair of rooms to migrate"
37
+ exit 1
38
+ end
39
+
40
+ @data = rooms
41
+
42
+ parse_rooms_arg
43
+ save
44
+ true
45
+ end
46
+
47
+ def save
48
+ puts "Opening #{@outpath} for writing..." if @verbose
49
+
50
+ File.open(@outpath, "w") do |jsonl|
51
+ puts "Writing version header..." if @verbose
52
+ jsonl.puts %[{ "type": "version", "version": 1 }]
53
+
54
+ puts "Writing team info..." if @verbose
55
+ @teams.each {|t| jsonl.puts(t.to_jsonl) }
56
+ puts "Writing channel info..." if @verbose
57
+ @channels.each {|r| jsonl.puts(r.to_jsonl) }
58
+
59
+ puts "Writing room members..." if @verbose
60
+ @rooms.each do |room|
61
+ room.users.each do |user|
62
+ jsonl.puts(user.to_jsonl)
63
+ end
64
+ end
65
+
66
+ puts "Writing room posts (newest to oldest)..." if @verbose
67
+ i = 1 if @verbose
68
+ j = 1 if @verbose
69
+
70
+ @rooms.each do |room|
71
+ if @verbose
72
+ puts "On room #{i}"
73
+ i += 1
74
+ end
75
+
76
+ Hipchat::PostRepository.new(room).tap(&:load).each do |post|
77
+ if @verbose
78
+ print "On post #{j}\r"
79
+ j += 1
80
+ end
81
+
82
+ jsonl.puts(post.to_jsonl)
83
+ end
84
+
85
+ if @verbose
86
+ print "\n"
87
+ puts "Successfully wrote public room data\n\n"
88
+ end
89
+ end
90
+ end
91
+ end
92
+
93
+ def parse_rooms_arg
94
+ @rooms = []
95
+ @channels = []
96
+ @teams = []
97
+
98
+ @data.each_slice(2).each do |hipchat_room, mattermost_room|
99
+ if @verbose
100
+ puts "Parsing rooms for Hipchat and Mattermost..."
101
+ puts "Hipchat room is: #{hipchat_room}"
102
+ puts "Mattermost team & channel are: #{mattermost_room}"
103
+ end
104
+
105
+ team, channel_name = mattermost_room.split(":")
106
+ team = Mattermost::Team.new(team)
107
+ room = Hipchat.rooms.find_by_name(hipchat_room)
108
+ channel = Mattermost::Channel.from_hipchat(room, name: channel_name, team: team)
109
+
110
+ room.team = team
111
+ room.channel = channel
112
+
113
+ @rooms << room
114
+ @teams << team
115
+ @channels << channel
116
+
117
+ puts "Successfully parsed rooms\n\n" if @verbose
118
+ end
119
+
120
+ @teams.uniq!
121
+ @rooms.uniq!
122
+ @channels.uniq!
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,68 @@
1
+ require 'json'
2
+ require 'date'
3
+
4
+ require_relative "hipchat/user_repository"
5
+ require_relative "hipchat/room_repository"
6
+ require_relative "hipchat/post_repository"
7
+ require_relative "hipchat/conversion"
8
+
9
+ module Hipmost
10
+ module Hipchat
11
+ class << self
12
+ def users
13
+ @users ||= UserRepository.load_from($path)
14
+ end
15
+
16
+ def rooms
17
+ @rooms ||= RoomRepository.new($path).tap(&:load)
18
+ end
19
+
20
+ def direct_channels
21
+ Dir[$path.join("users", "**", "*.json")].flat_map do |file_path|
22
+ json = JSON.parse(File.read(file_path))
23
+ json.map do |message|
24
+ msg = message["PrivateUserMessage"]
25
+ sender = users[msg["sender"]["id"]]
26
+ receiver = users[msg["receiver"]["id"]]
27
+ [sender.username, receiver.username].sort
28
+ end
29
+ end.uniq
30
+ end
31
+
32
+ def direct_posts(file, verbose)
33
+ i = 1 if verbose
34
+
35
+ Dir[$path.join("users", "**", "*.json")].each do |file_path|
36
+ puts "Opening 1-on-1 room at #{file_path}..." if verbose
37
+ json = JSON.parse(File.read(file_path))
38
+ puts "Successfully parsed file at #{file_path}" if verbose
39
+
40
+ puts "Examining messages in this file..." if verbose
41
+
42
+ json.each do |message|
43
+ if verbose
44
+ print "On post #{i}\r"
45
+ i += 1
46
+ end
47
+
48
+ msg = message["PrivateUserMessage"]
49
+ sender = users[msg["sender"]["id"]]
50
+ receiver = users[msg["receiver"]["id"]]
51
+ message = msg["message"]
52
+ create_at = DateTime.strptime(msg["timestamp"]).to_time.to_i*1000
53
+
54
+ members = [ sender.username, receiver.username ] .sort
55
+
56
+ Conversion.convert_formatting_to_markdown(message)
57
+ file.puts(%[{ "type": "direct_post", "direct_post": { "channel_members": #{members.inspect}, "user": "#{sender.username}", "message": "#{message}", "create_at": #{create_at} } }])
58
+ end
59
+
60
+ if verbose
61
+ print "\n"
62
+ puts "Successfully wrote data for that 1-on-1 room\n\n"
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,26 @@
1
+ # Misc utilties to do data conversion from Hipchat to Mattermost
2
+
3
+ module Hipmost
4
+ module Conversion
5
+ def self.convert_formatting_to_markdown(message)
6
+ # According to Hipchat's docs, the only formatting commands which are
7
+ # translatable to Mattermost are /code and /quote.
8
+ # Relevant docs: https://confluence.atlassian.com/hipchat/keyboard-shortcuts-and-slash-commands-749385232.html#Keyboardshortcutsandslashcommands-Slashcommands
9
+
10
+ if message
11
+ if message.start_with?("/code") && message.lines.count > 1
12
+ message.sub!("/code", "```\n")
13
+ message << "\n```"
14
+ elsif message.start_with?("/code") && message.lines.count == 1
15
+ message.sub!("/code", "`")
16
+ message << "`"
17
+ end
18
+
19
+ if message.start_with?("/quote")
20
+ message.sub!("/quote", ">")
21
+ message.gsub!(/\n\n(.)/, "\n\n> \\1")
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,36 @@
1
+ require 'date'
2
+
3
+ require_relative "conversion"
4
+
5
+ module Hipmost
6
+ module Hipchat
7
+ class Post
8
+ def initialize(attrs, room, is_private_room)
9
+ @private = is_private_room
10
+ @attrs = attrs
11
+ @sender = Hipchat.users[attrs["sender"]["id"]]
12
+ @message = attrs["message"]
13
+ @created_at = DateTime.strptime(attrs["timestamp"])
14
+ @team = room.team
15
+ @channel = room.channel
16
+ if is_private_room
17
+ @receiver = Hipchat.users[attrs["sender"]["id"]]
18
+ end
19
+ end
20
+ attr_reader :team, :channel, :sender
21
+
22
+ def to_jsonl
23
+ # First, convert Hipchat formatting to markdown...
24
+ Conversion.convert_formatting_to_markdown(@message)
25
+
26
+ # Then, generate the actual object based on whether the room is private or not.
27
+ if @private
28
+ members = [@sender.username, @receiver.username].sort
29
+ %[{ "type": "direct_post", "direct_post": { "channel_members": #{members.inspect}, "user": "#{@sender.username}", "message": "#{JSON.dump(@message)}", "create_at": #{@created_at.to_time.to_i*1000} } }]
30
+ else
31
+ %[{ "type": "post", "post": { "team": "#{team.name}", "channel": "#{channel.name}", "user": "#{sender.username}", "message": #{JSON.dump(@message)}, "create_at": #{@created_at.to_time.to_i*1000} } }]
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,43 @@
1
+ require 'json'
2
+ require 'forwardable'
3
+ require_relative 'post'
4
+
5
+ module Hipmost
6
+ module Hipchat
7
+ class PostRepository
8
+ attr_accessor :posts, :name_index
9
+ extend Forwardable
10
+
11
+ def_delegators :@posts, :size, :[], :select, :each
12
+
13
+ def self.for_room(room)
14
+ new(room).tap(&:load)
15
+ end
16
+
17
+ def initialize(room)
18
+ @room = room
19
+ @path = $path.join("rooms", room.id.to_s, "history.json")
20
+ @posts = []
21
+ end
22
+
23
+ def load(data = file_data)
24
+ return if !File.exists?(@path)
25
+ json = JSON.load(data)
26
+
27
+ json.each do |post_obj|
28
+ next if post_obj.key?("NotificationMessage")
29
+ next if post_obj.key?("GuestAccessMessage")
30
+ next if post_obj.key?("ArchiveRoomMessage")
31
+ next if post_obj.key?("TopicRoomMessage")
32
+ post = post_obj["UserMessage"]
33
+ @posts << Post.new(post, @room)
34
+ end
35
+ end
36
+
37
+ def file_data
38
+ return if !File.exists?(@path)
39
+ File.read(@path)
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,57 @@
1
+
2
+ =begin
3
+ {
4
+ "Room": {
5
+ "created": "2013-05-27T13:29:02+00:00",
6
+ "guest_access_url": null,
7
+ "id": 206455,
8
+ "is_archived": false,
9
+ "members": [
10
+ 752160,
11
+ 1002528,
12
+ 844771,
13
+ 340070,
14
+ 1017103,
15
+ 340825
16
+ ],
17
+ "name": "Orbital Impact",
18
+ "owner": 340070,
19
+ "participants": [],
20
+ "privacy": "private",
21
+ "topic": "Welcome! Send this link to coworkers who need accounts: https://www.hipchat.com/invite/50371/3c380f069e21ba92e3441c57952e4ed0"
22
+ }
23
+ },
24
+ =end
25
+
26
+ module Hipmost
27
+ module Hipchat
28
+ class Room
29
+ def initialize(attrs)
30
+ @id = attrs["id"]
31
+ @name = attrs["name"].gsub(/\s/, "-").downcase
32
+ @display_name = attrs["name"]
33
+ @topic = attrs["topic"]
34
+ @attrs = attrs
35
+ end
36
+ attr_reader :id, :attrs, :name, :display_name, :topic
37
+ attr_accessor :team, :channel
38
+
39
+ def private?
40
+ privacy == "private"
41
+ end
42
+
43
+ def users
44
+ @users ||= attrs["members"].map{|uid| Hipchat.users[uid] }
45
+ end
46
+
47
+ def posts
48
+ @posts ||= PostRepository.for_room(self)
49
+ end
50
+
51
+ def method_missing(method)
52
+ attrs[method.to_s]
53
+ end
54
+
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,46 @@
1
+ require 'json'
2
+ require 'forwardable'
3
+ require_relative "room"
4
+
5
+ module Hipmost
6
+ module Hipchat
7
+ class RoomRepository
8
+ attr_accessor :rooms, :name_index
9
+ extend Forwardable
10
+
11
+ def_delegators :@rooms, :size, :[], :select, :each
12
+
13
+ def self.load_from(path)
14
+ new(path).tap(&:load)
15
+ end
16
+
17
+ def initialize(path)
18
+ @path = Pathname.new(path).join("rooms.json")
19
+ @rooms = {}
20
+ @name_index = {}
21
+ end
22
+
23
+ def load(data = file_data)
24
+ json = JSON.load(data)
25
+
26
+ json.each do |room_obj|
27
+ room = room_obj["Room"]
28
+ @rooms[room["id"]] = Room.new(room)
29
+ @name_index[room["name"]] = room["id"]
30
+ end
31
+ end
32
+
33
+ def find_by_name(name)
34
+ self[name_index[name]]
35
+ end
36
+
37
+ def file_data
38
+ if File.exists? @path
39
+ File.read(@path)
40
+ else
41
+ abort "./data does not exist; did you forget to specify a path to the data?"
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,74 @@
1
+ require 'json'
2
+ require 'forwardable'
3
+
4
+ module Hipmost
5
+ module Hipchat
6
+ class UserRepository
7
+ attr_accessor :users
8
+ extend Forwardable
9
+
10
+ def_delegators :@users, :size, :[], :select, :each
11
+
12
+ def self.load_from(path)
13
+ new(path).tap(&:load)
14
+ end
15
+
16
+ def initialize(path)
17
+ @path = Pathname.new(path).join("users.json")
18
+ @users = {}
19
+ @name_index = {}
20
+ end
21
+
22
+ def load(data = file_data)
23
+ json = JSON.load(data)
24
+
25
+ json.each do |user_obj|
26
+ user = user_obj["User"]
27
+ user_obj = User.new(user)
28
+ @users[user["id"]] = user_obj
29
+ @name_index[user_obj.username] = user["id"]
30
+ end
31
+ end
32
+
33
+ def file_data
34
+ File.read(@path)
35
+ end
36
+
37
+ class User
38
+ def initialize(attrs)
39
+ @id = attrs["id"]
40
+ @attrs = attrs
41
+ end
42
+ attr_reader :id, :attrs
43
+
44
+ def guest?
45
+ attrs["account_type"] == "guest"
46
+ end
47
+
48
+ def inactive?
49
+ attrs["is_deleted"]
50
+ end
51
+
52
+ def method_missing(method)
53
+ attrs[method.to_s]
54
+ end
55
+
56
+ def username
57
+ attrs["mention_name"]
58
+ end
59
+
60
+ def email
61
+ attrs["email"] || "#{username}@orbitalimpact.com"
62
+ end
63
+
64
+ def teams
65
+ []
66
+ end
67
+
68
+ def to_jsonl
69
+ %[{ "type": "user", "user": { "username": "#{username}", "email": "#{email}", "teams": #{teams.inspect} } }]
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,7 @@
1
+ require_relative "mattermost/team"
2
+ require_relative "mattermost/channel"
3
+
4
+ module Hipmost
5
+ module Mattermost
6
+ end
7
+ end
@@ -0,0 +1,26 @@
1
+ module Hipmost
2
+ module Mattermost
3
+ class Channel
4
+ def self.from_hipchat(room, name:, team: )
5
+ new(name: name,
6
+ type: room.private? ? "P" : "O",
7
+ display_name: room.display_name,
8
+ header: room.topic,
9
+ team: team)
10
+ end
11
+
12
+ def initialize(name:, team:, display_name:, type:, header:)
13
+ @name = name.downcase.gsub(/\s/, "-")
14
+ @team = team
15
+ @display_name = display_name
16
+ @type = type
17
+ @header = header
18
+ end
19
+ attr_reader :name
20
+
21
+ def to_jsonl
22
+ %[{ "type": "channel", "channel": { "team": "#{@team.name}", "name": "#@name", "display_name": "#@display_name", "type": "#@type", "header": "#@header" } }]
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,22 @@
1
+ module Hipmost
2
+ module Mattermost
3
+ class Team
4
+ def initialize(display_name)
5
+ @display_name = display_name
6
+ end
7
+ attr_reader :display_name
8
+
9
+ def to_jsonl
10
+ %[{ "type": "team", "team": { "display_name": "#@display_name", "name": "#{name}", "type": "I", "description": "#@display_name", "allow_open_invite": false } }]
11
+ end
12
+
13
+ def name
14
+ if display_name == "Orbital Impact"
15
+ "oi"
16
+ else
17
+ @display_name.gsub(/\s/, "-").downcase
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,4 @@
1
+ module Hipmost
2
+ class RoomImporter
3
+ end
4
+ end
@@ -0,0 +1,3 @@
1
+ module Hipmost
2
+ VERSION = "1.0"
3
+ end
metadata ADDED
@@ -0,0 +1,160 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hipmost
3
+ version: !ruby/object:Gem::Version
4
+ version: '1.0'
5
+ platform: ruby
6
+ authors:
7
+ - Gabriel Rios
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-10-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.16'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.16'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.10.3
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.10.3
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry-doc
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: 0.8.0
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: 0.8.0
83
+ - !ruby/object:Gem::Dependency
84
+ name: method_source
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: 0.8.2
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: 0.8.2
97
+ description: Migrate your hipchat history to Mattermost
98
+ email:
99
+ - gabrielfalcaorios@gmail.com
100
+ executables:
101
+ - hipmost
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - ".gitignore"
106
+ - ".rspec"
107
+ - ".travis.yml"
108
+ - CODE_OF_CONDUCT.md
109
+ - Gemfile
110
+ - Gemfile.lock
111
+ - HOWTO.md
112
+ - KNOWN-BUGS.md
113
+ - LICENSE.txt
114
+ - README.md
115
+ - Rakefile
116
+ - bin/console
117
+ - bin/setup
118
+ - exe/hipmost
119
+ - hipmost.gemspec
120
+ - lib/hipmost.rb
121
+ - lib/hipmost/cmds.rb
122
+ - lib/hipmost/cmds/private.rb
123
+ - lib/hipmost/cmds/room.rb
124
+ - lib/hipmost/hipchat.rb
125
+ - lib/hipmost/hipchat/conversion.rb
126
+ - lib/hipmost/hipchat/post.rb
127
+ - lib/hipmost/hipchat/post_repository.rb
128
+ - lib/hipmost/hipchat/room.rb
129
+ - lib/hipmost/hipchat/room_repository.rb
130
+ - lib/hipmost/hipchat/user_repository.rb
131
+ - lib/hipmost/mattermost.rb
132
+ - lib/hipmost/mattermost/channel.rb
133
+ - lib/hipmost/mattermost/team.rb
134
+ - lib/hipmost/room.rb
135
+ - lib/hipmost/version.rb
136
+ homepage:
137
+ licenses:
138
+ - MIT
139
+ metadata: {}
140
+ post_install_message:
141
+ rdoc_options: []
142
+ require_paths:
143
+ - lib
144
+ required_ruby_version: !ruby/object:Gem::Requirement
145
+ requirements:
146
+ - - ">="
147
+ - !ruby/object:Gem::Version
148
+ version: '0'
149
+ required_rubygems_version: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ requirements: []
155
+ rubyforge_project:
156
+ rubygems_version: 2.7.7
157
+ signing_key:
158
+ specification_version: 4
159
+ summary: Migrate your hipchat history to Mattermost
160
+ test_files: []