Migrar un foro XenForo a Discourse

He propuesto una arquitectura limpia para el nuevo servicio. Pero “limpio” no siempre significa simple.i) do
username = $1
imported_post_id = $2

  if imported_post_id.present?
    topic_mapping = topic_lookup_from_imported_post_id(imported_post_id.to_i)
    if topic_mapping
      "\n[quote=\"#{username}, post:#{topic_mapping[:post_number]}, topic:#{topic_mapping[:topic_id]}\"]\n"
    else
      "\n[quote=\"#{username}\"]\n"
    end
  else
    "\n[quote=\"#{username}\"]\n"
  end
end

s.gsub!(%r{\[/QUOTE\]}i, "\n[/quote]\n")
s.gsub!(%r{\[HEADING=1\](.+?)\[/HEADING\]}i) { "\n# #{$1}\n" }
s.gsub!(%r{\[HEADING=2\](.+?)\[/HEADING\]}i) { "\n## #{$1}\n" }
s.gsub!(%r{\[HEADING=3\](.+?)\[/HEADING\]}i) { "\n### #{$1}\n" }

s.gsub!(%r{\[SPOILER="?([^\]]*?)"?\](.*?)\[/SPOILER\]}im) do
  title = $1.presence || "Spoiler"
  content = $2
  "\n[details=\"#{title}\"]\n#{content}\n[/details]\n"
end

s.gsub!(%r{\[CODE="?([a-zA-Z0-9_\-+]*)"?\](.*?)\[/CODE\]}im) do
  lang = $1.presence || ""
  code = $2
  "\n```#{lang}\n#{code}\n```\n"
end

s.gsub!(%r{[USER=\d+]@?(.+?)[/USER]}i, ‘@\1’)
s.gsub!(%r{[MEDIA=youtube](.+?)[/MEDIA]}i, ‘https://www.youtube.com/watch?v=\1’)
s.gsub!(%r{[MEDIA=[^]]+](.+?)[/MEDIA]}i, ‘\1’)

# Eliminar etiquetas BBCode de presentación que Markdown no utiliza
s.gsub!(%r{\[/?(?:LEFT|RIGHT|CENTER|JUSTIFY|FONT|SIZE|COLOR|INDENT)(?:=[^\]]+)?\]}i, '')

# Procesar y reemplazar las etiquetas [ATTACH] con cargas de Discourse
s = process_xf_attachments("post", s, import_id) if import_id.to_i > 0

s.strip

end

=========================================================================

11. Resolución de adjuntos y manejo de archivos

=========================================================================

def process_xf_attachments(content_type, text, content_id)
sql = "
SELECT a.attachment_id, a.data_id, d.filename, d.file_hash, d.user_id
FROM #{TABLE_PREFIX}attachment a
INNER JOIN #{TABLE_PREFIX}attachment_data d ON a.data_id = d.data_id
WHERE a.content_type = ‘#{@client.escape(content_type.to_s)}’
AND a.content_id = #{content_id.to_i}
"
attachments = mysql_query(sql).to_a
return text if attachments.empty?

embedded_ids = Set.new

attachments.each do |att|
  att_id = att[:attachment_id]
  upload = import_xf_attachment_file(att[:data_id], att[:file_hash], att[:user_id], att[:filename])
  next unless upload&.persisted?

  html = @uploader.html_for_upload(upload, att[:filename])

  # Coincidir con el formato estándar [ATTACH]123[/ATTACH] así como con las variantes de atributos de XF 2.3 como [ATTACH type="full" alt="..."]123[/ATTACH]
  pattern = %r{\[ATTACH[^\]]*\]\s*#{att_id}\s*\[/ATTACH\]}i
  if text.match?(pattern)
    text.gsub!(pattern, "\n#{html}\n")
    embedded_ids.add(att_id)
  end
end

# Agregar cualquier adjunto que no se haya colocado en línea en el cuerpo del texto
unattached = attachments.reject { |a| embedded_ids.include?(a[:attachment_id]) }
if unattached.any?
  text += "\n\n"
  unattached.each do |att|
    upload = import_xf_attachment_file(att[:data_id], att[:file_hash], att[:user_id], att[:filename])
    next unless upload&.persisted?
    html = @uploader.html_for_upload(upload, att[:filename])
    text += "#{html}\n"
  end
end

# Paso final de limpieza: purgar cualquier marca [ATTACH] huérfana o ausente
text.gsub!(%r{\[ATTACH[^\]]*\]\s*\d*\s*\[/ATTACH\]}i, '')

text

end

def import_xf_attachment_file(data_id, file_hash, owner_id, original_filename)
group_id = data_id.to_i / 1000

exact_name   = "#{data_id}-#{file_hash}.data"
path_grouped = Pathname.new(File.join(ATTACHMENT_DIR, group_id.to_s, exact_name))
path_flat    = Pathname.new(File.join(ATTACHMENT_DIR, exact_name))

path = if File.exist?(path_grouped)
         path_grouped
       elsif File.exist?(path_flat)
         path_flat
       else
         glob_grouped = Dir.glob(File.join(ATTACHMENT_DIR, group_id.to_s, "#{data_id}-*.data")).first
         glob_flat    = Dir.glob(File.join(ATTACHMENT_DIR, "#{data_id}-*.data")).first
         found = glob_grouped || glob_flat
         if found
           Pathname.new(found)
         else
           log_missing_file(:attachment,
             data_id: data_id,
             filename: original_filename,
             db_hash: file_hash,
             expected_grouped: path_grouped.to_s,
             expected_flat: path_flat.to_s
           )
           return nil
         end
       end

discourse_user_id = user_id_from_imported_user_id(owner_id) || Discourse::SYSTEM_USER_ID

temp_path = path.dirname.join(original_filename)
FileUtils.cp(path, temp_path)

upload = create_upload(discourse_user_id, temp_path, original_filename)
FileUtils.rm(temp_path) if File.exist?(temp_path)

upload

rescue StandardError => e
STDERR.puts “Failed to process attachment data_id #{data_id}: #{e.message}”
nil
end

def mysql_query(sql)
@client.query(sql)
end
end

ImportScripts::XenForo23.new.perform


He tenido problemas con algunos usuarios que no se han podido importar debido a direcciones de correo electrónico extrañas creadas por spambots. Por ejemplo, correos como Peter.Today.Rain.Import.Something@Dave.Gmail.com.

Actualizaré / editaré mi código de importación anterior una vez que estén disponibles los campos personalizados, ya que estos son importantes para mí.

.

![image|690x275](upload://9Dj6MAdiJpSiak5nKQFy59y70Yg.jpeg)

![image|690x268](upload://tjfkDVWhujr6qavpYTAcEIVdaCL.jpeg)

![image|690x386](upload://okHsZo8MARU83hyECp3L94rmwr9.png)
4 Me gusta