このコミットが含まれているのは:
2026-06-06 19:49:21 +09:00
コミット 81e620c33a
22個のファイルの変更1290行の追加245行の削除
+29
ファイルの表示
@@ -0,0 +1,29 @@
class TheatrePostAdvancer
def self.call(theatre:)
new(theatre:).call
end
def initialize(theatre:)
@theatre = theatre
end
def call
previous_post = theatre.current_post
post = TheatrePostSelector.new(theatre:).select
TheatreSkipVote.where(theatre:, post: previous_post).delete_all if previous_post
theatre.update!(current_post: post, current_post_started_at: post ? Time.current : nil)
if post
position = (theatre.programmes.maximum(:position) || 0) + 1
theatre.programmes.create!(position:, post:)
end
post
end
private
attr_reader :theatre
end
+92
ファイルの表示
@@ -0,0 +1,92 @@
class TheatrePostSelector
Candidate = Struct.new(:post, :weight, :penalty, :tags, keyword_init: true)
def initialize(theatre:)
@theatre = theatre
end
def select
candidates = weighted_candidates
return nil if candidates.empty?
total = candidates.sum(&:weight)
target = rand * total
candidates.each do |candidate|
target -= candidate.weight
return candidate.post if target <= 0
end
candidates.last.post
end
def weight_json(limit: 20)
candidates = weighted_candidates
sorted = candidates.sort_by { |candidate| [candidate.weight, candidate.post.id] }
{ tag_penalties: tag_penalty_json,
lightest_posts: post_weight_json(sorted.first(limit)),
heaviest_posts: post_weight_json(sorted.reverse.first(limit)) }
end
private
attr_reader :theatre
def weighted_candidates
@weighted_candidates ||= begin
penalties = tag_penalties
posts = eligible_posts.includes(tags: :tag_name).to_a
posts.map do |post|
post_tags = post.tags.to_a
penalty = post_tags.sum { |tag| penalties[tag.id].to_i }
Candidate.new(post:, penalty:, tags: post_tags, weight: 1.0 / (1.0 + penalty))
end
end
end
def eligible_posts
posts = Post.where("url LIKE '%nicovideo.jp%'")
posts = posts.where.not(id: theatre.current_post_id) if theatre.current_post_id
posts
end
def active_user_ids
@active_user_ids ||= theatre.watching_users.ids
end
def tag_penalties
@tag_penalties ||=
if active_user_ids.empty?
{}
else
TheatreSkipEventVoter
.joins(theatre_skip_event: :event_tags)
.where(user_id: active_user_ids)
.group('theatre_skip_event_tags.tag_id')
.count
end
end
def tag_penalty_json
return [] if tag_penalties.empty?
tags = Tag.where(id: tag_penalties.keys).includes(:tag_name).index_by(&:id)
tag_penalties.map { |tag_id, penalty|
tag = tags[tag_id]
next unless tag
{ tag: TagRepr.inline(tag), penalty: }
}.compact.sort_by { |row| [-row[:penalty], row[:tag]['name'].to_s] }
end
def post_weight_json(candidates)
candidates.map { |candidate|
{ post: PostRepr.base(candidate.post),
weight: candidate.weight,
penalty: candidate.penalty,
tags: candidate.tags.map { |tag| TagRepr.inline(tag) } }
}
end
end
+40
ファイルの表示
@@ -0,0 +1,40 @@
class TheatreSkipFinalizer
def self.call(theatre:, user:)
new(theatre:, user:).call
end
def initialize(theatre:, user:)
@theatre = theatre
@user = user
end
def call
return unless theatre.current_post
post = theatre.current_post
voters = TheatreSkipVote.where(theatre:, post:).includes(:user).map(&:user)
return if voters.empty?
event = TheatreSkipEvent.create!(
theatre:,
post:,
skipped_by_user: user,
programme_position: theatre.programmes.maximum(:position))
voters.uniq(&:id).each do |voter|
TheatreSkipEventVoter.create!(theatre_skip_event: event, user: voter)
end
post.tags.find_each do |tag|
TheatreSkipEventTag.create!(theatre_skip_event: event, tag:)
end
TheatreSkipVote.where(theatre:, post:).delete_all
event
end
private
attr_reader :theatre, :user
end