Refactored community-curated world timeline code; **privacy**: remove support for packmate-visible hashtags until we resolve federation caveats.
parent
c86c4b95be
commit
2db51e2f4c
|
@ -30,10 +30,19 @@ class Api::V1::Statuses::BookmarksController < Api::BaseController
|
||||||
|
|
||||||
bookmark = Bookmark.find_or_create_by!(account: current_user.account, status: requested_status)
|
bookmark = Bookmark.find_or_create_by!(account: current_user.account, status: requested_status)
|
||||||
|
|
||||||
|
curate_status(requested_status)
|
||||||
|
|
||||||
bookmark.status.reload
|
bookmark.status.reload
|
||||||
end
|
end
|
||||||
|
|
||||||
def requested_status
|
def requested_status
|
||||||
Status.find(params[:status_id])
|
Status.find(params[:status_id])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def curate_status(status)
|
||||||
|
return if status.curated
|
||||||
|
status.curated = true
|
||||||
|
status.save
|
||||||
|
FanOutOnWriteService.new.call(status)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -24,6 +24,8 @@
|
||||||
# local_only :boolean
|
# local_only :boolean
|
||||||
# poll_id :bigint(8)
|
# poll_id :bigint(8)
|
||||||
# content_type :string
|
# content_type :string
|
||||||
|
# tsv :tsvector
|
||||||
|
# curated :boolean
|
||||||
#
|
#
|
||||||
|
|
||||||
class Status < ApplicationRecord
|
class Status < ApplicationRecord
|
||||||
|
@ -89,13 +91,13 @@ class Status < ApplicationRecord
|
||||||
scope :remote, -> { where(local: false).or(where.not(uri: nil)) }
|
scope :remote, -> { where(local: false).or(where.not(uri: nil)) }
|
||||||
scope :local, -> { where(local: true).or(where(uri: nil)) }
|
scope :local, -> { where(local: true).or(where(uri: nil)) }
|
||||||
scope :network, -> { where(local: true).or(where(uri: nil)).or(where('statuses.uri LIKE ANY (array[?])', LOCAL_URIS)) }
|
scope :network, -> { where(local: true).or(where(uri: nil)).or(where('statuses.uri LIKE ANY (array[?])', LOCAL_URIS)) }
|
||||||
|
scope :curated, -> { where(curated: true) }
|
||||||
|
|
||||||
scope :without_replies, -> { where('statuses.reply = FALSE OR statuses.in_reply_to_account_id = statuses.account_id') }
|
scope :without_replies, -> { where('statuses.reply = FALSE OR statuses.in_reply_to_account_id = statuses.account_id') }
|
||||||
scope :without_reblogs, -> { where('statuses.reblog_of_id IS NULL') }
|
scope :without_reblogs, -> { where('statuses.reblog_of_id IS NULL') }
|
||||||
scope :reblogs, -> { where('statuses.reblog_of_id IS NOT NULL') } # all reblogs
|
scope :reblogs, -> { where('statuses.reblog_of_id IS NOT NULL') } # all reblogs
|
||||||
scope :with_public_visibility, -> { where(visibility: :public) }
|
scope :with_public_visibility, -> { where(visibility: :public) }
|
||||||
scope :public_browsable, -> { where(visibility: [:public, :unlisted]) }
|
scope :public_browsable, -> { where(visibility: [:public, :unlisted]) }
|
||||||
scope :visible_to, ->(account) { where(visibility: [:public, :unlisted]).or(where(account: [account] + account.following).where(visibility: :private)) }
|
|
||||||
scope :tagged_with, ->(tag) { joins(:statuses_tags).where(statuses_tags: { tag_id: tag }) }
|
scope :tagged_with, ->(tag) { joins(:statuses_tags).where(statuses_tags: { tag_id: tag }) }
|
||||||
scope :excluding_silenced_accounts, -> { left_outer_joins(:account).where(accounts: { silenced_at: nil }) }
|
scope :excluding_silenced_accounts, -> { left_outer_joins(:account).where(accounts: { silenced_at: nil }) }
|
||||||
scope :including_silenced_accounts, -> { left_outer_joins(:account).where.not(accounts: { silenced_at: nil }) }
|
scope :including_silenced_accounts, -> { left_outer_joins(:account).where.not(accounts: { silenced_at: nil }) }
|
||||||
|
@ -306,7 +308,7 @@ class Status < ApplicationRecord
|
||||||
if account.present?
|
if account.present?
|
||||||
query = query
|
query = query
|
||||||
.or(scope.where(account: account))
|
.or(scope.where(account: account))
|
||||||
.or(scope.where(account: account.following, visibility: [:unlisted, :private]))
|
.or(scope.where(account: account.following, visibility: [:private, :unlisted]))
|
||||||
.or(scope.where(id: account.mentions.select(:status_id)))
|
.or(scope.where(id: account.mentions.select(:status_id)))
|
||||||
end
|
end
|
||||||
query = query.where(reblog_of_id: nil).limit(limit)
|
query = query.where(reblog_of_id: nil).limit(limit)
|
||||||
|
@ -367,47 +369,26 @@ class Status < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def as_public_timeline(account = nil, local_only = false)
|
def as_public_timeline(account = nil, local_only = false)
|
||||||
if account.present? && account&.user&.setting_rawr_federated
|
if local_only
|
||||||
query = timeline_scope(local_only).without_replies
|
|
||||||
else
|
|
||||||
# instead of our ftl being a noisy irrelevant firehose
|
|
||||||
# only show public stuff boosted/faved by the community
|
|
||||||
query = Status.network
|
query = Status.network
|
||||||
if local_only then
|
.with_public_visibility
|
||||||
# we don't want to change the ltl
|
.without_replies
|
||||||
query = query
|
.without_reblogs
|
||||||
.with_public_visibility
|
elsif account.nil? || account&.user&.setting_rawr_federated
|
||||||
.without_replies
|
query = timeline_scope(local_only)
|
||||||
.without_reblogs
|
query = query.without_replies unless Setting.show_replies_in_public_timelines
|
||||||
else # but on the ftl
|
else
|
||||||
query = query.without_replies unless Setting.show_replies_in_public_timelines
|
scope = Status.curated
|
||||||
# grab the stuff we faved
|
scope = scope.without_replies unless Setting.show_replies_in_public_timelines
|
||||||
fav_query = Favourite.select('status_id')
|
query = scope.public_browsable
|
||||||
.where(account_id: Account.local)
|
.or(scope.where(account: account.following, visibility: :private))
|
||||||
.reorder(:status_id)
|
|
||||||
.distinct
|
|
||||||
|
|
||||||
# We need to find a new way to do this because it's much too slow.
|
|
||||||
|
|
||||||
# grab the stuff we boosted
|
|
||||||
#boost_query = query.reblogs.select(:reblog_of_id)
|
|
||||||
# .reorder(nil)
|
|
||||||
# .distinct
|
|
||||||
# map those ids to actual statuses
|
|
||||||
|
|
||||||
query = Status.where(id: fav_query)
|
|
||||||
.without_replies
|
|
||||||
.with_public_visibility
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
apply_timeline_filters(query, account, local_only)
|
apply_timeline_filters(query, account, local_only)
|
||||||
end
|
end
|
||||||
|
|
||||||
def as_tag_timeline(tag, account = nil, local_only = false)
|
def as_tag_timeline(tag, account = nil, local_only = false)
|
||||||
query = (account.nil?) ? browsable_timeline_scope(local_only) : user_timeline_scope(account, local_only)
|
query = browsable_timeline_scope(local_only).tagged_with(tag)
|
||||||
query = query.tagged_with(tag)
|
|
||||||
|
|
||||||
apply_timeline_filters(query, account, local_only)
|
apply_timeline_filters(query, account, local_only)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -496,13 +477,6 @@ class Status < ApplicationRecord
|
||||||
.without_reblogs
|
.without_reblogs
|
||||||
end
|
end
|
||||||
|
|
||||||
def user_timeline_scope(account, local_only = false)
|
|
||||||
starting_scope = local_only ? Status.local : Status
|
|
||||||
starting_scope
|
|
||||||
.visible_to(account)
|
|
||||||
.without_reblogs
|
|
||||||
end
|
|
||||||
|
|
||||||
def apply_timeline_filters(query, account, local_only)
|
def apply_timeline_filters(query, account, local_only)
|
||||||
if account.nil?
|
if account.nil?
|
||||||
filter_timeline_default(query)
|
filter_timeline_default(query)
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
class FanOutOnWriteService < BaseService
|
class FanOutOnWriteService < BaseService
|
||||||
# Push a status into home and mentions feeds
|
# Push a status into home and mentions feeds
|
||||||
# @param [Status] status
|
# @param [Status] status
|
||||||
def call(status, allow_nonlocal = false, deliver_to_local = true)
|
def call(status)
|
||||||
raise Mastodon::RaceConditionError if status.visibility.nil?
|
raise Mastodon::RaceConditionError if status.visibility.nil?
|
||||||
|
|
||||||
deliver_to_self(status) if status.account.local?
|
deliver_to_self(status) if status.account.local?
|
||||||
|
@ -24,25 +24,21 @@ class FanOutOnWriteService < BaseService
|
||||||
return if status.reblog? && !Setting.show_reblogs_in_public_timelines
|
return if status.reblog? && !Setting.show_reblogs_in_public_timelines
|
||||||
return if status.account.silenced?
|
return if status.account.silenced?
|
||||||
|
|
||||||
deliver_to_hashtags(status) if !status.reblog? && status.distributable?
|
if !status.reblog? && status.distributable?
|
||||||
|
deliver_to_hashtags(status)
|
||||||
# we want to let community users decide what goes on the ftl with boosts
|
deliver_to_public(status) if status.curated
|
||||||
return unless allow_nonlocal || status.network? || status.relayed?
|
|
||||||
|
|
||||||
if status.reblog? then
|
|
||||||
status = Status.find(status.reblog_of_id)
|
|
||||||
render_anonymous_payload(status)
|
|
||||||
deliver_to_local = status.network?
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return if status.account.silenced? || !status.public_visibility?
|
if status.relayed?
|
||||||
|
status = Status.find(status.reblog_of_id)
|
||||||
|
return if status.account.silenced?
|
||||||
|
render_anonymous_payload(status)
|
||||||
|
end
|
||||||
|
|
||||||
|
return unless status.network? && status.public_visibility? && !status.reblog
|
||||||
return if status.reply? && status.in_reply_to_account_id != status.account_id && !Setting.show_replies_in_public_timelines
|
return if status.reply? && status.in_reply_to_account_id != status.account_id && !Setting.show_replies_in_public_timelines
|
||||||
|
|
||||||
if deliver_to_local then
|
deliver_to_local(status)
|
||||||
deliver_to_local(status)
|
|
||||||
else
|
|
||||||
deliver_to_public(status)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -104,7 +100,6 @@ class FanOutOnWriteService < BaseService
|
||||||
def deliver_to_local(status)
|
def deliver_to_local(status)
|
||||||
Rails.logger.debug "Delivering status #{status.id} to local timeline"
|
Rails.logger.debug "Delivering status #{status.id} to local timeline"
|
||||||
|
|
||||||
return unless status.network?
|
|
||||||
Redis.current.publish('timeline:public:local', @payload)
|
Redis.current.publish('timeline:public:local', @payload)
|
||||||
Redis.current.publish('timeline:public:local:media', @payload) if status.media_attachments.any?
|
Redis.current.publish('timeline:public:local:media', @payload) if status.media_attachments.any?
|
||||||
end
|
end
|
||||||
|
|
|
@ -16,9 +16,7 @@ class FavouriteService < BaseService
|
||||||
|
|
||||||
favourite = Favourite.create!(account: account, status: status)
|
favourite = Favourite.create!(account: account, status: status)
|
||||||
|
|
||||||
# stream it to the world timeline if public
|
curate_status(status)
|
||||||
FanOutOnWriteService.new.call(status, true, false) if status.public_visibility?
|
|
||||||
|
|
||||||
create_notification(favourite)
|
create_notification(favourite)
|
||||||
bump_potential_friendship(account, status)
|
bump_potential_friendship(account, status)
|
||||||
|
|
||||||
|
@ -56,4 +54,11 @@ class FavouriteService < BaseService
|
||||||
def build_xml(favourite)
|
def build_xml(favourite)
|
||||||
OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.favourite_salmon(favourite))
|
OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.favourite_salmon(favourite))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def curate_status(status)
|
||||||
|
return if status.curated
|
||||||
|
status.curated = true
|
||||||
|
status.save
|
||||||
|
FanOutOnWriteService.new.call(status)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -29,6 +29,7 @@ class ReblogService < BaseService
|
||||||
ActivityPub::DistributionWorker.perform_async(reblog.id)
|
ActivityPub::DistributionWorker.perform_async(reblog.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
curate_status(reblogged_status)
|
||||||
create_notification(reblog)
|
create_notification(reblog)
|
||||||
bump_potential_friendship(account, reblog)
|
bump_potential_friendship(account, reblog)
|
||||||
|
|
||||||
|
@ -62,4 +63,11 @@ class ReblogService < BaseService
|
||||||
adapter: ActivityPub::Adapter
|
adapter: ActivityPub::Adapter
|
||||||
).as_json).sign!(reblog.account))
|
).as_json).sign!(reblog.account))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def curate_status(status)
|
||||||
|
return if status.curated
|
||||||
|
status.curated = true
|
||||||
|
status.save
|
||||||
|
FanOutOnWriteService.new.call(status)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
class AddCuratedToStatus < ActiveRecord::Migration[5.2]
|
||||||
|
def change
|
||||||
|
add_column :statuses, :curated, :boolean, null: true, default: nil
|
||||||
|
end
|
||||||
|
end
|
|
@ -639,10 +639,13 @@ ActiveRecord::Schema.define(version: 2019_05_19_130537) do
|
||||||
t.boolean "local_only"
|
t.boolean "local_only"
|
||||||
t.bigint "poll_id"
|
t.bigint "poll_id"
|
||||||
t.string "content_type"
|
t.string "content_type"
|
||||||
|
t.tsvector "tsv"
|
||||||
|
t.boolean "curated"
|
||||||
t.index ["account_id", "id", "visibility", "updated_at"], name: "index_statuses_20180106", order: { id: :desc }
|
t.index ["account_id", "id", "visibility", "updated_at"], name: "index_statuses_20180106", order: { id: :desc }
|
||||||
t.index ["in_reply_to_account_id"], name: "index_statuses_on_in_reply_to_account_id"
|
t.index ["in_reply_to_account_id"], name: "index_statuses_on_in_reply_to_account_id"
|
||||||
t.index ["in_reply_to_id"], name: "index_statuses_on_in_reply_to_id"
|
t.index ["in_reply_to_id"], name: "index_statuses_on_in_reply_to_id"
|
||||||
t.index ["reblog_of_id", "account_id"], name: "index_statuses_on_reblog_of_id_and_account_id"
|
t.index ["reblog_of_id", "account_id"], name: "index_statuses_on_reblog_of_id_and_account_id"
|
||||||
|
t.index ["tsv"], name: "tsv_idx", using: :gin
|
||||||
t.index ["uri"], name: "index_statuses_on_uri", unique: true
|
t.index ["uri"], name: "index_statuses_on_uri", unique: true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue