Skip to content

Commit

Permalink
Wip
Browse files Browse the repository at this point in the history
  • Loading branch information
kmycode committed Feb 20, 2024
1 parent 4b1c327 commit afd8704
Show file tree
Hide file tree
Showing 16 changed files with 541 additions and 96 deletions.
1 change: 1 addition & 0 deletions .haml-lint_todo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ linters:
# Offense count: 1
LineLength:
exclude:
- 'app/views/admin/ng_rules/_ng_rule_fields.html.haml'
- 'app/views/admin/roles/_form.html.haml'

# Offense count: 9
Expand Down
12 changes: 6 additions & 6 deletions app/controllers/admin/ng_rules_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ class NgRulesController < BaseController
def index
authorize :ng_words, :show?

@ng_rules = NgRule.order(id: :asc)
@ng_rules = ::NgRule.order(id: :asc)
end

def new
authorize :ng_words, :show?

@ng_rule = NgRule.build
@ng_rule = ::NgRule.build
end

def edit
Expand All @@ -23,7 +23,7 @@ def edit
def create
authorize :ng_words, :create?

@ng_rule = NgRule.build(resource_params)
@ng_rule = ::NgRule.build(resource_params)

if @ng_rule.save
redirect_to admin_ng_rules_path
Expand Down Expand Up @@ -52,16 +52,16 @@ def destroy
private

def set_ng_rule
@ng_rule = NgRule.find(params[:id])
@ng_rule = ::NgRule.find(params[:id])
end

def resource_params
params.require(:ng_rule).permit(:title, :expires_in, :account_domain, :account_username, :account_display_name, :account_note,
:account_field_name, :account_field_value, :account_avatar_state, :account_header_state,
:account_include_local, :status_spoiler_text, :status_text, :status_tag,
:status_media_state, :status_sensitive_state, :status_cw_state, :status_poll_state,
:status_sensitive_state, :status_cw_state, :status_media_state, :status_poll_state,
:status_quote_state, :status_reply_state, :status_media_threshold, :status_poll_threshold,
:status_mention_threshold, :status_mention_threshold_stranger_only, :status_violation_threshold,
:status_mention_threshold, :status_mention_threshold_stranger_only, :rule_violation_threshold_per_account,
:reaction_type, :reaction_allow_follower, :emoji_reaction_name, :emoji_reaction_origin_domain,
:status_reference_threshold, :account_action, :status_action, :reaction_action,
status_visibility: [], status_searchability: [])
Expand Down
147 changes: 134 additions & 13 deletions app/models/admin/ng_rule.rb
Original file line number Diff line number Diff line change
@@ -1,39 +1,160 @@
# frozen_string_literal: true

class Admin::NgRule
def initialize(ng_rule, account)
def initialize(ng_rule, account, **options)
@ng_rule = ng_rule
@account = account
@options = options
@uri = nil
end

def account_match_and_record!(uri)
@uri = uri
def account_match?
return false if @account.local? && !@ng_rule.account_include_local

record_if_text_match!(:account_domain, @account.domain, @ng_rule.account_domain) ||
record_if_text_match!(:account_username, @account.username, @ng_rule.account_username)
if @account.local?
return false unless @ng_rule.account_include_local
else
return false unless text_match?(:account_domain, @account.domain, @ng_rule.account_domain)
end

text_match?(:account_username, @account.username, @ng_rule.account_username) &&
text_match?(:account_display_name, @account.display_name, @ng_rule.account_display_name) &&
text_match?(:account_note, @account.note, @ng_rule.account_note) &&
text_match?(:account_field_name, @account.fields&.map(&:name)&.join("\n"), @ng_rule.account_field_name) &&
text_match?(:account_field_value, @account.fields&.map(&:value)&.join("\n"), @ng_rule.account_field_value) &&
media_state_match?(:account_avatar_state, @account.avatar, @ng_rule.account_avatar_state) &&
media_state_match?(:account_header_state, @account.header, @ng_rule.account_header_state)
end

def status_match?
@options[:mention_count] = 0 if @ng_rule.status_mention_threshold_stranger_only && !(@options[:mention_to_stranger])

has_media = @options[:media_count].is_a?(Integer) && @options[:media_count].positive?
has_poll = @options[:poll_count].is_a?(Integer) && @options[:poll_count].positive?

