Last active
May 27, 2022 22:04
-
-
Save icelander/80874e9d6f362146bd421265b79bfafa to your computer and use it in GitHub Desktop.
Migrates custom emoji from one Mattermost server to another
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
# frozen_string_literal: true | |
## migrate_custom_emoji.rb | |
# | |
## ABOUT | |
# | |
# This script migrates custom emoji from one Mattermost instance to another. | |
# While it can be run on the source, any machine that can access both | |
# servers via HTTP that has a copy of the `/opt/mattermost/data/emoji` | |
# directory will work. | |
# | |
## HOW TO USE | |
# | |
# 1. Install Ruby and the HTTParty gem | |
# 2. Put this script somewhere on your Mattermost server | |
# 3. Generate a personal access token with admin privileges in both servers | |
# 4. Change these values to match your environment. Be sure to quote the | |
# strings properly, e.g. | |
# | |
# source_server = 'this is correct' | |
# auth_token = this is not correct | |
# emoji_path = 'Neither is this" | |
# destination_server = ''Also incorrect'' | |
# | |
# URL of the source Mattermost server | |
source_server = 'https://mattermost.movetoiceland.com/' | |
# Authentication token for source Mattermost server | |
source_token = 'qtmwjk6q8igyfqm491u9unbk1w' | |
# Path to Emoji Directory, NOTE: Use '/opt/mattermost/data/emoji' if you are | |
# running this on the source Mattermost server | |
emoji_path = './emoji/' | |
# URL of the destination Mattermost server | |
destination_server = 'https://chat.iclnd.me/' | |
# Authentication token for source Mattermost server | |
destination_token = '871nkkt8ht8ad8urfonefxadce' | |
# | |
# 5. Run the script through Ruby and use tee to capture the output: | |
# | |
# ruby migrate_custom_emoji.rb | tee migrate_custom_emoji.log | |
# | |
# 6. Check the destination for the custom emoji | |
# 7. (Optional) Delete the personal access tokens and this script for security | |
####################################### | |
### DO NOT EDIT ANYTHING BELOW HERE ### | |
####################################### | |
require 'httparty' | |
require 'uri' | |
require 'fileutils' | |
def check_version(min_version, level=0) | |
if min_version.kind_of?(String) | |
min_version = min_version.split('.') | |
end | |
if level > min_version.length | |
return true | |
end | |
ruby_version = RUBY_VERSION.split('.') | |
if min_version[level].to_i < ruby_version[level].to_i | |
return true | |
elsif min_version[level].to_i == ruby_version[level].to_i | |
check_version(min_version, level+1) | |
else | |
return false | |
end | |
end | |
min_version = '2.5.1' | |
if ! check_version(min_version) | |
puts "Please install Ruby version #{min_version}" | |
end | |
class MattermostApi | |
include HTTParty | |
format :json | |
# UNCOMMENT NEXT LINE TO DEBUG REQUESTS | |
# debug_output $stdout | |
def initialize(mattermost_url, auth_token) | |
# Default Options | |
@options = { | |
headers: { | |
'Content-Type' => 'application/json', | |
'User-Agent' => 'HTTParty On, Dudes!' | |
}, | |
# TODO Make this more secure | |
verify: false | |
} | |
# check the config for mattermost_url | |
if mattermost_url.nil? or !url_valid?(mattermost_url) | |
raise 'url is required in configuration' | |
else | |
if mattermost_url[-1] != '/' | |
mattermost_url = mattermost_url + '/' | |
end | |
end | |
@base_uri = mattermost_url + 'api/v4/' | |
@options[:headers]['Authorization'] = "Bearer #{auth_token}" | |
@options[:body] = nil | |
@user = nil | |
test_response = self.class.get("#{@base_uri}users/me", @options) | |
if test_response.code == 200 | |
@user = JSON.parse(test_response.to_s) | |
else | |
puts "Could not connect to Mattermost server #{mattermost_url}, aborting." | |
exit 1 | |
end | |
if @user.nil? || @user['id'].nil? | |
puts "Could not get current user, aborting." | |
exit 1 | |
end | |
end | |
def get_custom_emoji | |
return self.get_all('emoji') | |
end | |
def emoji_exists?(emoji_name) | |
url = "emoji/name/#{emoji_name}" | |
return self.get_exists(url, emoji_name, 'name') | |
end | |
def upload_emoji(emoji_name, image_path) | |
post_body = { | |
emoji: { | |
name: emoji_name, | |
creator_id: @user['id'] | |
}.to_json, | |
image: File.open(image_path) | |
} | |
upload_response = self.class.post( | |
@base_uri + 'emoji', | |
multipart: true, | |
body: post_body, | |
headers: @options[:headers] | |
) | |
if upload_response.code == 200 | |
return JSON.parse(upload_response.to_s) | |
else | |
puts "ERROR Could not upload #{emoji_name}" | |
return false | |
end | |
end | |
def post_data(payload, request_url) | |
options = @options | |
options[:body] = payload.to_json | |
return self.class.post("#{@base_uri}#{request_url}", options) | |
end | |
def url_valid?(url) | |
url = URI.parse(url) rescue false | |
end | |
def get_all(url) | |
results = Array.new | |
per_page = 60 | |
page = 0 | |
loop do | |
page_url = url + "?per_page=#{per_page}&page=#{page}" | |
returned = self.get_url(page_url) | |
results += returned | |
page += 1 | |
break if returned.count != per_page | |
end | |
return results | |
end | |
def get_url(url) | |
JSON.parse(self.class.get("#{@base_uri}#{url}", @options).to_s) | |
end | |
def get_exists(url, compare_value=nil, compare_field=nil) | |
# How does one test that something exists? | |
# Well, get the url and if it returns 200 | |
uri = @base_uri + url | |
resp = self.class.get(uri, @options) | |
case resp.code | |
when 200 | |
if !compare_field.nil? and !compare_value.nil? | |
result = JSON.parse(resp.to_s) | |
if !result[compare_field].nil? | |
if compare_value == result[compare_field] | |
return true | |
else | |
return false | |
end | |
else | |
raise "Error checking for emoji existence" | |
end | |
else | |
return true | |
end | |
when 404 | |
return false | |
else | |
raise "Error checking existence" | |
end | |
if resp.code == 200 | |
else | |
return false | |
end | |
end | |
end | |
puts '=' * 80 | |
output = <<EOF | |
Migrating custom emoji with these settings | |
- Source: #{source_server} | |
- Emoji Directory Path: #{emoji_path} | |
- Destination: #{destination_server} | |
Beginning Migration.... | |
EOF | |
puts output | |
puts '=' * 80 | |
$source_mm = MattermostApi.new(source_server, source_token) | |
$destination_mm = MattermostApi.new(destination_server, destination_token) | |
# Get the custom emoji as an array | |
custom_emoji_list = $source_mm.get_custom_emoji() | |
count_total = 0 | |
count_migrated = 0 | |
count_errors = 0 | |
puts "Found #{custom_emoji_list.length} custom emoji on source server" | |
custom_emoji_list.each do |emoji| | |
count_total += 1 | |
begin | |
if $destination_mm.emoji_exists?(emoji['name']) | |
puts ":#{emoji['name']}: exists on destination, skipping" | |
count_errors = count_errors + 1 | |
next | |
end | |
rescue Exception => e | |
count_errors = count_errors + 1 | |
puts "Error checking for :#{emoji['name']}: existence on destination, skipping" | |
next | |
end | |
emoji_dir = emoji_path + emoji['id'] | |
image_file = emoji_path + emoji['id'] + '/image' | |
if File.readable?(image_file) | |
resp = $destination_mm.upload_emoji(emoji['name'], image_file) | |
if resp | |
count_migrated = count_migrated + 1 | |
puts ":#{resp['name']}: migrated!" | |
else | |
count_errors = count_errors + 1 | |
end | |
else | |
puts ":#{emoji['name']}: image file #{image_file} can't be read, skipping" | |
count_errors = count_errors + 1 | |
end | |
end | |
puts '=' * 80 | |
output = <<EOF | |
Migration Complete! | |
Results: | |
- Total Emoji Found: #{custom_emoji_list.length} | |
- Total Emoji Processed: #{count_total} | |
- Total Migrated: #{count_migrated} | |
- Total Errors: #{count_errors} | |
EOF | |
puts output | |
puts '=' * 80 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment