discourse/script/import_scripts/ning.rb
Sam Saffron 30990006a9 DEV: enable frozen string literal on all files
This reduces chances of errors where consumers of strings mutate inputs
and reduces memory usage of the app.

Test suite passes now, but there may be some stuff left, so we will run
a few sites on a branch prior to merging
2019-05-13 09:31:32 +08:00

404 lines
12 KiB
Ruby

# frozen_string_literal: true
require File.expand_path(File.dirname(__FILE__) + "/base.rb")
# Edit the constants and initialize method for your import data.
class ImportScripts::Ning < ImportScripts::Base
JSON_FILES_DIR = "/Users/techapj/Downloads/ben/ADEM"
ATTACHMENT_PREFIXES = ["discussions", "pages", "blogs", "members", "photos"]
EXTRA_AUTHORIZED_EXTENSIONS = ["bmp", "ico", "txt", "pdf", "gif", "jpg", "jpeg", "html"]
def initialize
super
@system_user = Discourse.system_user
@users_json = load_ning_json("ning-members-local.json")
@discussions_json = load_ning_json("ning-discussions-local.json")
# An example of a custom category from Ning:
@blogs_json = load_ning_json("ning-blogs-local.json")
@photos_json = load_ning_json("ning-photos-local.json")
@pages_json = load_ning_json("ning-pages-local.json")
SiteSetting.max_image_size_kb = 10240
SiteSetting.max_attachment_size_kb = 10240
SiteSetting.authorized_extensions = (SiteSetting.authorized_extensions.split("|") + EXTRA_AUTHORIZED_EXTENSIONS).uniq.join("|")
# Example of importing a custom profile field:
# @interests_field = UserField.find_by_name("My interests")
# unless @interests_field
# @interests_field = UserField.create(name: "My interests", description: "Do you like stuff?", field_type: "text", editable: true, required: false, show_on_profile: true, show_on_user_card: true)
# end
end
def execute
puts "", "Importing from Ning..."
import_users
import_categories
import_discussions
import_blogs # Remove this and/or add more as necessary
import_photos
import_pages
suspend_users
update_tl0
puts "", "Done"
end
def load_ning_json(arg)
filename = File.join(JSON_FILES_DIR, arg)
raise RuntimeError.new("File #{filename} not found!") if !File.exists?(filename)
JSON.parse(repair_json(File.read(filename))).reverse
end
def repair_json(arg)
arg.gsub!(/^\(/, "") # content of file is surround by ( )
arg.gsub!(/\)$/, "")
arg.gsub!(/\]\]$/, "]") # there can be an extra ] at the end
arg.gsub!(/\}\{/, "},{") # missing commas sometimes!
arg.gsub!("}]{", "},{") # surprise square brackets
arg.gsub!("}[{", "},{") # :troll:
arg
end
def import_users
puts '', "Importing users"
staff_levels = ["admin", "moderator", "owner"]
create_users(@users_json) do |u|
{
id: u["contributorName"],
name: u["fullName"],
email: u["email"],
created_at: Time.zone.parse(u["createdDate"]),
date_of_birth: u["birthdate"] ? Time.zone.parse(u["birthdate"]) : nil,
location: "#{u["location"]} #{u["country"]}",
avatar_url: u["profilePhoto"],
bio_raw: u["profileQuestions"].is_a?(Hash) ? u["profileQuestions"]["About Me"] : nil,
post_create_action: proc do |newuser|
# if u["profileQuestions"].is_a?(Hash)
# newuser.custom_fields = {"user_field_#{@interests_field.id}" => u["profileQuestions"]["My interests"]}
# end
if staff_levels.include?(u["level"].downcase)
if u["level"].downcase == "admin" || u["level"].downcase == "owner"
newuser.admin = true
else
newuser.moderator = true
end
end
# states: ["active", "suspended", "left", "pending"]
if u["state"] == "active" && newuser.approved_at.nil?
newuser.approved = true
newuser.approved_by_id = @system_user.id
newuser.approved_at = newuser.created_at
end
newuser.save
if u["profilePhoto"] && newuser.user_avatar.try(:custom_upload_id).nil?
photo_path = file_full_path(u["profilePhoto"])
if File.exists?(photo_path)
begin
upload = create_upload(newuser.id, photo_path, File.basename(photo_path))
if upload.persisted?
newuser.import_mode = false
newuser.create_user_avatar
newuser.import_mode = true
newuser.user_avatar.update(custom_upload_id: upload.id)
newuser.update(uploaded_avatar_id: upload.id)
else
puts "Error: Upload did not persist for #{photo_path}!"
end
rescue SystemCallError => err
puts "Could not import avatar #{photo_path}: #{err.message}"
end
else
puts "avatar file not found at #{photo_path}"
end
end
end
}
end
EmailToken.delete_all
end
def suspend_users
puts '', "Updating suspended users"
count = 0
suspended = 0
total = @users_json.size
@users_json.each do |u|
if u["state"].downcase == "suspended"
if user = find_user_by_import_id(u["contributorName"])
user.suspended_at = Time.zone.now
user.suspended_till = 200.years.from_now
if user.save
StaffActionLogger.new(@system_user).log_user_suspend(user, "Import data indicates account is suspended.")
suspended += 1
else
puts "Failed to suspend user #{user.username}. #{user.errors.try(:full_messages).try(:inspect)}"
end
end
end
count += 1
print_status count, total
end
puts "", "Marked #{suspended} users as suspended."
end
def import_categories
puts "", "Importing categories"
create_categories((["Blog", "Pages", "Photos"] + @discussions_json.map { |d| d["category"] }).uniq.compact) do |name|
if name.downcase == "uncategorized"
nil
else
{
id: name, # ning has no id for categories, so use the name
name: name
}
end
end
end
def import_discussions
puts "", "Importing discussions"
import_topics(@discussions_json)
end
def import_blogs
puts "", "Importing blogs"
import_topics(@blogs_json, "Blog")
end
def import_photos
puts "", "Importing photos"
import_topics(@photos_json, "Photos")
end
def import_pages
puts "", "Importing pages"
import_topics(@pages_json, "Pages")
end
def import_topics(topics_json, default_category = nil)
topics = 0
posts = 0
total = topics_json.size # number of topics. posts are embedded in the topic json, so we can't get total post count quickly.
topics_json.each do |topic|
if topic["title"].present? && topic["description"].present?
@current_topic_title = topic["title"] # for debugging
parent_post = nil
if parent_post_id = post_id_from_imported_post_id(topic["id"])
parent_post = Post.find(parent_post_id) # already imported this post
else
mapped = {}
mapped[:id] = topic["id"]
mapped[:user_id] = user_id_from_imported_user_id(topic["contributorName"]) || -1
mapped[:created_at] = Time.zone.parse(topic["createdDate"])
unless topic["category"].nil? || topic["category"].downcase == "uncategorized"
mapped[:category] = category_id_from_imported_category_id(topic["category"])
end
if topic["category"].nil? && default_category
mapped[:category] = default_category
end
mapped[:title] = CGI.unescapeHTML(topic["title"])
mapped[:raw] = process_ning_post_body(topic["description"])
if topic["fileAttachments"]
mapped[:raw] = add_file_attachments(mapped[:raw], topic["fileAttachments"])
end
if topic["photoUrl"]
mapped[:raw] = add_photo(mapped[:raw], topic["photoUrl"])
end
if topic["embedCode"]
mapped[:raw] = add_video(mapped[:raw], topic["embedCode"])
end
parent_post = create_post(mapped, mapped[:id])
unless parent_post.is_a?(Post)
puts "Error creating topic #{mapped[:id]}. Skipping."
puts parent_post.inspect
end
end
if topic["comments"].present?
topic["comments"].reverse.each do |post|
if post_id_from_imported_post_id(post["id"])
next # already imported this post
end
raw = process_ning_post_body(post["description"])
if post["fileAttachments"]
raw = add_file_attachments(raw, post["fileAttachments"])
end
new_post = create_post({
id: post["id"],
topic_id: parent_post.topic_id,
user_id: user_id_from_imported_user_id(post["contributorName"]) || -1,
raw: raw,
created_at: Time.zone.parse(post["createdDate"])
}, post["id"])
if new_post.is_a?(Post)
posts += 1
else
puts "Error creating post #{post["id"]}. Skipping."
puts new_post.inspect
end
end
end
end
topics += 1
print_status topics, total
end
puts "", "Imported #{topics} topics with #{topics + posts} posts."
[topics, posts]
end
def file_full_path(relpath)
File.join JSON_FILES_DIR, relpath.split("?").first
end
def attachment_regex
@_attachment_regex ||= Regexp.new(%Q[<a (?:[^>]*)href="(?:#{ATTACHMENT_PREFIXES.join('|')})\/(?:[^"]+)"(?:[^>]*)><img (?:[^>]*)src="([^"]+)"(?:[^>]*)><\/a>])
end
def youtube_iframe_regex
@_youtube_iframe_regex ||= Regexp.new(%Q[<p><iframe(?:[^>]*)src="\/\/www.youtube.com\/embed\/([^"]+)"(?:[^>]*)><\/iframe>(?:[^<]*)<\/p>])
end
def process_ning_post_body(arg)
return "" if arg.nil?
raw = arg.gsub("</p>\n", "</p>")
# youtube iframe
raw.gsub!(youtube_iframe_regex) do |s|
matches = youtube_iframe_regex.match(s)
video_id = matches[1].split("?").first
next s unless video_id
"\n\nhttps://www.youtube.com/watch?v=#{video_id}\n"
end
# attachments
raw.gsub!(attachment_regex) do |s|
matches = attachment_regex.match(s)
ning_filename = matches[1]
filename = File.join(JSON_FILES_DIR, ning_filename.split("?").first)
if !File.exists?(filename)
puts "Attachment file doesn't exist: #{filename}"
next s
end
upload = create_upload(@system_user.id, filename, File.basename(filename))
if upload.nil? || !upload.valid?
puts "Upload not valid :( #{filename}"
puts upload.errors.inspect if upload
next s
end
html_for_upload(upload, File.basename(filename))
end
raw
end
def add_file_attachments(arg, file_names)
raw = arg
file_names.each do |f|
filename = File.join(JSON_FILES_DIR, f.split("?").first)
if !File.exists?(filename)
puts "Attachment file doesn't exist: #{filename}"
next
end
upload = create_upload(@system_user.id, filename, File.basename(filename))
if upload.nil? || !upload.valid?
puts "Upload not valid :( #{filename}"
puts upload.errors.inspect if upload
next
end
raw += "\n" + attachment_html(upload, File.basename(filename))
end
raw
end
def add_photo(arg, file_name)
raw = arg
# filename = File.join(JSON_FILES_DIR, file_name)
filename = file_full_path(file_name)
if File.exists?(filename)
upload = create_upload(@system_user.id, filename, File.basename(filename))
if upload.nil? || !upload.valid?
puts "Upload not valid :( #{filename}"
puts upload.errors.inspect if upload
return
end
raw += "\n" + embedded_image_html(upload)
else
puts "Attachment file doesn't exist: #{filename}"
end
raw
end
def add_video(arg, embed_code)
raw = arg
youtube_regex = Regexp.new(%Q[<iframe(?:[^>]*)src="http:\/\/www.youtube.com\/embed\/([^"]+)"(?:[^>]*)><\/iframe>])
raw.gsub!(youtube_regex) do |s|
matches = youtube_regex.match(s)
video_id = matches[1].split("?").first
if video_id
raw += "\n\nhttps://www.youtube.com/watch?v=#{video_id}\n"
end
end
raw += "\n" + embed_code + "\n"
raw
end
end
if __FILE__ == $0
ImportScripts::Ning.new.perform
end