@options = @options.merge({ searchability: 'unset' }) if @options[:searchability].nil?

text_match?(:status_spoiler_text, @options[:spoiler_text], @ng_rule.status_spoiler_text) &&
text_match?(:status_text, @options[:text], @ng_rule.status_text) &&
text_match?(:status_tag, @options[:tag_names]&.join("\n"), @ng_rule.status_tag) &&
enum_match?(:status_visibility, @options[:visibility], @ng_rule.status_visibility) &&
enum_match?(:status_searchability, @options[:searchability], @ng_rule.status_searchability) &&
state_match?(:status_sensitive_state, @options[:sensitive], @ng_rule.status_sensitive_state) &&
state_match?(:status_cw_state, @options[:spoiler_text].present?, @ng_rule.status_cw_state) &&
state_match?(:status_media_state, has_media, @ng_rule.status_media_state) &&
state_match?(:status_poll_state, has_poll, @ng_rule.status_poll_state) &&
state_match?(:status_quote_state, @options[:quote].present?, @ng_rule.status_quote_state) &&
state_match?(:status_reply_state, @options[:reply].presence, @ng_rule.status_reply_state) &&
value_over_threshold?(:status_media_threshold, @options[:media_count], @ng_rule.status_media_threshold) &&
value_over_threshold?(:status_poll_threshold, @options[:poll_count], @ng_rule.status_poll_threshold) &&
value_over_threshold?(:status_mention_threshold, @options[:mention_count], @ng_rule.status_mention_threshold) &&
value_over_threshold?(:status_reference_threshold, @options[:reference_count], @ng_rule.status_reference_threshold)
end

def reaction_match?
return false if @ng_rule.reaction_allow_follower && @options[:following]

if @options[:reaction_type] == 'emoji_reaction'
enum_match?(:reaction_type, @options[:reaction_type], @ng_rule.reaction_type) &&
text_match?(:emoji_reaction_name, @options[:emoji_reaction_name], @ng_rule.emoji_reaction_name) &&
text_match?(:emoji_reaction_origin_domain, @options[:emoji_reaction_origin_domain], @ng_rule.emoji_reaction_origin_domain)
else
enum_match?(:reaction_type, @options[:reaction_type], @ng_rule.reaction_type)
end
end

def check_account_or_record!
return true unless account_match?

record!('account', @account.uri)

!violation?
end

def check_status_or_record!
return true unless account_match? && status_match?

record!('status', @options[:uri], text: "#{@options[:spoiler_text]}\n\n#{@options[:text]}") if !@options.key?(:visibility) || %i(public public_unlisted login unlsited).include?(@options[:visibility].to_sym)

!violation?
end

def check_reaction_or_record!
return true unless account_match? && reaction_match?

record!('reaction', @options[:uri])

!violation?
end

private

def include?(text, word)
if word.start_with?('?') && word.size >= 2
text =~ /#{word[1..]}/i
text =~ /#{word[1..]}/
else
text.include?(word)
end
end

def record!(reason, **options)
NgRuleHistory.create!(ng_rule: @ng_rule, account: @account, reason: reason, uri: @uri, **options)
def already_did_count
return @already_did_count if defined?(@already_did_count)

@already_did_count = NgRuleHistory.count(ng_rule: @ng_rule, account: @account)
end

def violation?
limit = @ng_rule.rule_violation_threshold_per_account
limit = 1 unless limit.is_a?(Integer)

return false unless limit.positive?
return true if limit <= 1

already_did_count >= limit - 1
end

def record!(reason, uri, **options)
opts = options.merge({
ng_rule: @ng_rule,
account: @account,
reason: reason,
uri: uri,
})
opts = opts.merge({ skip_count: already_did_count, skip: true }) unless violation?
NgRuleHistory.create!(**opts)
end

def text_match?(_reason, text, arr)
return true if arr.blank? || !text.is_a?(String)

detect_keyword?(text, arr)
end

def enum_match?(_reason, text, arr)
return true if !text.is_a?(String) || text.blank?

arr.include?(text)
end

def state_match?(_reason, exists, expected)
case expected.to_sym
when :needed
exists
when :no_needed
!exists
else
true
end
end

def media_state_match?(reason, media, expected)
state_match?(reason, media.present?, expected)
end

