Files
danbooru/app/controllers/posts_controller.rb
evazion 43c4158d36 uploads: merge tags when a duplicate is uploaded (fix #3130).
Automatically merge tags when uploading a duplicate.

There are two cases:

* You try to upload an image, but it's already on Danbooru. In this case
  you'll be immediately redirected to the original post, before you
  can start tagging the upload.

* You're uploading an image, it wasn't a dupe when you first opened the
  upload page, but you got sniped while tagging it. In this case your tags
  will be merged with the original post, and you will be redirected to the
  original post.

There are a few corner cases:

* If you don't have permission to edit the original post, for example
  because it's banned or has a censored tag, then your tags won't be
  merged and will be silently ignored.

* Only the tags, rating, and parent ID will be merged. The source and
  artist commentary won't be merged. This is so that if an artist uploads
  the exact same file to multiple sites, the new source won't override
  the original source.

* Some tags might be contradictory. For example, the new post might
  be tagged translation_request, but the original post might already be
  translated. It's up to the user to fix these things afterwards.
2022-01-30 03:14:22 -06:00

182 lines
6.0 KiB
Ruby

# frozen_string_literal: true
class PostsController < ApplicationController
respond_to :html, :xml, :json, :js
layout "sidebar"
before_action :log_search_query, only: :index
after_action :log_search_count, only: :index, if: -> { request.format.html? && response.successful? }
rate_limit :index, rate: 1.0/2.seconds, burst: 50, if: -> { request.format.atom? }, key: "posts:index.atom"
def index
if params[:md5].present?
@post = authorize Post.find_by!(md5: params[:md5])
respond_with(@post) do |format|
format.html { redirect_to(@post) }
end
elsif params[:random].to_s.truthy?
query = "#{post_set.normalized_query.to_s} random:#{post_set.per_page}".strip
redirect_to posts_path(tags: query, page: params[:page], limit: params[:limit], format: request.format.symbol)
else
@preview_size = params[:size].presence || cookies[:post_preview_size].presence || PostGalleryComponent::DEFAULT_SIZE
@posts = authorize post_set.posts, policy_class: PostPolicy
respond_with(@posts) do |format|
format.atom
end
end
end
def show
@post = authorize Post.find(params[:id])
if request.format.html?
include_deleted = @post.is_deleted? || (@post.parent_id.present? && @post.parent.is_deleted?) || CurrentUser.user.show_deleted_children?
@sibling_posts = @post.parent.present? ? @post.parent.children : Post.none
@sibling_posts = @sibling_posts.undeleted unless include_deleted
@sibling_posts = @sibling_posts.includes(:media_asset)
@child_posts = @post.children
@child_posts = @child_posts.undeleted unless include_deleted
@sibling_posts = @sibling_posts.includes(:media_asset)
end
respond_with(@post) do |format|
format.html.tooltip { render layout: false }
end
end
def show_seq
authorize Post
context = PostSearchContext.new(params)
if context.post_id
redirect_to(post_path(context.post_id, q: params[:q]))
else
redirect_to(post_path(params[:id], q: params[:q]))
end
end
def update
@post = authorize Post.find(params[:id])
@post.update(permitted_attributes(@post))
@show_votes = (params[:show_votes].presence || cookies[:post_preview_show_votes].presence || "false").truthy?
@preview_size = params[:size].presence || cookies[:post_preview_size].presence || PostGalleryComponent::DEFAULT_SIZE
respond_with_post_after_update(@post)
end
def create
@post = authorize Post.new_from_upload(permitted_attributes(Post))
@post.save
if @post.errors.none?
respond_with(@post)
elsif @post.errors.of_kind?(:md5, :taken)
@original_post = Post.find_by!(md5: @post.md5)
@original_post.update(rating: @post.rating, parent_id: @post.parent_id, tag_string: "#{@original_post.tag_string} #{@post.tag_string}")
flash[:notice] = "Duplicate of post ##{@original_post.id}; merging tags"
redirect_to @original_post
else
@upload = UploadMediaAsset.find(params[:post][:upload_media_asset_id]).upload
flash[:notice] = @post.errors.full_messages.join("; ")
respond_with(@post, render: { template: "uploads/show" })
end
end
def destroy
@post = authorize Post.find(params[:id])
if params[:commit] == "Delete"
move_favorites = params.dig(:post, :move_favorites).to_s.truthy?
@post.delete!(params.dig(:post, :reason), move_favorites: move_favorites, user: CurrentUser.user)
flash[:notice] = "Post deleted"
end
respond_with_post_after_update(@post)
end
def revert
@post = authorize Post.find(params[:id])
@version = @post.versions.find(params[:version_id])
@post.revert_to!(@version)
respond_with(@post) do |format|
format.js
end
end
def copy_notes
@post = Post.find(params[:id])
@other_post = authorize Post.find(params[:other_post_id].to_i)
@post.copy_notes_to(@other_post)
if @post.errors.any?
@error_message = @post.errors.full_messages.join("; ")
render :json => {:success => false, :reason => @error_message}.to_json, :status => 400
else
head 204
end
end
def random
@post = Post.user_tag_match(params[:tags]).random(1).take
raise ActiveRecord::RecordNotFound if @post.nil?
authorize @post
respond_with(@post) do |format|
format.html { redirect_to post_path(@post, :tags => params[:tags]) }
end
end
def mark_as_translated
@post = authorize Post.find(params[:id])
@post.mark_as_translated(params[:post])
respond_with_post_after_update(@post)
end
private
def post_set
@post_set ||= begin
tag_query = params[:tags] || params.dig(:post, :tags)
show_votes = (params[:show_votes].presence || cookies[:post_preview_show_votes].presence || "false").truthy?
PostSets::Post.new(tag_query, params[:page], params[:limit], format: request.format.symbol, show_votes: show_votes)
end
end
def log_search_query
DanbooruLogger.add_attributes("search", {
query: post_set.normalized_query.to_s,
page: post_set.current_page,
limit: post_set.per_page,
term_count: post_set.normalized_query.terms.count,
tag_count: post_set.normalized_query.tags.count,
metatag_count: post_set.normalized_query.metatags.count,
})
end
def log_search_count
DanbooruLogger.add_attributes("search", { count: post_set.post_count, })
end
def respond_with_post_after_update(post)
respond_with(post) do |format|
format.html do
if post.warnings.any?
flash[:notice] = post.warnings.full_messages.join(".\n \n")
end
if post.errors.any?
@error_message = post.errors.full_messages.join("; ")
render :template => "static/error", :status => 500
else
response_params = {:q => params[:tags_query], :pool_id => params[:pool_id], :favgroup_id => params[:favgroup_id]}
response_params.reject! {|_key, value| value.blank?}
redirect_to post_path(post, response_params)
end
end
format.json do
render :json => post.to_json
end
end
end
end