2024-02-25 12:15:55 -05:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2024-12-14 20:37:53 -05:00
|
|
|
require "zxcvbn"
|
|
|
|
|
2017-06-14 22:27:53 -04:00
|
|
|
class User < ApplicationRecord
|
2010-03-09 16:14:29 -05:00
|
|
|
class Error < Exception ; end
|
2019-06-20 22:49:40 -04:00
|
|
|
class PrivilegeError < Exception
|
|
|
|
attr_accessor :message
|
|
|
|
|
|
|
|
def initialize(msg = nil)
|
|
|
|
@message = "Access Denied: #{msg}" if msg
|
|
|
|
end
|
|
|
|
end
|
2013-03-19 08:10:10 -04:00
|
|
|
|
2011-06-25 19:31:39 -04:00
|
|
|
module Levels
|
2019-06-29 15:25:44 -04:00
|
|
|
Danbooru.config.levels.each do |name, level|
|
|
|
|
const_set(name.upcase.tr(' ', '_'), level)
|
|
|
|
end
|
2011-06-25 19:31:39 -04:00
|
|
|
end
|
2013-03-19 08:10:10 -04:00
|
|
|
|
2018-04-02 13:51:26 -04:00
|
|
|
# Used for `before_action :<role>_only`. Must have a corresponding `is_<role>?` method.
|
2016-10-14 00:40:48 -04:00
|
|
|
Roles = Levels.constants.map(&:downcase) + [
|
|
|
|
:approver,
|
|
|
|
]
|
|
|
|
|
2024-02-23 11:06:50 -05:00
|
|
|
BOOLEAN_ATTRIBUTES = %w[
|
|
|
|
_show_avatars
|
|
|
|
_blacklist_avatars
|
2019-06-05 13:29:00 -04:00
|
|
|
blacklist_users
|
|
|
|
description_collapsed_initially
|
|
|
|
hide_comments
|
|
|
|
show_hidden_comments
|
|
|
|
show_post_statistics
|
2015-07-31 19:20:36 -04:00
|
|
|
is_banned
|
2024-02-23 11:18:22 -05:00
|
|
|
_has_mail
|
2015-07-31 19:20:36 -04:00
|
|
|
receive_email_notifications
|
2019-06-01 10:58:10 -04:00
|
|
|
enable_keyboard_navigation
|
2015-07-31 19:20:36 -04:00
|
|
|
enable_privacy_mode
|
|
|
|
style_usernames
|
|
|
|
enable_auto_complete
|
2024-02-23 11:06:50 -05:00
|
|
|
_has_saved_searches
|
2015-07-31 19:20:36 -04:00
|
|
|
can_approve_posts
|
2015-10-15 18:24:24 -04:00
|
|
|
can_upload_free
|
2017-09-08 18:01:12 -04:00
|
|
|
disable_cropped_thumbnails
|
2024-02-23 11:06:50 -05:00
|
|
|
_disable_mobile_gestures
|
2017-11-20 19:33:59 -05:00
|
|
|
enable_safe_mode
|
2017-12-15 16:52:25 -05:00
|
|
|
disable_responsive_mode
|
2024-02-23 11:06:50 -05:00
|
|
|
_disable_post_tooltips
|
2018-12-11 19:41:20 -05:00
|
|
|
no_flagging
|
2024-02-23 11:06:50 -05:00
|
|
|
_no_feedback
|
2019-10-03 10:29:49 -04:00
|
|
|
disable_user_dmails
|
2020-01-03 09:39:55 -05:00
|
|
|
enable_compact_uploader
|
2022-01-30 07:33:14 -05:00
|
|
|
replacements_beta
|
2023-04-25 13:53:07 -04:00
|
|
|
is_bd_staff
|
2024-02-23 11:06:50 -05:00
|
|
|
].freeze
|
2015-07-31 19:20:36 -04:00
|
|
|
|
|
|
|
include Danbooru::HasBitFlags
|
|
|
|
has_bit_flags BOOLEAN_ATTRIBUTES, :field => "bit_prefs"
|
2014-06-03 18:54:22 -04:00
|
|
|
|
2023-04-30 07:10:43 -04:00
|
|
|
attr_accessor :password, :old_password, :validate_email_format, :is_admin_edit
|
2017-02-25 02:47:57 -05:00
|
|
|
|
2018-04-02 13:51:26 -04:00
|
|
|
after_initialize :initialize_attributes, if: :new_record?
|
2019-09-03 23:54:24 -04:00
|
|
|
|
2022-12-01 11:38:29 -05:00
|
|
|
validates :email, presence: { if: :enable_email_verification? }
|
2022-10-16 09:03:16 -04:00
|
|
|
validates :email, uniqueness: { case_sensitive: false, if: :enable_email_verification? }
|
2022-12-01 11:38:29 -05:00
|
|
|
validates :email, format: { with: /\A.+@[^ ,;@]+\.[^ ,;@]+\z/, if: :enable_email_verification? }
|
2024-04-28 05:28:07 -04:00
|
|
|
validates :email, length: { maximum: 100 }
|
2019-07-02 13:25:47 -04:00
|
|
|
validate :validate_email_address_allowed, on: [:create, :update], if: ->(rec) { (rec.new_record? && rec.email.present?) || (rec.email.present? && rec.email_changed?) }
|
2019-09-03 23:54:24 -04:00
|
|
|
|
2024-11-10 23:22:40 -05:00
|
|
|
normalizes :profile_about, :profile_artinfo, with: ->(value) { value.gsub("\r\n", "\n") }
|
2019-09-03 23:54:24 -04:00
|
|
|
validates :name, user_name: true, on: :create
|
2020-09-26 01:28:26 -04:00
|
|
|
validates :default_image_size, inclusion: { :in => %w(large fit fitv original) }
|
2019-09-05 08:59:51 -04:00
|
|
|
validates :per_page, inclusion: { :in => 1..320 }
|
|
|
|
validates :comment_threshold, presence: true
|
|
|
|
validates :comment_threshold, numericality: { only_integer: true, less_than: 50_000, greater_than: -50_000 }
|
2024-12-14 20:37:53 -05:00
|
|
|
validates :password, length: { minimum: 8, if: ->(rec) { rec.new_record? || rec.password.present? || rec.old_password.present? } }
|
|
|
|
validate :password_is_secure, if: ->(rec) { rec.new_record? || rec.password.present? || rec.old_password.present? }
|
2019-09-05 08:59:51 -04:00
|
|
|
validates :password, confirmation: true
|
2020-02-23 23:18:02 -05:00
|
|
|
validates :password_confirmation, presence: { if: ->(rec) { rec.new_record? || rec.old_password.present? } }
|
2010-03-18 17:55:57 -04:00
|
|
|
validate :validate_ip_addr_is_not_banned, :on => :create
|
2018-05-10 14:18:02 -04:00
|
|
|
validate :validate_sock_puppets, :on => :create, :if => -> { Danbooru.config.enable_sock_puppet_validation? }
|
2019-08-13 12:40:53 -04:00
|
|
|
before_validation :normalize_blacklisted_tags, if: ->(rec) { rec.blacklisted_tags_changed? }
|
2019-10-03 10:29:49 -04:00
|
|
|
before_validation :staff_cant_disable_dmail
|
2020-02-20 01:19:15 -05:00
|
|
|
before_validation :blank_out_nonexistent_avatars
|
2019-09-05 08:59:51 -04:00
|
|
|
validates :blacklisted_tags, length: { maximum: 150_000 }
|
2022-12-30 13:32:03 -05:00
|
|
|
validates :custom_style, length: { maximum: 500_000 }
|
2021-10-31 23:30:37 -04:00
|
|
|
validates :profile_about, length: { maximum: Danbooru.config.user_about_max_size }
|
|
|
|
validates :profile_artinfo, length: { maximum: Danbooru.config.user_about_max_size }
|
2022-12-30 13:32:03 -05:00
|
|
|
validates :time_zone, inclusion: { in: ActiveSupport::TimeZone.all.map(&:name) }
|
2011-09-14 17:46:42 -04:00
|
|
|
before_create :encrypt_password_on_create
|
|
|
|
before_update :encrypt_password_on_update
|
2010-02-15 13:59:58 -05:00
|
|
|
after_save :update_cache
|
2016-08-24 18:58:22 -04:00
|
|
|
#after_create :notify_sock_puppets
|
2019-06-02 10:49:38 -04:00
|
|
|
after_create :create_user_status
|
2017-05-07 13:10:26 -04:00
|
|
|
|
2014-07-23 18:15:47 -04:00
|
|
|
has_one :api_key
|
2014-11-20 00:28:26 -05:00
|
|
|
has_one :dmail_filter
|
2023-11-14 15:20:33 -05:00
|
|
|
has_one :user_status
|
|
|
|
has_one :recent_ban, -> { order("bans.id desc") }, class_name: "Ban"
|
|
|
|
has_many :bans, -> { order("bans.id desc") }
|
|
|
|
has_many :dmails, -> { order("dmails.id desc") }, foreign_key: "owner_id"
|
|
|
|
has_many :favorites, -> { order(id: :desc) }
|
2024-09-07 17:28:07 -04:00
|
|
|
has_many :feedback, class_name: "UserFeedback", dependent: :destroy
|
2023-11-14 15:20:33 -05:00
|
|
|
has_many :forum_posts, -> { order("forum_posts.created_at, forum_posts.id") }, foreign_key: "creator_id"
|
2019-11-04 17:06:19 -05:00
|
|
|
has_many :forum_topic_visits
|
2023-11-14 15:20:33 -05:00
|
|
|
has_many :note_versions, foreign_key: "updater_id"
|
|
|
|
has_many :posts, foreign_key: "uploader_id"
|
|
|
|
has_many :post_approvals, dependent: :destroy
|
|
|
|
has_many :post_disapprovals, dependent: :destroy
|
|
|
|
has_many :post_replacements, foreign_key: :creator_id
|
|
|
|
has_many :post_sets, -> { order(name: :asc) }, foreign_key: :creator_id
|
|
|
|
has_many :post_versions
|
|
|
|
has_many :post_votes
|
2025-01-12 22:28:32 -05:00
|
|
|
has_many :staff_notes, -> { active.order("staff_notes.id desc") }
|
2022-07-10 14:48:50 -04:00
|
|
|
has_many :user_name_change_requests, -> { order(id: :asc) }
|
2023-11-14 15:20:33 -05:00
|
|
|
|
2019-04-30 09:50:43 -04:00
|
|
|
belongs_to :avatar, class_name: 'Post', optional: true
|
2014-11-20 00:28:26 -05:00
|
|
|
accepts_nested_attributes_for :dmail_filter
|
2013-03-19 08:10:10 -04:00
|
|
|
|
2010-03-18 17:55:57 -04:00
|
|
|
module BanMethods
|
|
|
|
def validate_ip_addr_is_not_banned
|
2011-02-02 15:53:28 -05:00
|
|
|
if IpBan.is_banned?(CurrentUser.ip_addr)
|
2021-02-07 21:56:22 -05:00
|
|
|
self.errors.add(:base, "IP address is banned")
|
2010-03-18 17:55:57 -04:00
|
|
|
return false
|
|
|
|
end
|
|
|
|
end
|
2013-03-19 08:10:10 -04:00
|
|
|
|
2010-08-18 18:42:33 -04:00
|
|
|
def unban!
|
2014-06-23 21:00:31 -04:00
|
|
|
self.is_banned = false
|
2020-03-06 15:04:08 -05:00
|
|
|
self.level = 20
|
2014-06-23 21:00:31 -04:00
|
|
|
save
|
2017-05-07 13:10:26 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def ban_expired?
|
|
|
|
is_banned? && recent_ban.try(:expired?)
|
2010-08-18 18:42:33 -04:00
|
|
|
end
|
2010-03-18 17:55:57 -04:00
|
|
|
end
|
2013-03-19 08:10:10 -04:00
|
|
|
|
2010-02-10 16:12:30 -05:00
|
|
|
module NameMethods
|
2010-11-19 16:24:17 -05:00
|
|
|
extend ActiveSupport::Concern
|
2013-03-19 08:10:10 -04:00
|
|
|
|
2010-02-10 16:12:30 -05:00
|
|
|
module ClassMethods
|
2010-08-18 18:42:33 -04:00
|
|
|
def name_to_id(name)
|
2023-02-18 10:55:45 -05:00
|
|
|
normalized_name = normalize_name(name)
|
2023-05-19 16:53:20 -04:00
|
|
|
Cache.fetch("uni:#{normalized_name}", expires_in: 4.hours) do
|
2023-06-04 16:06:11 -04:00
|
|
|
User.where("lower(name) = ?", normalized_name).pick(:id)
|
2010-08-18 18:42:33 -04:00
|
|
|
end
|
|
|
|
end
|
2013-03-19 08:10:10 -04:00
|
|
|
|
2019-09-25 23:40:52 -04:00
|
|
|
def name_or_id_to_id(name)
|
2020-03-26 05:55:41 -04:00
|
|
|
if name =~ /\A!\d+\z/
|
|
|
|
return name[1..-1].to_i
|
|
|
|
end
|
|
|
|
User.name_to_id(name)
|
|
|
|
end
|
|
|
|
|
|
|
|
def name_or_id_to_id_forced(name)
|
2019-11-08 15:33:04 -05:00
|
|
|
if name =~ /\A\d+\z/
|
2019-09-25 23:40:52 -04:00
|
|
|
return name.to_i
|
|
|
|
end
|
|
|
|
User.name_to_id(name)
|
|
|
|
end
|
|
|
|
|
2010-08-18 18:42:33 -04:00
|
|
|
def id_to_name(user_id)
|
2019-10-12 19:37:16 -04:00
|
|
|
RequestStore[:id_name_cache] ||= {}
|
|
|
|
if RequestStore[:id_name_cache].key?(user_id)
|
2019-10-24 08:42:08 -04:00
|
|
|
return RequestStore[:id_name_cache][user_id]
|
2019-10-12 19:37:16 -04:00
|
|
|
end
|
2023-05-19 16:53:20 -04:00
|
|
|
name = Cache.fetch("uin:#{user_id}", expires_in: 4.hours) do
|
2023-06-04 16:06:11 -04:00
|
|
|
User.where(id: user_id).pick(:name) || Danbooru.config.default_guest_name
|
2010-02-10 16:12:30 -05:00
|
|
|
end
|
2019-10-12 19:37:16 -04:00
|
|
|
RequestStore[:id_name_cache][user_id] = name
|
|
|
|
name
|
2010-02-10 16:12:30 -05:00
|
|
|
end
|
2013-03-19 08:10:10 -04:00
|
|
|
|
2010-03-11 19:42:04 -05:00
|
|
|
def find_by_name(name)
|
2023-02-18 10:55:45 -05:00
|
|
|
where("lower(name) = ?", normalize_name(name)).first
|
2010-03-11 19:42:04 -05:00
|
|
|
end
|
2013-03-19 08:10:10 -04:00
|
|
|
|
2020-06-28 16:14:15 -04:00
|
|
|
def find_by_name_or_id(name)
|
|
|
|
if name =~ /\A!\d+\z/
|
|
|
|
where('id = ?', name[1..-1].to_i).first
|
|
|
|
else
|
|
|
|
find_by_name(name)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-02-25 02:47:57 -05:00
|
|
|
def normalize_name(name)
|
2022-04-09 08:23:12 -04:00
|
|
|
name.to_s.downcase.strip.tr(" ", "_").to_s
|
2017-02-25 02:47:57 -05:00
|
|
|
end
|
2010-02-10 16:12:30 -05:00
|
|
|
end
|
2013-03-19 08:10:10 -04:00
|
|
|
|
2010-02-10 16:12:30 -05:00
|
|
|
def pretty_name
|
2013-04-19 19:16:26 -04:00
|
|
|
name.gsub(/([^_])_+(?=[^_])/, "\\1 \\2")
|
2010-02-10 16:12:30 -05:00
|
|
|
end
|
2013-03-19 08:10:10 -04:00
|
|
|
|
2010-02-15 13:59:58 -05:00
|
|
|
def update_cache
|
2023-05-19 16:53:20 -04:00
|
|
|
Cache.write("uin:#{id}", name, expires_in: 4.hours)
|
2023-07-31 17:14:39 -04:00
|
|
|
Cache.write("uni:#{User.normalize_name(name)}", id, expires_in: 4.hours)
|
2010-02-15 13:59:58 -05:00
|
|
|
end
|
2010-02-06 16:48:40 -05:00
|
|
|
end
|
2013-03-19 08:10:10 -04:00
|
|
|
|
2010-02-10 16:12:30 -05:00
|
|
|
module PasswordMethods
|
2021-12-18 06:36:19 -05:00
|
|
|
def password_token
|
|
|
|
Zlib::crc32(bcrypt_password_hash)
|
|
|
|
end
|
|
|
|
|
2013-03-04 22:55:41 -05:00
|
|
|
def bcrypt_password
|
|
|
|
BCrypt::Password.new(bcrypt_password_hash)
|
|
|
|
end
|
2013-03-19 08:10:10 -04:00
|
|
|
|
2011-09-14 17:46:42 -04:00
|
|
|
def encrypt_password_on_create
|
2013-03-04 22:55:41 -05:00
|
|
|
self.password_hash = ""
|
|
|
|
self.bcrypt_password_hash = User.bcrypt(password)
|
2011-09-14 17:46:42 -04:00
|
|
|
end
|
2013-03-19 08:10:10 -04:00
|
|
|
|
2011-09-14 17:46:42 -04:00
|
|
|
def encrypt_password_on_update
|
|
|
|
return if password.blank?
|
|
|
|
return if old_password.blank?
|
2013-03-19 08:10:10 -04:00
|
|
|
|
2019-02-22 00:06:18 -05:00
|
|
|
if bcrypt_password == old_password
|
2013-03-04 22:55:41 -05:00
|
|
|
self.bcrypt_password_hash = User.bcrypt(password)
|
2011-09-14 17:46:42 -04:00
|
|
|
return true
|
|
|
|
else
|
2021-02-07 21:56:22 -05:00
|
|
|
errors.add(:old_password, "is incorrect")
|
2011-09-14 17:46:42 -04:00
|
|
|
return false
|
|
|
|
end
|
2010-02-06 16:59:38 -05:00
|
|
|
end
|
|
|
|
|
2019-02-22 00:06:18 -05:00
|
|
|
def upgrade_password(pass)
|
|
|
|
self.update_columns(password_hash: '', bcrypt_password_hash: User.bcrypt(pass))
|
|
|
|
end
|
2024-12-14 20:37:53 -05:00
|
|
|
|
|
|
|
def password_is_secure
|
|
|
|
analysis = Zxcvbn.test(password, [name, email])
|
|
|
|
return unless analysis.score < 2
|
|
|
|
if analysis.feedback.warning
|
|
|
|
errors.add(:password, "is insecure: #{analysis.feedback.warning}")
|
|
|
|
else
|
|
|
|
errors.add(:password, "is insecure")
|
|
|
|
end
|
|
|
|
end
|
2010-02-06 16:59:38 -05:00
|
|
|
end
|
2013-03-19 08:10:10 -04:00
|
|
|
|
2010-02-10 16:12:30 -05:00
|
|
|
module AuthenticationMethods
|
2011-10-15 16:36:07 -04:00
|
|
|
extend ActiveSupport::Concern
|
2013-03-19 08:10:10 -04:00
|
|
|
|
2011-10-15 16:36:07 -04:00
|
|
|
module ClassMethods
|
|
|
|
def authenticate(name, pass)
|
2019-02-22 00:06:18 -05:00
|
|
|
user = find_by_name(name)
|
2019-09-13 13:18:20 -04:00
|
|
|
if user && user.password_hash.present? && Pbkdf2.validate_password(pass, user.password_hash)
|
2019-02-22 00:06:18 -05:00
|
|
|
user.upgrade_password(pass)
|
|
|
|
user
|
|
|
|
elsif user && user.bcrypt_password_hash && user.bcrypt_password == pass
|
|
|
|
user
|
|
|
|
else
|
|
|
|
nil
|
|
|
|
end
|
2011-10-15 16:36:07 -04:00
|
|
|
end
|
2010-02-06 16:59:38 -05:00
|
|
|
|
2014-07-23 18:15:47 -04:00
|
|
|
def authenticate_api_key(name, api_key)
|
|
|
|
key = ApiKey.where(:key => api_key).first
|
|
|
|
return nil if key.nil?
|
|
|
|
user = find_by_name(name)
|
|
|
|
return nil if user.nil?
|
|
|
|
return user if key.user_id == user.id
|
|
|
|
nil
|
|
|
|
end
|
|
|
|
|
2013-03-04 22:55:41 -05:00
|
|
|
def bcrypt(pass)
|
2019-02-22 00:06:18 -05:00
|
|
|
BCrypt::Password.create(pass)
|
2011-10-15 16:36:07 -04:00
|
|
|
end
|
2010-02-10 16:12:30 -05:00
|
|
|
end
|
2010-02-06 16:59:38 -05:00
|
|
|
end
|
2013-03-19 08:10:10 -04:00
|
|
|
|
2010-02-19 17:30:11 -05:00
|
|
|
module LevelMethods
|
2011-10-21 17:29:41 -04:00
|
|
|
extend ActiveSupport::Concern
|
2013-03-19 08:10:10 -04:00
|
|
|
|
2011-10-21 17:29:41 -04:00
|
|
|
module ClassMethods
|
2017-02-28 20:10:36 -05:00
|
|
|
def system
|
2017-12-15 15:51:24 -05:00
|
|
|
User.find_by!(name: Danbooru.config.system_user)
|
2017-02-28 20:10:36 -05:00
|
|
|
end
|
|
|
|
|
2018-09-09 19:56:03 -04:00
|
|
|
def anonymous
|
|
|
|
user = User.new(name: "Anonymous", created_at: Time.now)
|
2019-09-13 16:34:20 -04:00
|
|
|
user.level = Levels::ANONYMOUS
|
2018-09-09 19:56:03 -04:00
|
|
|
user.freeze.readonly!
|
|
|
|
user
|
|
|
|
end
|
|
|
|
|
2011-10-21 17:29:41 -04:00
|
|
|
def level_hash
|
2019-06-29 15:25:44 -04:00
|
|
|
Danbooru.config.levels
|
2011-10-21 17:29:41 -04:00
|
|
|
end
|
2016-10-24 19:56:18 -04:00
|
|
|
|
|
|
|
def level_string(value)
|
2019-06-29 15:25:44 -04:00
|
|
|
Danbooru.config.levels.invert[value] || ""
|
2016-10-24 19:56:18 -04:00
|
|
|
end
|
2011-10-21 17:29:41 -04:00
|
|
|
end
|
2013-03-19 08:10:10 -04:00
|
|
|
|
2014-10-28 14:05:21 -04:00
|
|
|
def promote_to!(new_level, options = {})
|
|
|
|
UserPromotion.new(self, CurrentUser.user, new_level, options).promote!
|
2013-05-24 15:59:13 -04:00
|
|
|
end
|
|
|
|
|
2014-03-05 20:44:07 -05:00
|
|
|
def level_string_was
|
|
|
|
level_string(level_was)
|
|
|
|
end
|
|
|
|
|
2013-02-28 13:19:31 -05:00
|
|
|
def level_string(value = nil)
|
2016-10-24 19:56:18 -04:00
|
|
|
User.level_string(value || level)
|
2010-02-19 17:30:11 -05:00
|
|
|
end
|
2011-06-25 19:31:39 -04:00
|
|
|
|
2010-03-10 18:21:43 -05:00
|
|
|
def is_anonymous?
|
2018-09-09 19:56:03 -04:00
|
|
|
level == Levels::ANONYMOUS
|
2010-03-10 18:21:43 -05:00
|
|
|
end
|
2010-03-12 19:27:54 -05:00
|
|
|
|
2016-10-14 00:40:48 -04:00
|
|
|
def is_blocked?
|
2019-06-30 13:37:32 -04:00
|
|
|
is_banned? || level == Levels::BLOCKED
|
2016-10-14 00:40:48 -04:00
|
|
|
end
|
|
|
|
|
2019-06-29 15:25:44 -04:00
|
|
|
# Defines various convenience methods for finding out the user's level
|
|
|
|
Danbooru.config.levels.each do |name, value|
|
2019-06-30 13:37:32 -04:00
|
|
|
# TODO: HACK: Remove this and make the below logic better to work with the new setup.
|
|
|
|
next if [0, 10].include?(value)
|
2019-06-29 15:25:44 -04:00
|
|
|
normalized_name = name.downcase.tr(' ', '_')
|
2013-03-19 08:10:10 -04:00
|
|
|
|
2019-06-29 15:25:44 -04:00
|
|
|
# Changed from e6 to match new Danbooru semantics.
|
|
|
|
define_method("is_#{normalized_name}?") do
|
2019-09-23 19:16:28 -04:00
|
|
|
is_verified? && self.level >= value && self.id.present?
|
2019-06-29 15:25:44 -04:00
|
|
|
end
|
2011-06-25 19:31:39 -04:00
|
|
|
end
|
2013-03-19 08:10:10 -04:00
|
|
|
|
2023-04-25 13:53:07 -04:00
|
|
|
def is_bd_staff?
|
|
|
|
is_bd_staff
|
|
|
|
end
|
|
|
|
|
2024-08-03 17:15:26 -04:00
|
|
|
def is_staff?
|
|
|
|
is_janitor?
|
|
|
|
end
|
|
|
|
|
2016-10-14 01:04:40 -04:00
|
|
|
def is_approver?
|
|
|
|
can_approve_posts?
|
|
|
|
end
|
|
|
|
|
2020-02-20 01:19:15 -05:00
|
|
|
def blank_out_nonexistent_avatars
|
|
|
|
if avatar_id.present? && avatar.nil?
|
|
|
|
self.avatar_id = nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-10-03 10:29:49 -04:00
|
|
|
def staff_cant_disable_dmail
|
|
|
|
self.disable_user_dmails = false if self.is_janitor?
|
|
|
|
end
|
|
|
|
|
2022-12-20 09:50:18 -05:00
|
|
|
def level_css_class
|
|
|
|
"user-#{level_string.parameterize}"
|
2013-03-29 23:57:49 -04:00
|
|
|
end
|
2019-06-02 10:49:38 -04:00
|
|
|
|
|
|
|
def create_user_status
|
|
|
|
UserStatus.create!(user_id: id)
|
|
|
|
end
|
2010-02-19 17:30:11 -05:00
|
|
|
end
|
2013-03-19 08:10:10 -04:00
|
|
|
|
2011-09-11 17:48:30 -04:00
|
|
|
module EmailMethods
|
2010-03-09 16:14:29 -05:00
|
|
|
def is_verified?
|
2019-09-23 19:16:28 -04:00
|
|
|
id.present? && email_verification_key.nil?
|
|
|
|
end
|
|
|
|
|
|
|
|
def mark_unverified!
|
|
|
|
update_attribute(:email_verification_key, '1')
|
|
|
|
end
|
|
|
|
|
|
|
|
def mark_verified!
|
|
|
|
update_attribute(:email_verification_key, nil)
|
2010-03-09 16:14:29 -05:00
|
|
|
end
|
2017-01-16 17:56:08 -05:00
|
|
|
|
2022-10-16 09:03:16 -04:00
|
|
|
def enable_email_verification?
|
2023-04-30 07:10:43 -04:00
|
|
|
# Allow admins to edit users with blank/duplicate emails
|
|
|
|
return false if is_admin_edit && !email_changed?
|
2022-10-16 09:03:16 -04:00
|
|
|
Danbooru.config.enable_email_verification? && validate_email_format
|
2017-01-16 17:56:08 -05:00
|
|
|
end
|
2019-02-09 17:00:37 -05:00
|
|
|
|
|
|
|
def validate_email_address_allowed
|
|
|
|
if EmailBlacklist.is_banned?(self.email)
|
2021-02-07 21:56:22 -05:00
|
|
|
self.errors.add(:base, "Email address may not be used")
|
2019-02-09 17:00:37 -05:00
|
|
|
return false
|
|
|
|
end
|
|
|
|
end
|
2010-03-09 16:14:29 -05:00
|
|
|
end
|
2013-03-19 08:10:10 -04:00
|
|
|
|
2010-03-10 18:21:43 -05:00
|
|
|
module BlacklistMethods
|
2011-09-11 17:54:44 -04:00
|
|
|
def normalize_blacklisted_tags
|
2024-07-27 12:19:49 -04:00
|
|
|
self.blacklisted_tags = TagAlias.to_aliased_query(blacklisted_tags, comments: true) if blacklisted_tags.present?
|
2011-09-11 17:54:44 -04:00
|
|
|
end
|
2019-10-03 10:29:49 -04:00
|
|
|
|
|
|
|
def is_blacklisting_user?(user)
|
2020-03-12 22:24:39 -04:00
|
|
|
return false if blacklisted_tags.blank?
|
2024-04-09 21:44:56 -04:00
|
|
|
bltags = blacklisted_tags.split("\n").map(&:downcase)
|
|
|
|
strings = %W[user:#{user.name.downcase} user:!#{user.id} userid:#{user.id}]
|
|
|
|
strings.any? { |str| bltags.include?(str) }
|
2019-10-03 10:29:49 -04:00
|
|
|
end
|
2010-03-10 18:21:43 -05:00
|
|
|
end
|
2013-03-19 08:10:10 -04:00
|
|
|
|
2010-03-11 19:42:04 -05:00
|
|
|
module ForumMethods
|
|
|
|
def has_forum_been_updated?
|
2019-11-15 05:15:55 -05:00
|
|
|
return false unless is_member?
|
2024-07-14 16:14:09 -04:00
|
|
|
max_updated_at = ForumTopic.visible(self).order(updated_at: :desc).first&.updated_at
|
2014-07-17 18:53:36 -04:00
|
|
|
return false if max_updated_at.nil?
|
2011-01-13 18:16:39 -05:00
|
|
|
return true if last_forum_read_at.nil?
|
2014-07-17 18:53:36 -04:00
|
|
|
return max_updated_at > last_forum_read_at
|
2010-03-11 19:42:04 -05:00
|
|
|
end
|
2019-11-04 17:06:19 -05:00
|
|
|
|
|
|
|
def has_viewed_thread?(id, last_updated)
|
|
|
|
@topic_views ||= forum_topic_visits.pluck(:forum_topic_id, :last_read_at).to_h
|
|
|
|
@topic_views.key?(id) && @topic_views[id] >= last_updated
|
|
|
|
end
|
2010-03-11 19:42:04 -05:00
|
|
|
end
|
2013-03-19 08:10:10 -04:00
|
|
|
|
2019-06-27 11:33:02 -04:00
|
|
|
module ThrottleMethods
|
2023-09-17 11:57:17 -04:00
|
|
|
def throttle_reason(reason, timeframe = "hourly")
|
2019-06-27 11:33:02 -04:00
|
|
|
reasons = {
|
2023-09-17 11:57:17 -04:00
|
|
|
REJ_NEWBIE: "can not yet perform this action. Account is too new",
|
|
|
|
REJ_LIMITED: "have reached the #{timeframe} limit for this action",
|
2019-06-27 11:33:02 -04:00
|
|
|
}
|
2023-09-17 11:57:17 -04:00
|
|
|
reasons.fetch(reason, "unknown throttle reason, please report this as a bug")
|
2019-06-27 11:33:02 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def upload_reason_string(reason)
|
|
|
|
reasons = {
|
|
|
|
REJ_UPLOAD_HOURLY: "have reached your hourly upload limit",
|
|
|
|
REJ_UPLOAD_EDIT: "have no remaining tag edits available",
|
|
|
|
REJ_UPLOAD_LIMIT: "have reached your upload limit",
|
|
|
|
REJ_UPLOAD_NEWBIE: "cannot upload during your first week"
|
|
|
|
}
|
|
|
|
reasons.fetch(reason, "unknown upload rejection reason")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2010-03-12 15:18:30 -05:00
|
|
|
module LimitMethods
|
2019-09-15 13:19:16 -04:00
|
|
|
def younger_than(duration)
|
2021-11-14 16:16:36 -05:00
|
|
|
return false if Danbooru.config.disable_age_checks?
|
2019-09-15 13:19:16 -04:00
|
|
|
created_at > duration.ago
|
|
|
|
end
|
|
|
|
|
|
|
|
def older_than(duration)
|
2021-11-14 16:16:36 -05:00
|
|
|
return true if Danbooru.config.disable_age_checks?
|
2019-09-15 13:19:16 -04:00
|
|
|
created_at < duration.ago
|
|
|
|
end
|
|
|
|
|
2019-06-27 11:33:02 -04:00
|
|
|
def self.create_user_throttle(name, limiter, checker, newbie_duration)
|
2023-12-03 09:36:37 -05:00
|
|
|
define_method(:"#{name}_limit", limiter)
|
2021-05-15 06:36:29 -04:00
|
|
|
|
2023-12-03 09:36:37 -05:00
|
|
|
define_method(:"can_#{name}_with_reason") do
|
2021-11-14 16:16:36 -05:00
|
|
|
return true if Danbooru.config.disable_throttles?
|
2019-06-27 11:33:02 -04:00
|
|
|
return send(checker) if checker && send(checker)
|
2019-09-15 13:19:16 -04:00
|
|
|
return :REJ_NEWBIE if newbie_duration && younger_than(newbie_duration)
|
2019-06-27 11:33:02 -04:00
|
|
|
return :REJ_LIMITED if send("#{name}_limit") <= 0
|
|
|
|
true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-02-01 15:38:26 -05:00
|
|
|
def token_bucket
|
|
|
|
@token_bucket ||= UserThrottle.new({prefix: "thtl:", duration: 1.minute}, self)
|
|
|
|
end
|
|
|
|
|
2019-07-19 06:48:16 -04:00
|
|
|
def general_bypass_throttle?
|
2019-09-04 00:56:41 -04:00
|
|
|
is_privileged?
|
2019-06-27 11:33:02 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
create_user_throttle(:artist_edit, ->{ Danbooru.config.artist_edit_limit - ArtistVersion.for_user(id).where('updated_at > ?', 1.hour.ago).count },
|
2019-09-15 13:19:16 -04:00
|
|
|
:general_bypass_throttle?, 7.days)
|
2022-08-06 12:58:24 -04:00
|
|
|
create_user_throttle(:post_edit, ->{ Danbooru.config.post_edit_limit - PostVersion.for_user(id).where('updated_at > ?', 1.hour.ago).count },
|
2019-09-15 13:19:16 -04:00
|
|
|
:general_bypass_throttle?, 7.days)
|
2019-06-27 11:33:02 -04:00
|
|
|
create_user_throttle(:wiki_edit, ->{ Danbooru.config.wiki_edit_limit - WikiPageVersion.for_user(id).where('updated_at > ?', 1.hour.ago).count },
|
2019-09-15 13:19:16 -04:00
|
|
|
:general_bypass_throttle?, 7.days)
|
2019-06-27 11:33:02 -04:00
|
|
|
create_user_throttle(:pool, ->{ Danbooru.config.pool_limit - Pool.for_user(id).where('created_at > ?', 1.hour.ago).count },
|
2020-09-15 03:33:40 -04:00
|
|
|
:is_janitor?, 7.days)
|
2022-08-06 12:58:24 -04:00
|
|
|
create_user_throttle(:pool_edit, ->{ Danbooru.config.pool_edit_limit - PoolVersion.for_user(id).where('updated_at > ?', 1.hour.ago).count },
|
2020-09-15 03:33:40 -04:00
|
|
|
:is_janitor?, 3.days)
|
2022-08-06 12:58:24 -04:00
|
|
|
create_user_throttle(:pool_post_edit, -> { Danbooru.config.pool_post_edit_limit - PoolVersion.for_user(id).where('updated_at > ?', 1.hour.ago).group(:pool_id).count(:pool_id).length },
|
2020-04-12 21:03:36 -04:00
|
|
|
:general_bypass_throttle?, 7.days)
|
2019-06-27 11:33:02 -04:00
|
|
|
create_user_throttle(:note_edit, ->{ Danbooru.config.note_edit_limit - NoteVersion.for_user(id).where('updated_at > ?', 1.hour.ago).count },
|
2019-09-15 13:19:16 -04:00
|
|
|
:general_bypass_throttle?, 3.days)
|
2019-06-27 11:33:02 -04:00
|
|
|
create_user_throttle(:comment, ->{ Danbooru.config.member_comment_limit - Comment.for_creator(id).where('created_at > ?', 1.hour.ago).count },
|
2019-09-15 13:19:16 -04:00
|
|
|
:general_bypass_throttle?, 7.days)
|
2020-05-11 00:09:45 -04:00
|
|
|
create_user_throttle(:forum_post, ->{ Danbooru.config.member_comment_limit - ForumPost.for_user(id).where('created_at > ?', 1.hour.ago).count },
|
|
|
|
nil, 3.days)
|
2019-06-27 11:33:02 -04:00
|
|
|
create_user_throttle(:blip, ->{ Danbooru.config.blip_limit - Blip.for_creator(id).where('created_at > ?', 1.hour.ago).count },
|
2019-09-15 13:19:16 -04:00
|
|
|
:general_bypass_throttle?, 3.days)
|
2023-09-17 11:57:17 -04:00
|
|
|
create_user_throttle(:dmail_minute, ->{ Danbooru.config.dmail_minute_limit - Dmail.sent_by_id(id).where('created_at > ?', 1.minute.ago).count },
|
|
|
|
nil, 7.days)
|
2019-06-30 13:37:32 -04:00
|
|
|
create_user_throttle(:dmail, ->{ Danbooru.config.dmail_limit - Dmail.sent_by_id(id).where('created_at > ?', 1.hour.ago).count },
|
2019-09-15 13:19:16 -04:00
|
|
|
nil, 7.days)
|
2023-09-17 11:57:17 -04:00
|
|
|
create_user_throttle(:dmail_day, ->{ Danbooru.config.dmail_day_limit - Dmail.sent_by_id(id).where('created_at > ?', 1.day.ago).count },
|
2019-09-15 13:19:16 -04:00
|
|
|
nil, 7.days)
|
2019-09-15 19:06:18 -04:00
|
|
|
create_user_throttle(:comment_vote, ->{ Danbooru.config.comment_vote_limit - CommentVote.for_user(id).where("created_at > ?", 1.hour.ago).count },
|
2019-09-15 13:19:16 -04:00
|
|
|
:general_bypass_throttle?, 3.days)
|
2019-09-15 19:06:18 -04:00
|
|
|
create_user_throttle(:post_vote, ->{ Danbooru.config.post_vote_limit - PostVote.for_user(id).where("created_at > ?", 1.hour.ago).count },
|
2019-09-04 00:56:41 -04:00
|
|
|
:general_bypass_throttle?, nil)
|
2019-09-16 15:16:49 -04:00
|
|
|
create_user_throttle(:post_flag, ->{ Danbooru.config.post_flag_limit - PostFlag.for_creator(id).where("created_at > ?", 1.hour.ago).count },
|
2019-09-15 13:19:16 -04:00
|
|
|
:can_approve_posts?, 3.days)
|
2019-09-15 19:06:18 -04:00
|
|
|
create_user_throttle(:ticket, ->{ Danbooru.config.ticket_limit - Ticket.for_creator(id).where("created_at > ?", 1.hour.ago).count },
|
2019-09-15 13:19:16 -04:00
|
|
|
:general_bypass_throttle?, 3.days)
|
2020-07-10 07:20:24 -04:00
|
|
|
create_user_throttle(:suggest_tag, -> { Danbooru.config.tag_suggestion_limit - (TagAlias.for_creator(id).where("created_at > ?", 1.hour.ago).count + TagImplication.for_creator(id).where("created_at > ?", 1.hour.ago).count + BulkUpdateRequest.for_creator(id).where("created_at > ?", 1.hour.ago).count) },
|
|
|
|
:is_janitor?, 7.days)
|
2021-03-11 14:28:43 -05:00
|
|
|
create_user_throttle(:forum_vote, -> { Danbooru.config.forum_vote_limit - ForumPostVote.by(id).where("created_at > ?", 1.hour.ago).count },
|
2021-03-04 23:33:56 -05:00
|
|
|
:is_janitor?, 3.days)
|
2019-06-27 11:33:02 -04:00
|
|
|
|
2010-03-12 15:18:30 -05:00
|
|
|
def can_remove_from_pools?
|
2020-07-22 20:23:31 -04:00
|
|
|
is_member? && older_than(7.days)
|
2010-03-12 15:18:30 -05:00
|
|
|
end
|
2013-03-19 08:10:10 -04:00
|
|
|
|
2020-02-02 16:50:20 -05:00
|
|
|
def can_discord?
|
2020-07-22 20:23:31 -04:00
|
|
|
is_member? && older_than(7.days)
|
2020-02-02 16:50:20 -05:00
|
|
|
end
|
|
|
|
|
2017-06-14 11:43:25 -04:00
|
|
|
def can_view_flagger?(flagger_id)
|
2020-03-06 15:57:51 -05:00
|
|
|
is_janitor? || flagger_id == id
|
2017-06-14 11:43:25 -04:00
|
|
|
end
|
|
|
|
|
2017-11-07 23:02:03 -05:00
|
|
|
def can_view_flagger_on_post?(flag)
|
2020-03-06 18:23:04 -05:00
|
|
|
is_janitor? || flag.creator_id == id || flag.is_deletion
|
2017-11-07 23:02:03 -05:00
|
|
|
end
|
|
|
|
|
2022-01-30 07:33:14 -05:00
|
|
|
def can_replace?
|
|
|
|
is_janitor? || replacements_beta?
|
|
|
|
end
|
|
|
|
|
2022-02-19 14:21:09 -05:00
|
|
|
def can_view_staff_notes?
|
2024-08-03 17:15:26 -04:00
|
|
|
is_staff?
|
2022-02-19 14:21:09 -05:00
|
|
|
end
|
|
|
|
|
2023-04-25 13:53:07 -04:00
|
|
|
def can_handle_takedowns?
|
|
|
|
is_bd_staff?
|
|
|
|
end
|
|
|
|
|
2024-08-03 17:15:26 -04:00
|
|
|
def can_edit_avoid_posting_entries?
|
|
|
|
is_bd_staff?
|
|
|
|
end
|
|
|
|
|
2023-08-01 12:20:39 -04:00
|
|
|
def can_undo_post_versions?
|
|
|
|
is_member?
|
|
|
|
end
|
|
|
|
|
|
|
|
def can_revert_post_versions?
|
|
|
|
is_member?
|
|
|
|
end
|
|
|
|
|
2019-06-22 04:48:08 -04:00
|
|
|
def can_upload_with_reason
|
2021-11-14 16:16:36 -05:00
|
|
|
if hourly_upload_limit <= 0 && !Danbooru.config.disable_throttles?
|
2019-06-22 04:48:08 -04:00
|
|
|
:REJ_UPLOAD_HOURLY
|
2020-01-18 15:48:22 -05:00
|
|
|
elsif can_upload_free? || is_admin?
|
|
|
|
true
|
2021-04-05 11:23:12 -04:00
|
|
|
elsif younger_than(7.days)
|
2019-06-22 04:48:08 -04:00
|
|
|
:REJ_UPLOAD_NEWBIE
|
2021-11-14 16:16:36 -05:00
|
|
|
elsif !is_privileged? && post_edit_limit <= 0 && !Danbooru.config.disable_throttles?
|
2019-06-22 04:48:08 -04:00
|
|
|
:REJ_UPLOAD_EDIT
|
2021-11-14 16:16:36 -05:00
|
|
|
elsif upload_limit <= 0 && !Danbooru.config.disable_throttles?
|
2019-06-22 04:48:08 -04:00
|
|
|
:REJ_UPLOAD_LIMIT
|
|
|
|
else
|
|
|
|
true
|
|
|
|
end
|
2017-08-10 22:20:10 -04:00
|
|
|
end
|
|
|
|
|
2019-06-22 04:48:08 -04:00
|
|
|
def hourly_upload_limit
|
2023-11-08 16:21:11 -05:00
|
|
|
@hourly_upload_limit ||= begin
|
|
|
|
post_count = posts.where("created_at >= ?", 1.hour.ago).count
|
|
|
|
replacement_count = can_approve_posts? ? 0 : post_replacements.where("created_at >= ? and status != ?", 1.hour.ago, "original").count
|
|
|
|
Danbooru.config.hourly_upload_limit - post_count - replacement_count
|
|
|
|
end
|
2017-08-10 22:20:10 -04:00
|
|
|
end
|
|
|
|
|
2019-06-22 04:48:08 -04:00
|
|
|
def upload_limit
|
2022-01-30 08:03:51 -05:00
|
|
|
pieces = upload_limit_pieces
|
|
|
|
base_upload_limit + (pieces[:approved] / 10) - (pieces[:deleted] / 4) - pieces[:pending]
|
2019-06-22 04:48:08 -04:00
|
|
|
end
|
|
|
|
|
2025-02-26 08:34:43 -05:00
|
|
|
def upload_limit_max
|
|
|
|
pieces = upload_limit_pieces
|
|
|
|
base_upload_limit + (pieces[:approved] / 10) - (pieces[:deleted] / 4)
|
|
|
|
end
|
|
|
|
|
2019-06-22 04:48:08 -04:00
|
|
|
def upload_limit_pieces
|
2023-11-08 16:21:11 -05:00
|
|
|
@upload_limit_pieces ||= begin
|
|
|
|
deleted_count = Post.deleted.for_user(id).count
|
|
|
|
rejected_replacement_count = post_replacement_rejected_count
|
|
|
|
replaced_penalize_count = own_post_replaced_penalize_count
|
|
|
|
unapproved_count = Post.pending_or_flagged.for_user(id).count
|
|
|
|
unapproved_replacements_count = post_replacements.pending.count
|
|
|
|
approved_count = Post.for_user(id).where(is_flagged: false, is_deleted: false, is_pending: false).count
|
|
|
|
|
|
|
|
{
|
|
|
|
deleted: deleted_count + replaced_penalize_count + rejected_replacement_count,
|
|
|
|
deleted_ignore: own_post_replaced_count - replaced_penalize_count,
|
|
|
|
approved: approved_count,
|
|
|
|
pending: unapproved_count + unapproved_replacements_count,
|
|
|
|
}
|
|
|
|
end
|
2015-08-14 16:56:25 -04:00
|
|
|
end
|
2013-03-19 08:10:10 -04:00
|
|
|
|
2019-06-22 04:48:08 -04:00
|
|
|
def post_upload_throttle
|
2023-11-08 16:21:11 -05:00
|
|
|
@post_upload_throttle ||= is_privileged? ? hourly_upload_limit : [hourly_upload_limit, post_edit_limit].min
|
2017-08-11 00:52:47 -04:00
|
|
|
end
|
|
|
|
|
2013-02-20 00:02:43 -05:00
|
|
|
def tag_query_limit
|
2022-10-10 07:26:12 -04:00
|
|
|
Danbooru.config.tag_query_limit
|
2013-02-20 00:02:43 -05:00
|
|
|
end
|
2013-03-19 08:10:10 -04:00
|
|
|
|
2013-02-20 00:02:43 -05:00
|
|
|
def favorite_limit
|
2023-08-21 15:41:06 -04:00
|
|
|
Danbooru.config.legacy_favorite_limit.fetch(id, 80_000)
|
2013-02-20 00:02:43 -05:00
|
|
|
end
|
2015-06-23 15:25:54 -04:00
|
|
|
|
2017-01-06 18:40:49 -05:00
|
|
|
def api_regen_multiplier
|
2019-09-16 15:39:47 -04:00
|
|
|
1
|
2017-01-06 18:40:49 -05:00
|
|
|
end
|
2016-10-17 19:27:03 -04:00
|
|
|
|
2017-01-06 18:40:49 -05:00
|
|
|
def api_burst_limit
|
|
|
|
# can make this many api calls at once before being bound by
|
|
|
|
# api_regen_multiplier refilling your pool
|
2023-08-21 15:24:31 -04:00
|
|
|
if is_former_staff?
|
2019-09-16 15:39:47 -04:00
|
|
|
120
|
2019-06-29 11:45:58 -04:00
|
|
|
elsif is_privileged?
|
2019-09-16 15:39:47 -04:00
|
|
|
90
|
2016-10-17 19:27:03 -04:00
|
|
|
else
|
2019-09-16 15:39:47 -04:00
|
|
|
60
|
2013-03-20 19:35:35 -04:00
|
|
|
end
|
|
|
|
end
|
2014-06-01 13:39:19 -04:00
|
|
|
|
2017-01-06 18:40:49 -05:00
|
|
|
def remaining_api_limit
|
2019-02-01 15:38:26 -05:00
|
|
|
token_bucket.uncached_count
|
2016-10-17 19:27:03 -04:00
|
|
|
end
|
|
|
|
|
2013-03-21 10:46:49 -04:00
|
|
|
def statement_timeout
|
2023-08-21 15:24:31 -04:00
|
|
|
if is_former_staff?
|
2013-03-21 10:46:49 -04:00
|
|
|
9_000
|
2019-06-29 11:45:58 -04:00
|
|
|
elsif is_privileged?
|
2013-03-21 10:46:49 -04:00
|
|
|
6_000
|
|
|
|
else
|
|
|
|
3_000
|
|
|
|
end
|
|
|
|
end
|
2010-03-12 15:18:30 -05:00
|
|
|
end
|
2013-03-19 08:10:10 -04:00
|
|
|
|
2011-09-10 15:58:04 -04:00
|
|
|
module ApiMethods
|
2017-04-29 00:24:23 -04:00
|
|
|
# blacklist all attributes by default. whitelist only safe attributes.
|
2011-09-10 15:58:04 -04:00
|
|
|
def hidden_attributes
|
2017-04-29 00:24:23 -04:00
|
|
|
super + attributes.keys.map(&:to_sym)
|
2011-09-10 15:58:04 -04:00
|
|
|
end
|
2013-03-19 08:10:10 -04:00
|
|
|
|
2014-06-01 13:39:19 -04:00
|
|
|
def method_attributes
|
2017-04-29 00:24:23 -04:00
|
|
|
list = super + [
|
2019-09-16 16:50:47 -04:00
|
|
|
:id, :created_at, :name, :level, :base_upload_limit,
|
2017-04-29 00:24:23 -04:00
|
|
|
:post_upload_count, :post_update_count, :note_update_count,
|
2019-06-01 11:46:03 -04:00
|
|
|
:is_banned, :can_approve_posts, :can_upload_free,
|
2020-06-26 20:58:52 -04:00
|
|
|
:level_string, :avatar_id
|
2017-04-29 00:24:23 -04:00
|
|
|
]
|
|
|
|
|
2014-06-01 13:39:19 -04:00
|
|
|
if id == CurrentUser.user.id
|
2024-02-23 11:11:40 -05:00
|
|
|
boolean_attributes = %i[
|
|
|
|
blacklist_users description_collapsed_initially
|
|
|
|
hide_comments show_hidden_comments show_post_statistics
|
2024-02-23 11:18:22 -05:00
|
|
|
is_banned receive_email_notifications
|
2024-02-23 11:11:40 -05:00
|
|
|
enable_keyboard_navigation enable_privacy_mode
|
|
|
|
style_usernames enable_auto_complete
|
|
|
|
can_approve_posts can_upload_free
|
|
|
|
disable_cropped_thumbnails enable_safe_mode
|
|
|
|
disable_responsive_mode no_flagging disable_user_dmails
|
|
|
|
enable_compact_uploader replacements_beta
|
|
|
|
]
|
|
|
|
list += boolean_attributes + [
|
2017-04-29 00:24:23 -04:00
|
|
|
:updated_at, :email, :last_logged_in_at, :last_forum_read_at,
|
|
|
|
:recent_tags, :comment_threshold, :default_image_size,
|
|
|
|
:favorite_tags, :blacklisted_tags, :time_zone, :per_page,
|
|
|
|
:custom_style, :favorite_count,
|
|
|
|
:api_regen_multiplier, :api_burst_limit, :remaining_api_limit,
|
2019-03-23 03:57:37 -04:00
|
|
|
:statement_timeout, :favorite_limit,
|
2024-02-23 11:18:22 -05:00
|
|
|
:tag_query_limit, :has_mail?
|
2017-04-29 00:24:23 -04:00
|
|
|
]
|
2014-06-01 13:39:19 -04:00
|
|
|
end
|
2017-04-29 00:24:23 -04:00
|
|
|
|
2014-06-01 13:39:19 -04:00
|
|
|
list
|
|
|
|
end
|
|
|
|
|
2017-04-29 11:45:24 -04:00
|
|
|
# extra attributes returned for /users/:id.json but not for /users.json.
|
|
|
|
def full_attributes
|
2024-04-05 11:37:39 -04:00
|
|
|
%i[
|
|
|
|
wiki_page_version_count artist_version_count pool_version_count
|
|
|
|
forum_post_count comment_count flag_count favorite_count
|
|
|
|
positive_feedback_count neutral_feedback_count negative_feedback_count
|
|
|
|
upload_limit profile_about profile_artinfo
|
2017-04-29 11:45:24 -04:00
|
|
|
]
|
|
|
|
end
|
2011-09-10 15:58:04 -04:00
|
|
|
end
|
2013-03-19 08:10:10 -04:00
|
|
|
|
2013-05-05 17:34:41 -04:00
|
|
|
module CountMethods
|
|
|
|
def wiki_page_version_count
|
2019-06-04 08:56:20 -04:00
|
|
|
user_status.wiki_edit_count
|
|
|
|
end
|
|
|
|
|
2019-06-04 09:41:43 -04:00
|
|
|
def post_update_count
|
|
|
|
user_status.post_update_count
|
|
|
|
end
|
|
|
|
|
|
|
|
def post_upload_count
|
|
|
|
user_status.post_count
|
|
|
|
end
|
|
|
|
|
2020-01-03 08:52:31 -05:00
|
|
|
def post_deleted_count
|
|
|
|
user_status.post_deleted_count
|
|
|
|
end
|
|
|
|
|
2019-06-04 08:56:20 -04:00
|
|
|
def note_version_count
|
|
|
|
user_status.note_count
|
2013-05-05 17:34:41 -04:00
|
|
|
end
|
|
|
|
|
2019-06-04 09:41:43 -04:00
|
|
|
def note_update_count
|
|
|
|
note_version_count
|
|
|
|
end
|
|
|
|
|
2013-05-05 17:34:41 -04:00
|
|
|
def artist_version_count
|
2019-06-04 08:56:20 -04:00
|
|
|
user_status.artist_edit_count
|
2013-05-05 17:34:41 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def pool_version_count
|
2019-06-04 08:56:20 -04:00
|
|
|
user_status.pool_edit_count
|
2013-05-05 17:34:41 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def forum_post_count
|
2019-06-04 08:56:20 -04:00
|
|
|
user_status.forum_post_count
|
|
|
|
end
|
|
|
|
|
|
|
|
def favorite_count
|
|
|
|
user_status.favorite_count
|
2013-05-05 17:34:41 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def comment_count
|
2019-06-04 08:56:20 -04:00
|
|
|
user_status.comment_count
|
2013-05-05 17:34:41 -04:00
|
|
|
end
|
2013-05-20 09:38:05 -04:00
|
|
|
|
2013-08-13 13:33:25 -04:00
|
|
|
def flag_count
|
2019-06-04 08:56:20 -04:00
|
|
|
user_status.post_flag_count
|
2013-08-13 13:33:25 -04:00
|
|
|
end
|
|
|
|
|
2023-05-16 14:48:51 -04:00
|
|
|
def ticket_count
|
|
|
|
user_status.ticket_count
|
|
|
|
end
|
|
|
|
|
2025-02-26 08:34:43 -05:00
|
|
|
def feedback_pieces
|
|
|
|
@feedback_pieces ||= begin
|
|
|
|
count = {
|
|
|
|
deleted: 0,
|
|
|
|
negative: 0,
|
|
|
|
neutral: 0,
|
|
|
|
positive: 0,
|
|
|
|
}
|
|
|
|
|
|
|
|
feedback.each do |one|
|
|
|
|
if one.is_deleted
|
|
|
|
count[:deleted] += 1
|
|
|
|
next
|
|
|
|
end
|
|
|
|
|
|
|
|
count[one.category.to_sym] += 1
|
|
|
|
end
|
|
|
|
|
|
|
|
count
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-05-20 09:38:05 -04:00
|
|
|
def positive_feedback_count
|
2024-09-07 17:28:07 -04:00
|
|
|
feedback.active.positive.count
|
2013-05-20 09:38:05 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def neutral_feedback_count
|
2024-09-07 17:28:07 -04:00
|
|
|
feedback.active.neutral.count
|
2013-05-20 09:38:05 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def negative_feedback_count
|
2024-09-07 17:28:07 -04:00
|
|
|
feedback.active.negative.count
|
|
|
|
end
|
|
|
|
|
|
|
|
def deleted_feedback_count
|
|
|
|
feedback.deleted.count
|
2013-05-20 09:38:05 -04:00
|
|
|
end
|
2018-06-20 14:23:44 -04:00
|
|
|
|
2021-06-26 07:01:26 -04:00
|
|
|
def post_replacement_rejected_count
|
|
|
|
user_status.post_replacement_rejected_count
|
|
|
|
end
|
|
|
|
|
|
|
|
def own_post_replaced_count
|
|
|
|
user_status.own_post_replaced_count
|
|
|
|
end
|
|
|
|
|
|
|
|
def own_post_replaced_penalize_count
|
|
|
|
user_status.own_post_replaced_penalize_count
|
|
|
|
end
|
|
|
|
|
2018-06-20 14:23:44 -04:00
|
|
|
def refresh_counts!
|
|
|
|
self.class.without_timeout do
|
2019-06-04 09:41:43 -04:00
|
|
|
UserStatus.where(user_id: id).update_all(
|
|
|
|
post_count: Post.for_user(id).count,
|
2020-01-03 08:52:31 -05:00
|
|
|
post_deleted_count: Post.for_user(id).deleted.count,
|
2022-08-06 12:58:24 -04:00
|
|
|
post_update_count: PostVersion.for_user(id).count,
|
2023-05-16 13:58:49 -04:00
|
|
|
favorite_count: Favorite.for_user(id).count,
|
|
|
|
note_count: NoteVersion.for_user(id).count,
|
|
|
|
own_post_replaced_count: PostReplacement.for_uploader_on_approve(id).count,
|
|
|
|
own_post_replaced_penalize_count: PostReplacement.penalized.for_uploader_on_approve(id).count,
|
2023-08-15 08:13:58 -04:00
|
|
|
post_replacement_rejected_count: post_replacements.rejected.count,
|
2018-06-20 14:23:44 -04:00
|
|
|
)
|
|
|
|
end
|
|
|
|
end
|
2013-05-05 17:34:41 -04:00
|
|
|
end
|
|
|
|
|
2013-01-10 17:45:52 -05:00
|
|
|
module SearchMethods
|
|
|
|
def admins
|
2013-03-26 01:03:42 -04:00
|
|
|
where("level = ?", Levels::ADMIN)
|
2013-01-10 17:45:52 -05:00
|
|
|
end
|
2013-03-19 08:10:10 -04:00
|
|
|
|
2013-01-10 17:45:52 -05:00
|
|
|
def with_email(email)
|
2013-03-19 08:10:10 -04:00
|
|
|
if email.blank?
|
2013-01-10 17:45:52 -05:00
|
|
|
where("FALSE")
|
2013-03-19 08:10:10 -04:00
|
|
|
else
|
2020-03-12 22:05:14 -04:00
|
|
|
where("lower(email) = lower(?)", email)
|
2013-01-10 17:45:52 -05:00
|
|
|
end
|
|
|
|
end
|
2013-03-19 08:10:10 -04:00
|
|
|
|
2013-01-10 17:45:52 -05:00
|
|
|
def search(params)
|
2017-12-17 17:58:34 -05:00
|
|
|
q = super
|
2019-06-04 09:41:43 -04:00
|
|
|
q = q.joins(:user_status)
|
2013-03-19 08:10:10 -04:00
|
|
|
|
2018-10-04 14:03:18 -04:00
|
|
|
q = q.attribute_matches(:level, params[:level])
|
2023-05-18 14:48:01 -04:00
|
|
|
|
|
|
|
if params[:about_me].present?
|
|
|
|
q = q.attribute_matches(:profile_about, params[:about_me]).or(attribute_matches(:profile_artinfo, params[:about_me]))
|
|
|
|
end
|
2018-10-04 14:03:18 -04:00
|
|
|
|
2023-08-01 14:06:46 -04:00
|
|
|
if params[:avatar_id].present?
|
|
|
|
q = q.where(avatar_id: params[:avatar_id])
|
|
|
|
end
|
|
|
|
|
2022-05-22 09:21:41 -04:00
|
|
|
if params[:email_matches].present?
|
2020-03-06 14:37:13 -05:00
|
|
|
q = q.where_ilike(:email, params[:email_matches])
|
|
|
|
end
|
|
|
|
|
2013-02-19 12:27:17 -05:00
|
|
|
if params[:name_matches].present?
|
2018-10-02 20:38:55 -04:00
|
|
|
q = q.where_ilike(:name, normalize_name(params[:name_matches]))
|
|
|
|
end
|
|
|
|
|
2013-02-19 12:27:17 -05:00
|
|
|
if params[:min_level].present?
|
2013-01-10 17:45:52 -05:00
|
|
|
q = q.where("level >= ?", params[:min_level].to_i)
|
|
|
|
end
|
2013-02-22 12:27:19 -05:00
|
|
|
|
2013-04-28 14:11:21 -04:00
|
|
|
if params[:max_level].present?
|
|
|
|
q = q.where("level <= ?", params[:max_level].to_i)
|
|
|
|
end
|
|
|
|
|
2016-09-23 19:21:09 -04:00
|
|
|
bitprefs_length = BOOLEAN_ATTRIBUTES.length
|
2016-09-10 16:06:27 -04:00
|
|
|
bitprefs_include = nil
|
|
|
|
bitprefs_exclude = nil
|
|
|
|
|
2019-06-01 11:46:03 -04:00
|
|
|
[:can_approve_posts, :can_upload_free].each do |x|
|
2016-09-10 16:06:27 -04:00
|
|
|
if params[x].present?
|
|
|
|
attr_idx = BOOLEAN_ATTRIBUTES.index(x.to_s)
|
2018-05-03 19:53:35 -04:00
|
|
|
if params[x].to_s.truthy?
|
2016-09-23 19:21:09 -04:00
|
|
|
bitprefs_include ||= "0"*bitprefs_length
|
|
|
|
bitprefs_include[attr_idx] = '1'
|
2018-05-03 19:53:35 -04:00
|
|
|
elsif params[x].to_s.falsy?
|
2016-09-23 19:21:09 -04:00
|
|
|
bitprefs_exclude ||= "0"*bitprefs_length
|
|
|
|
bitprefs_exclude[attr_idx] = '1'
|
2016-09-10 16:06:27 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2016-09-23 19:21:09 -04:00
|
|
|
|
2016-09-10 16:06:27 -04:00
|
|
|
if bitprefs_include
|
2016-09-23 19:21:09 -04:00
|
|
|
bitprefs_include.reverse!
|
|
|
|
q = q.where("bit_prefs::bit(:len) & :bits::bit(:len) = :bits::bit(:len)",
|
|
|
|
{:len => bitprefs_length, :bits => bitprefs_include})
|
2016-09-10 16:06:27 -04:00
|
|
|
end
|
2016-09-23 19:21:09 -04:00
|
|
|
|
2016-09-10 16:06:27 -04:00
|
|
|
if bitprefs_exclude
|
2016-09-23 19:21:09 -04:00
|
|
|
bitprefs_exclude.reverse!
|
|
|
|
q = q.where("bit_prefs::bit(:len) & :bits::bit(:len) = 0::bit(:len)",
|
|
|
|
{:len => bitprefs_length, :bits => bitprefs_exclude})
|
2016-09-10 16:06:27 -04:00
|
|
|
end
|
|
|
|
|
2021-10-30 13:46:55 -04:00
|
|
|
if params[:ip_addr].present?
|
|
|
|
q = q.where("last_ip_addr <<= ?", params[:ip_addr])
|
2015-04-23 09:43:19 -04:00
|
|
|
end
|
2019-05-15 13:50:46 -04:00
|
|
|
|
2013-03-08 16:52:24 -05:00
|
|
|
case params[:order]
|
|
|
|
when "name"
|
|
|
|
q = q.order("name")
|
|
|
|
when "post_upload_count"
|
2019-06-04 09:41:43 -04:00
|
|
|
q = q.order("user_statuses.post_count desc")
|
2013-03-08 16:52:24 -05:00
|
|
|
when "note_count"
|
2019-06-04 09:41:43 -04:00
|
|
|
q = q.order("user_statuses.note_count desc")
|
2013-03-19 08:10:10 -04:00
|
|
|
when "post_update_count"
|
2019-06-04 09:41:43 -04:00
|
|
|
q = q.order("user_statuses.post_update_count desc")
|
2013-03-08 16:52:24 -05:00
|
|
|
else
|
2023-07-07 08:32:57 -04:00
|
|
|
q = q.apply_basic_order(params)
|
2013-03-08 16:52:24 -05:00
|
|
|
end
|
2013-03-19 08:10:10 -04:00
|
|
|
|
2013-01-10 17:45:52 -05:00
|
|
|
q
|
|
|
|
end
|
|
|
|
end
|
2013-03-19 08:10:10 -04:00
|
|
|
|
2017-09-15 18:13:01 -04:00
|
|
|
concerning :SockPuppetMethods do
|
|
|
|
def validate_sock_puppets
|
|
|
|
if User.where(last_ip_addr: CurrentUser.ip_addr).where("created_at > ?", 1.day.ago).exists?
|
|
|
|
errors.add(:last_ip_addr, "was used recently for another account and cannot be reused for another day")
|
2016-08-24 18:58:22 -04:00
|
|
|
end
|
|
|
|
end
|
2016-08-19 19:40:14 -04:00
|
|
|
end
|
|
|
|
|
2010-03-18 17:55:57 -04:00
|
|
|
include BanMethods
|
2010-02-10 16:12:30 -05:00
|
|
|
include NameMethods
|
|
|
|
include PasswordMethods
|
2011-10-15 16:36:07 -04:00
|
|
|
include AuthenticationMethods
|
2010-02-19 17:30:11 -05:00
|
|
|
include LevelMethods
|
2011-09-11 17:48:30 -04:00
|
|
|
include EmailMethods
|
2010-03-10 18:21:43 -05:00
|
|
|
include BlacklistMethods
|
2010-03-11 19:42:04 -05:00
|
|
|
include ForumMethods
|
2010-03-12 15:18:30 -05:00
|
|
|
include LimitMethods
|
2011-09-10 15:58:04 -04:00
|
|
|
include ApiMethods
|
2013-05-05 17:34:41 -04:00
|
|
|
include CountMethods
|
2013-01-10 17:45:52 -05:00
|
|
|
extend SearchMethods
|
2019-06-27 11:33:02 -04:00
|
|
|
extend ThrottleMethods
|
2013-03-19 08:10:10 -04:00
|
|
|
|
2024-02-23 11:18:22 -05:00
|
|
|
def has_mail?
|
|
|
|
unread_dmail_count > 0
|
|
|
|
end
|
|
|
|
|
2016-02-22 15:18:19 -05:00
|
|
|
def hide_favorites?
|
2022-11-28 10:21:40 -05:00
|
|
|
return false if CurrentUser.is_moderator?
|
2022-09-11 13:49:13 -04:00
|
|
|
return true if is_blocked?
|
|
|
|
enable_privacy_mode? && CurrentUser.user.id != id
|
2016-02-22 15:18:19 -05:00
|
|
|
end
|
|
|
|
|
2020-01-03 09:39:55 -05:00
|
|
|
def compact_uploader?
|
|
|
|
post_upload_count >= 10 && enable_compact_uploader?
|
|
|
|
end
|
|
|
|
|
2018-04-02 13:51:26 -04:00
|
|
|
def initialize_attributes
|
2019-09-12 18:00:03 -04:00
|
|
|
return if Rails.env.test?
|
|
|
|
Danbooru.config.customize_new_user(self)
|
2014-06-19 03:07:19 -04:00
|
|
|
end
|
2017-08-11 00:54:59 -04:00
|
|
|
|
|
|
|
def presenter
|
|
|
|
@presenter ||= UserPresenter.new(self)
|
|
|
|
end
|
2023-11-12 15:04:51 -05:00
|
|
|
|
|
|
|
# Copied from UserNameValidator. Check back later how effective this was.
|
|
|
|
# Users with invalid names may be automatically renamed in the future.
|
|
|
|
def name_error
|
|
|
|
if name.length > 20
|
|
|
|
"must be 2 to 20 characters long"
|
|
|
|
elsif name !~ /\A[a-zA-Z0-9\-_~']+\z/
|
|
|
|
"must contain only alphanumeric characters, hypens, apostrophes, tildes and underscores"
|
|
|
|
elsif name =~ /\A[_\-~']/
|
|
|
|
"must not begin with a special character"
|
|
|
|
elsif name =~ /_{2}|-{2}|~{2}|'{2}/
|
|
|
|
"must not contain consecutive special characters"
|
|
|
|
elsif name =~ /\A_|_\z/
|
|
|
|
"cannot begin or end with an underscore"
|
|
|
|
elsif name =~ /\A[0-9]+\z/
|
|
|
|
"cannot consist of numbers only"
|
|
|
|
end
|
|
|
|
end
|
2010-02-06 16:48:40 -05:00
|
|
|
end
|