diff options
author | multiple creatures <dev@multiple-creature.party> | 2019-08-07 01:08:07 -0500 |
---|---|---|
committer | multiple creatures <dev@multiple-creature.party> | 2019-08-07 01:08:34 -0500 |
commit | ef04f3879ac3bd7ec6dddd6cb843c8cdb79a1175 (patch) | |
tree | 719373d32c084e20d878e9de13a034946c5663b3 /app | |
parent | a8475313b8e81f1e91ee446599a9b7b78716f30c (diff) |
add option to automatically space out boosts over configurable random intervals
Diffstat (limited to 'app')
-rw-r--r-- | app/controllers/settings/preferences_controller.rb | 4 | ||||
-rw-r--r-- | app/lib/user_settings_decorator.rb | 24 | ||||
-rw-r--r-- | app/models/concerns/account_associations.rb | 3 | ||||
-rw-r--r-- | app/models/queued_boost.rb | 17 | ||||
-rw-r--r-- | app/models/status.rb | 2 | ||||
-rw-r--r-- | app/models/user.rb | 26 | ||||
-rw-r--r-- | app/services/reblog_service.rb | 30 | ||||
-rw-r--r-- | app/services/remove_status_service.rb | 6 | ||||
-rw-r--r-- | app/views/settings/preferences/show.html.haml | 12 | ||||
-rw-r--r-- | app/workers/reblog_status_worker.rb | 17 | ||||
-rw-r--r-- | app/workers/scheduler/boosts_scheduler.rb | 49 |
11 files changed, 173 insertions, 17 deletions
diff --git a/app/controllers/settings/preferences_controller.rb b/app/controllers/settings/preferences_controller.rb index 2ae90c7fb..483af2c16 100644 --- a/app/controllers/settings/preferences_controller.rb +++ b/app/controllers/settings/preferences_controller.rb @@ -56,6 +56,10 @@ class Settings::PreferencesController < Settings::BaseController :setting_roar_lifespan, :setting_delayed_roars, :setting_delayed_for, + :setting_boost_interval, + :setting_boost_random, + :setting_boost_interval_from, + :setting_boost_interval_to, :setting_show_cursor, :setting_default_privacy, diff --git a/app/lib/user_settings_decorator.rb b/app/lib/user_settings_decorator.rb index 73831208a..b7e0d577b 100644 --- a/app/lib/user_settings_decorator.rb +++ b/app/lib/user_settings_decorator.rb @@ -40,6 +40,10 @@ class UserSettingsDecorator user.settings['roar_lifespan'] = roar_lifespan_preference if change?('setting_roar_lifespan') user.settings['delayed_roars'] = delayed_roars_preference if change?('setting_delayed_roars') user.settings['delayed_for'] = delayed_for_preference if change?('setting_delayed_for') + user.settings['boost_interval'] = boost_interval_preference if change?('setting_boost_interval') + user.settings['boost_random'] = boost_random_preference if change?('setting_boost_random') + user.settings['boost_interval_from'] = boost_interval_from_preference if change?('setting_boost_interval_from') + user.settings['boost_interval_to'] = boost_interval_to_preference if change?('setting_boost_interval_to') user.settings['show_cursor'] = show_cursor_preference if change?('setting_show_cursor') user.settings['notification_emails'] = merged_notification_emails if change?('notification_emails') @@ -150,6 +154,26 @@ class UserSettingsDecorator boolean_cast_setting 'setting_delayed_roars' end + def boost_interval_preference + boolean_cast_setting 'setting_boost_interval' + end + + def boost_random_preference + boolean_cast_setting 'setting_boost_random' + end + + def boost_interval_from_preference + settings['setting_boost_interval_from'] + end + + def boost_interval_to_preference + settings['setting_boost_interval_to'] + end + + def delayed_for_preference + settings['setting_delayed_for'] + end + def merged_notification_emails user.settings['notification_emails'].merge coerced_settings('notification_emails').to_h end diff --git a/app/models/concerns/account_associations.rb b/app/models/concerns/account_associations.rb index 0c3725e54..a90104943 100644 --- a/app/models/concerns/account_associations.rb +++ b/app/models/concerns/account_associations.rb @@ -58,5 +58,8 @@ module AccountAssociations # Hashtags has_and_belongs_to_many :tags has_many :featured_tags, -> { includes(:tag) }, dependent: :destroy, inverse_of: :account + + # queued boosts + has_many :queued_boosts, dependent: :destroy, inverse_of: :account end end diff --git a/app/models/queued_boost.rb b/app/models/queued_boost.rb new file mode 100644 index 000000000..b23282697 --- /dev/null +++ b/app/models/queued_boost.rb @@ -0,0 +1,17 @@ +# == Schema Information +# +# Table name: queued_boosts +# +# id :bigint(8) not null, primary key +# account_id :bigint(8) +# status_id :bigint(8) +# created_at :datetime not null +# updated_at :datetime not null +# + +class QueuedBoost < ApplicationRecord + belongs_to :account, inverse_of: :queued_boosts + belongs_to :status, inverse_of: :queued_boosts + + validates :account_id, uniqueness: { scope: :status_id } +end diff --git a/app/models/status.rb b/app/models/status.rb index 9f11e6d5d..a2d64ecf0 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -76,6 +76,8 @@ class Status < ApplicationRecord has_many :active_mentions, -> { active }, class_name: 'Mention', inverse_of: :status has_many :media_attachments, dependent: :nullify + has_many :queued_boosts, dependent: :destroy, inverse_of: :status + has_and_belongs_to_many :tags has_and_belongs_to_many :preview_cards diff --git a/app/models/user.rb b/app/models/user.rb index 479392642..a0786aa69 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -131,6 +131,10 @@ class User < ApplicationRecord :roar_lifespan, :delayed_roars, :delayed_for, + :boost_interval, + :boost_random, + :boost_interval_from, + :boost_interval_to, :show_cursor, :auto_play_gif, @@ -303,11 +307,11 @@ class User < ApplicationRecord end def max_public_history - @_max_public_history ||= (settings.max_public_history || 6) + @_max_public_history ||= [1, (settings.max_public_history || 6).to_i].max end def roar_lifespan - @_roar_lifespan ||= (settings.roar_lifespan || 0) + @_roar_lifespan ||= [0, (settings.roar_lifespan || 0).to_i].max end def delayed_roars? @@ -315,7 +319,23 @@ class User < ApplicationRecord end def delayed_for - @_delayed_for ||= (settings.delayed_for || 60) + @_delayed_for ||= [5, (settings.delayed_for || 60).to_i].max + end + + def boost_interval? + @boost_interval ||= (settings.boost_interval || false) + end + + def boost_random? + @boost_random ||= (settings.boost_random || false) + end + + def boost_interval_from + @boost_interval_from ||= [1, (settings.boost_interval_from || 1).to_i].max + end + + def boost_interval_to + @boost_interval_to ||= [2, (settings.boost_interval_to || 15).to_i].max end def shows_cursor? diff --git a/app/services/reblog_service.rb b/app/services/reblog_service.rb index 9ac38ac9d..7d72357f9 100644 --- a/app/services/reblog_service.rb +++ b/app/services/reblog_service.rb @@ -14,22 +14,30 @@ class ReblogService < BaseService authorize_with account, reblogged_status, :reblog? reblog = account.statuses.find_by(reblog: reblogged_status) + new_reblog = reblog.nil? - return reblog unless reblog.nil? + if new_reblog + visibility = options[:visibility] || account.user&.setting_default_privacy + visibility = reblogged_status.visibility if reblogged_status.hidden? + reblog = account.statuses.create!(reblog: reblogged_status, text: '', visibility: visibility) + end - visibility = options[:visibility] || account.user&.setting_default_privacy - visibility = reblogged_status.visibility if reblogged_status.hidden? - reblog = account.statuses.create!(reblog: reblogged_status, text: '', visibility: visibility) + if !options[:distribute] && account&.user&.boost_interval? + QueuedBoost.find_or_create_by!(account_id: account.id, status_id: reblogged_status.id) if account&.user&.boost_interval? + elsif !options[:nodistribute] + return reblog unless options[:distribute] || new_reblog - DistributionWorker.perform_async(reblog.id) + DistributionWorker.perform_async(reblog.id) - unless reblogged_status.local_only? - ActivityPub::DistributionWorker.perform_async(reblog.id) - end + unless reblogged_status.local_only? + ActivityPub::DistributionWorker.perform_async(reblog.id) + end - curate_status(reblogged_status) - create_notification(reblog) unless options[:skip_notify] - bump_potential_friendship(account, reblog) + curate_status(reblogged_status) + + create_notification(reblog) unless options[:skip_notify] + bump_potential_friendship(account, reblog) + end reblog end diff --git a/app/services/remove_status_service.rb b/app/services/remove_status_service.rb index d55ecadef..f9f20aae5 100644 --- a/app/services/remove_status_service.rb +++ b/app/services/remove_status_service.rb @@ -17,6 +17,7 @@ class RemoveStatusService < BaseService RedisLock.acquire(lock_options) do |lock| if lock.acquired? + remove_from_queued remove_from_self if status.account.local? remove_from_followers remove_from_lists @@ -46,6 +47,11 @@ class RemoveStatusService < BaseService private + def remove_from_queued + QueuedBoost.where(account_id: @account.id, status_id: @status.proper.id).destroy_all + QueuedBoost.where(status_id: @status.id).destroy_all + end + def remove_from_self FeedManager.instance.unpush_from_home(@account, @status) end diff --git a/app/views/settings/preferences/show.html.haml b/app/views/settings/preferences/show.html.haml index 07c9fd86f..e6198de6a 100644 --- a/app/views/settings/preferences/show.html.haml +++ b/app/views/settings/preferences/show.html.haml @@ -35,7 +35,13 @@ .fields-group = f.input :setting_delayed_roars, as: :boolean, wrapper: :with_label - = f.input :setting_delayed_for, collection: [5, 10, 15, 30, 60, 120, 180, 300, 360, 600, 1800, 3600], wrapper: :with_label, include_blank: false, label_method: lambda { |item| safe_join([t("simple_form.labels.delayed_for.#{item}")]) }, selected: [5, current_user.delayed_for.to_i].max + = f.input :setting_delayed_for, collection: [5, 10, 15, 30, 60, 120, 180, 300, 360, 600, 1800, 3600], wrapper: :with_label, include_blank: false, label_method: lambda { |item| safe_join([t("simple_form.labels.delayed_for.#{item}")]) }, selected: current_user.delayed_for + + .fields-group + = f.input :setting_boost_interval, as: :boolean, wrapper: :with_label + = f.input :setting_boost_random, as: :boolean, wrapper: :with_label + = f.input :setting_boost_interval_from, collection: [1, 2, 3, 4, 5, 6, 10, 15, 30, 60, 120, 180, 300, 360, 720, 1440], wrapper: :with_label, include_blank: false, label_method: lambda { |item| safe_join([t("simple_form.labels.boost_interval.#{item}")]) }, selected: current_user.boost_interval_from + = f.input :setting_boost_interval_to, collection: [1, 2, 3, 4, 5, 6, 10, 15, 30, 60, 120, 180, 300, 360, 720, 1440], wrapper: :with_label, include_blank: false, label_method: lambda { |item| safe_join([t("simple_form.labels.boost_interval.#{item}")]) }, selected: current_user.boost_interval_to %hr#settings_other/ @@ -54,8 +60,8 @@ %hr/ .fields-group - = f.input :setting_max_public_history, collection: [1, 3, 6, 7, 14, 30, 60, 90, 180, 365, 730, 1095, 2190], wrapper: :with_label, include_blank: false, label_method: lambda { |item| safe_join([t("simple_form.labels.lifespan.#{item}")]) }, selected: current_user.max_public_history.to_i - = f.input :setting_roar_lifespan, collection: [0, 1, 3, 6, 7, 14, 30, 60, 90, 180, 365, 730, 1095, 2190], wrapper: :with_label, include_blank: false, label_method: lambda { |item| safe_join([t("simple_form.labels.lifespan.#{item}")]) }, selected: current_user.roar_lifespan.to_i + = f.input :setting_max_public_history, collection: [1, 3, 6, 7, 14, 30, 60, 90, 180, 365, 730, 1095, 2190], wrapper: :with_label, include_blank: false, label_method: lambda { |item| safe_join([t("simple_form.labels.lifespan.#{item}")]) }, selected: current_user.max_public_history + = f.input :setting_roar_lifespan, collection: [0, 1, 3, 6, 7, 14, 30, 60, 90, 180, 365, 730, 1095, 2190], wrapper: :with_label, include_blank: false, label_method: lambda { |item| safe_join([t("simple_form.labels.lifespan.#{item}")]) }, selected: current_user.roar_lifespan = f.input :setting_hide_public_profile, as: :boolean, wrapper: :with_label = f.input :setting_hide_public_outbox, as: :boolean, wrapper: :with_label diff --git a/app/workers/reblog_status_worker.rb b/app/workers/reblog_status_worker.rb new file mode 100644 index 000000000..c0b2153b2 --- /dev/null +++ b/app/workers/reblog_status_worker.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class ReblogStatusWorker + include Sidekiq::Worker + + sidekiq_options unique: :until_executed + + def perform(account_id, status_id, reblog_params = {}) + account = Account.find(account_id) + status = Status.find(status_id) + return false if status.destroyed? + ReblogService.new.call(account, status, reblog_params.symbolize_keys) + true + rescue ActiveRecord::RecordNotFound, ActiveRecord::RecordInvalid + true + end +end diff --git a/app/workers/scheduler/boosts_scheduler.rb b/app/workers/scheduler/boosts_scheduler.rb new file mode 100644 index 000000000..de0d89992 --- /dev/null +++ b/app/workers/scheduler/boosts_scheduler.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +class Scheduler::BoostsScheduler + include Sidekiq::Worker + include Redisable + + sidekiq_options unique: :until_executed, retry: 0 + + def perform + process_queued_boosts! + end + + private + + def process_queued_boosts! + queued_accounts.find_each do |account| + next if redis.exists("queued_boost:#{account.id}") || account&.user.nil? + + q = next_boost(account.id, account.user.boost_random?) + next if q.empty? + + from_interval = account.user.boost_interval_from + to_interval = account.user.boost_interval_to + + if from_interval > to_interval + from_interval, to_interval = [to_interval, from_interval] + end + + interval = rand(from_interval .. to_interval).minutes + + redis.setex("queued_boost:#{account.id}", interval, 1) + ReblogStatusWorker.perform_async(account.id, q.first.status_id, distribute: true) + q.destroy_all + end + end + + def queued_accounts + Account.where(id: queued_account_ids) + end + + def queued_account_ids + QueuedBoost.distinct.pluck(:account_id) + end + + def next_boost(account_id, boost_random = false) + q = QueuedBoost.where(account_id: account_id) + (boost_random ? q.order(Arel.sql('RANDOM()')) : q.order(:id)).limit(1) + end +end |