def record_if_text_match!(reason, text, arr, **options)
keyword = detect_keyword(text, arr)
def value_over_threshold?(_reason, value, expected)
return true if !expected.is_a?(Integer) || expected.negative? || !value.is_a?(Integer)

opts = options.merge({ text: text, keyword: keyword })
record!(reason, **opts) if keyword.present?
keyword.present?
value > expected
end

def string_to_array(text)
Expand Down
27 changes: 19 additions & 8 deletions app/models/ng_rule.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,30 +13,30 @@
# account_note :string default(""), not null
# account_field_name :string default(""), not null
# account_field_value :string default(""), not null
# account_avatar_state :integer default(0), not null
# account_header_state :integer default(0), not null
# account_avatar_state :integer default("optional"), not null
# account_header_state :integer default("optional"), not null
# account_include_local :boolean default(TRUE), not null
# status_spoiler_text :string default(""), not null
# status_text :string default(""), not null
# status_tag :string default(""), not null
# status_visibility :string default([]), not null, is an Array
# status_searchability :string default([]), not null, is an Array
# status_media_state :integer default(0), not null
# status_sensitive_state :integer default(0), not null
# status_cw_state :integer default(0), not null
# status_sensitive_state :integer default("optional"), not null
# status_cw_state :integer default("optional"), not null
# status_poll_state :integer default(0), not null
# status_quote_state :integer default(0), not null
# status_reply_state :integer default(0), not null
# status_quote_state :integer default("optional"), not null
# status_reply_state :integer default("optional"), not null
# status_media_threshold :integer default(-1), not null
# status_poll_threshold :integer default(-1), not null
# status_mention_threshold :integer default(-1), not null
# status_mention_threshold_stranger_only :boolean default(TRUE), not null
# status_reference_threshold :integer default(-1), not null
# status_violation_threshold :integer default(1), not null
# reaction_type :string default([]), not null, is an Array
# reaction_allow_follower :boolean default(TRUE), not null
# emoji_reaction_name :string default(""), not null
# emoji_reaction_origin_domain :string default(""), not null
# rule_violation_threshold_per_account :integer default(1), not null
# account_action :integer default(0), not null
# status_action :integer default(0), not null
# reaction_action :integer default(0), not null
Expand All @@ -50,14 +50,25 @@ class NgRule < ApplicationRecord

has_many :histories, class_name: 'NgRuleHistory', inverse_of: :ng_rule, dependent: :destroy

enum :account_avatar_state, { optional: 0, needed: 1, no_needed: 2 }, prefix: :account_avatar
enum :account_header_state, { optional: 0, needed: 1, no_needed: 2 }, prefix: :account_header
enum :status_sensitive_state, { optional: 0, needed: 1, no_needed: 2 }, prefix: :status_sensitive
enum :status_cw_state, { optional: 0, needed: 1, no_needed: 2 }, prefix: :status_cw
enum :status_quote_state, { optional: 0, needed: 1, no_needed: 2 }, prefix: :status_quote
enum :status_reply_state, { optional: 0, needed: 1, no_needed: 2 }, prefix: :status_reply
enum :status_media_state, { optional: 0, needed: 1, no_needed: 2 }, prefix: :status_media
enum :status_poll_state, { optional: 0, needed: 1, no_needed: 2 }, prefix: :status_poll

scope :enabled, -> { where(available: true) }

before_validation :clean_up_arrays
before_save :prepare_cache_invalidation!
before_destroy :prepare_cache_invalidation!
after_commit :invalidate_cache!

def self.cached_rules
active_rules = Rails.cache.fetch('ng_rules') do
NgRule.to_a
NgRule.enabled.to_a
end

active_rules.reject { |ng_rule, _| ng_rule.expired? }
Expand Down
8 changes: 3 additions & 5 deletions app/models/ng_rule_history.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,14 @@
# ng_rule_id :bigint(8) not null
# account_id :bigint(8) not null
# text :string
# keyword :string
# count :integer
# uri :string
# reason :integer not null
# reason :string not null
# skip :boolean default(FALSE), not null
# skip_count :integer
# created_at :datetime not null
# updated_at :datetime not null
#
class NgRuleHistory < ApplicationRecord
enum :reason, { none: 0, account_domain: 1, account_username: 2 }, prefix: :within

belongs_to :ng_rule
belongs_to :account
end
Loading

0 comments on commit afd8704

Please sign in to comment.