From a9c440637ca9f36bcf051094abe3bcba1da63166 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 20 Apr 2018 02:28:48 +0200 Subject: Improve report layout (#7188) * Use table for statuses in report * Display reported account and reporter in the same table * Split accounts and general report info into two tables again * Redesign report statuses table, notes, merge notes and action log * Remove unused translations * Fix code style issue * Fix code style issue * Fix code style issue --- app/controllers/admin/reported_statuses_controller.rb | 14 ++++++++++++-- app/controllers/admin/reports_controller.rb | 8 ++++---- 2 files changed, 16 insertions(+), 6 deletions(-) (limited to 'app/controllers') diff --git a/app/controllers/admin/reported_statuses_controller.rb b/app/controllers/admin/reported_statuses_controller.rb index 535bd11d4..522f68c98 100644 --- a/app/controllers/admin/reported_statuses_controller.rb +++ b/app/controllers/admin/reported_statuses_controller.rb @@ -8,7 +8,7 @@ module Admin def create authorize :status, :update? - @form = Form::StatusBatch.new(form_status_batch_params.merge(current_account: current_account)) + @form = Form::StatusBatch.new(form_status_batch_params.merge(current_account: current_account, action: action_from_button)) flash[:alert] = I18n.t('admin.statuses.failed_to_execute') unless @form.save redirect_to admin_report_path(@report) @@ -35,7 +35,17 @@ module Admin end def form_status_batch_params - params.require(:form_status_batch).permit(:action, status_ids: []) + params.require(:form_status_batch).permit(status_ids: []) + end + + def action_from_button + if params[:nsfw_on] + 'nsfw_on' + elsif params[:nsfw_off] + 'nsfw_off' + elsif params[:delete] + 'delete' + end end def set_report diff --git a/app/controllers/admin/reports_controller.rb b/app/controllers/admin/reports_controller.rb index a4ae9507d..d00b3d222 100644 --- a/app/controllers/admin/reports_controller.rb +++ b/app/controllers/admin/reports_controller.rb @@ -11,10 +11,10 @@ module Admin def show authorize @report, :show? - @report_note = @report.notes.new - @report_notes = @report.notes.latest - @report_history = @report.history - @form = Form::StatusBatch.new + + @report_note = @report.notes.new + @report_notes = (@report.notes.latest + @report.history).sort_by(&:created_at) + @form = Form::StatusBatch.new end def update -- cgit From 87e3f0a41d2a2d223c663365a199c01989afc8ed Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Sat, 21 Apr 2018 01:14:31 +0900 Subject: Fix spec for sr-Latn (#7203) --- app/controllers/concerns/localized.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'app/controllers') diff --git a/app/controllers/concerns/localized.rb b/app/controllers/concerns/localized.rb index abd85ea27..145549bcd 100644 --- a/app/controllers/concerns/localized.rb +++ b/app/controllers/concerns/localized.rb @@ -29,10 +29,14 @@ module Localized end def preferred_locale - http_accept_language.preferred_language_from(I18n.available_locales) + http_accept_language.preferred_language_from(available_locales) end def compatible_locale - http_accept_language.compatible_language_from(I18n.available_locales) + http_accept_language.compatible_language_from(available_locales) + end + + def available_locales + I18n.available_locales.reverse end end -- cgit From 1258efa882b7a0eedc868640eb8e5a9075445ca0 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Tue, 24 Apr 2018 02:27:35 +0900 Subject: Paginate descendant statuses in public page (#7148) --- app/controllers/api/v1/statuses_controller.rb | 2 +- app/controllers/statuses_controller.rb | 73 +++++++++++++++++++++- app/models/concerns/status_threading_concern.rb | 17 ++--- app/views/stream_entries/_more.html.haml | 2 + app/views/stream_entries/_status.html.haml | 15 ++++- spec/controllers/statuses_controller_spec.rb | 43 +++++++++++++ .../concerns/status_threading_concern_spec.rb | 12 ++-- spec/views/stream_entries/show.html.haml_spec.rb | 4 +- 8 files changed, 146 insertions(+), 22 deletions(-) create mode 100644 app/views/stream_entries/_more.html.haml (limited to 'app/controllers') diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb index e98241323..01880565c 100644 --- a/app/controllers/api/v1/statuses_controller.rb +++ b/app/controllers/api/v1/statuses_controller.rb @@ -18,7 +18,7 @@ class Api::V1::StatusesController < Api::BaseController def context ancestors_results = @status.in_reply_to_id.nil? ? [] : @status.ancestors(DEFAULT_STATUSES_LIMIT, current_account) - descendants_results = @status.descendants(current_account) + descendants_results = @status.descendants(DEFAULT_STATUSES_LIMIT, current_account) loaded_ancestors = cache_collection(ancestors_results, Status) loaded_descendants = cache_collection(descendants_results, Status) diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb index a2943982a..01dac35e4 100644 --- a/app/controllers/statuses_controller.rb +++ b/app/controllers/statuses_controller.rb @@ -5,6 +5,8 @@ class StatusesController < ApplicationController include Authorization ANCESTORS_LIMIT = 20 + DESCENDANTS_LIMIT = 20 + DESCENDANTS_DEPTH_LIMIT = 4 layout 'public' @@ -19,9 +21,8 @@ class StatusesController < ApplicationController def show respond_to do |format| format.html do - @ancestors = @status.reply? ? cache_collection(@status.ancestors(ANCESTORS_LIMIT, current_account), Status) : [] - @next_ancestor = @ancestors.size < ANCESTORS_LIMIT ? nil : @ancestors.shift - @descendants = cache_collection(@status.descendants(current_account), Status) + set_ancestors + set_descendants render 'stream_entries/show' end @@ -51,10 +52,76 @@ class StatusesController < ApplicationController private + def create_descendant_thread(depth, statuses) + if depth < DESCENDANTS_DEPTH_LIMIT + { statuses: statuses } + else + next_status = statuses.pop + { statuses: statuses, next_status: next_status } + end + end + def set_account @account = Account.find_local!(params[:account_username]) end + def set_ancestors + @ancestors = @status.reply? ? cache_collection(@status.ancestors(ANCESTORS_LIMIT, current_account), Status) : [] + @next_ancestor = @ancestors.size < ANCESTORS_LIMIT ? nil : @ancestors.shift + end + + def set_descendants + @max_descendant_thread_id = params[:max_descendant_thread_id]&.to_i + @since_descendant_thread_id = params[:since_descendant_thread_id]&.to_i + + descendants = cache_collection( + @status.descendants( + DESCENDANTS_LIMIT, + current_account, + @max_descendant_thread_id, + @since_descendant_thread_id, + DESCENDANTS_DEPTH_LIMIT + ), + Status + ) + @descendant_threads = [] + + if descendants.present? + statuses = [descendants.first] + depth = 1 + + descendants.drop(1).each_with_index do |descendant, index| + if descendants[index].id == descendant.in_reply_to_id + depth += 1 + statuses << descendant + else + @descendant_threads << create_descendant_thread(depth, statuses) + + @descendant_threads.reverse_each do |descendant_thread| + statuses = descendant_thread[:statuses] + + index = statuses.find_index do |thread_status| + thread_status.id == descendant.in_reply_to_id + end + + if index.present? + depth += index - statuses.size + break + end + + depth -= statuses.size + end + + statuses = [descendant] + end + end + + @descendant_threads << create_descendant_thread(depth, statuses) + end + + @max_descendant_thread_id = @descendant_threads.pop[:statuses].first.id if descendants.size >= DESCENDANTS_LIMIT + end + def set_link_headers response.headers['Link'] = LinkHeader.new( [ diff --git a/app/models/concerns/status_threading_concern.rb b/app/models/concerns/status_threading_concern.rb index fffc095ee..a3fd34e94 100644 --- a/app/models/concerns/status_threading_concern.rb +++ b/app/models/concerns/status_threading_concern.rb @@ -7,8 +7,8 @@ module StatusThreadingConcern find_statuses_from_tree_path(ancestor_ids(limit), account) end - def descendants(account = nil) - find_statuses_from_tree_path(descendant_ids, account) + def descendants(limit, account = nil, max_child_id = nil, since_child_id = nil, depth = nil) + find_statuses_from_tree_path(descendant_ids(limit, max_child_id, since_child_id, depth), account) end private @@ -46,26 +46,27 @@ module StatusThreadingConcern SQL end - def descendant_ids - descendant_statuses.pluck(:id) + def descendant_ids(limit, max_child_id, since_child_id, depth) + descendant_statuses(limit, max_child_id, since_child_id, depth).pluck(:id) end - def descendant_statuses - Status.find_by_sql([<<-SQL.squish, id: id]) + def descendant_statuses(limit, max_child_id, since_child_id, depth) + Status.find_by_sql([<<-SQL.squish, id: id, limit: limit, max_child_id: max_child_id, since_child_id: since_child_id, depth: depth]) WITH RECURSIVE search_tree(id, path) AS ( SELECT id, ARRAY[id] FROM statuses - WHERE in_reply_to_id = :id + WHERE in_reply_to_id = :id AND COALESCE(id < :max_child_id, TRUE) AND COALESCE(id > :since_child_id, TRUE) UNION ALL SELECT statuses.id, path || statuses.id FROM search_tree JOIN statuses ON statuses.in_reply_to_id = search_tree.id - WHERE NOT statuses.id = ANY(path) + WHERE COALESCE(array_length(path, 1) < :depth, TRUE) AND NOT statuses.id = ANY(path) ) SELECT id FROM search_tree ORDER BY path + LIMIT :limit SQL end diff --git a/app/views/stream_entries/_more.html.haml b/app/views/stream_entries/_more.html.haml new file mode 100644 index 000000000..9b1dfe4a7 --- /dev/null +++ b/app/views/stream_entries/_more.html.haml @@ -0,0 +1,2 @@ += link_to url, class: 'more light' do + = t('statuses.show_more') diff --git a/app/views/stream_entries/_status.html.haml b/app/views/stream_entries/_status.html.haml index 2d0dafcb7..8decdf6b5 100644 --- a/app/views/stream_entries/_status.html.haml +++ b/app/views/stream_entries/_status.html.haml @@ -16,8 +16,7 @@ - if status.reply? && include_threads - if @next_ancestor .entry{ class: entry_classes } - = link_to short_account_status_url(@next_ancestor.account.username, @next_ancestor), class: 'more light' do - = t('statuses.show_more') + = render 'stream_entries/more', url: short_account_status_url(@next_ancestor.account.username, @next_ancestor) = render partial: 'stream_entries/status', collection: @ancestors, as: :status, locals: { is_predecessor: true, direct_reply_id: status.in_reply_to_id } .entry{ class: entry_classes } @@ -40,4 +39,14 @@ = render (centered ? 'stream_entries/detailed_status' : 'stream_entries/simple_status'), status: status.proper - if include_threads - = render partial: 'stream_entries/status', collection: @descendants, as: :status, locals: { is_successor: true, parent_id: status.id } + - if @since_descendant_thread_id + .entry{ class: entry_classes } + = render 'stream_entries/more', url: short_account_status_url(status.account.username, status, max_descendant_thread_id: @since_descendant_thread_id + 1) + - @descendant_threads.each do |thread| + = render partial: 'stream_entries/status', collection: thread[:statuses], as: :status, locals: { is_successor: true, parent_id: status.id } + - if thread[:next_status] + .entry{ class: entry_classes } + = render 'stream_entries/more', url: short_account_status_url(thread[:next_status].account.username, thread[:next_status]) + - if @next_descendant_thread + .entry{ class: entry_classes } + = render 'stream_entries/more', url: short_account_status_url(status.account.username, status, since_descendant_thread_id: @max_descendant_thread_id - 1) diff --git a/spec/controllers/statuses_controller_spec.rb b/spec/controllers/statuses_controller_spec.rb index 89af55688..b4f3c5a08 100644 --- a/spec/controllers/statuses_controller_spec.rb +++ b/spec/controllers/statuses_controller_spec.rb @@ -82,6 +82,49 @@ describe StatusesController do expect(assigns(:ancestors)).to eq [] end + it 'assigns @descendant_threads for a thread with several statuses' do + status = Fabricate(:status) + child = Fabricate(:status, in_reply_to_id: status.id) + grandchild = Fabricate(:status, in_reply_to_id: child.id) + + get :show, params: { account_username: status.account.username, id: status.id } + + expect(assigns(:descendant_threads)[0][:statuses].pluck(:id)).to eq [child.id, grandchild.id] + end + + it 'assigns @descendant_threads for several threads sharing the same descendant' do + status = Fabricate(:status) + child = Fabricate(:status, in_reply_to_id: status.id) + grandchildren = 2.times.map { Fabricate(:status, in_reply_to_id: child.id) } + + get :show, params: { account_username: status.account.username, id: status.id } + + expect(assigns(:descendant_threads)[0][:statuses].pluck(:id)).to eq [child.id, grandchildren[0].id] + expect(assigns(:descendant_threads)[1][:statuses].pluck(:id)).to eq [grandchildren[1].id] + end + + it 'assigns @max_descendant_thread_id for the last thread if it is hitting the status limit' do + stub_const 'StatusesController::DESCENDANTS_LIMIT', 1 + status = Fabricate(:status) + child = Fabricate(:status, in_reply_to_id: status.id) + + get :show, params: { account_username: status.account.username, id: status.id } + + expect(assigns(:descendant_threads)).to eq [] + expect(assigns(:max_descendant_thread_id)).to eq child.id + end + + it 'assigns @descendant_threads for threads with :next_status key if they are hitting the depth limit' do + stub_const 'StatusesController::DESCENDANTS_DEPTH_LIMIT', 1 + status = Fabricate(:status) + child = Fabricate(:status, in_reply_to_id: status.id) + + get :show, params: { account_username: status.account.username, id: status.id } + + expect(assigns(:descendant_threads)[0][:statuses].pluck(:id)).not_to include child.id + expect(assigns(:descendant_threads)[0][:next_status].id).to eq child.id + end + it 'returns a success' do status = Fabricate(:status) get :show, params: { account_username: status.account.username, id: status.id } diff --git a/spec/models/concerns/status_threading_concern_spec.rb b/spec/models/concerns/status_threading_concern_spec.rb index b8ebdd58c..e5736a307 100644 --- a/spec/models/concerns/status_threading_concern_spec.rb +++ b/spec/models/concerns/status_threading_concern_spec.rb @@ -89,34 +89,34 @@ describe StatusThreadingConcern do let!(:viewer) { Fabricate(:account, username: 'viewer') } it 'returns replies' do - expect(status.descendants).to include(reply1, reply2, reply3) + expect(status.descendants(4)).to include(reply1, reply2, reply3) end it 'does not return replies user is not allowed to see' do reply1.update(visibility: :private) reply3.update(visibility: :direct) - expect(status.descendants(viewer)).to_not include(reply1, reply3) + expect(status.descendants(4, viewer)).to_not include(reply1, reply3) end it 'does not return replies from blocked users' do viewer.block!(jeff) - expect(status.descendants(viewer)).to_not include(reply3) + expect(status.descendants(4, viewer)).to_not include(reply3) end it 'does not return replies from muted users' do viewer.mute!(jeff) - expect(status.descendants(viewer)).to_not include(reply3) + expect(status.descendants(4, viewer)).to_not include(reply3) end it 'does not return replies from silenced and not followed users' do jeff.update(silenced: true) - expect(status.descendants(viewer)).to_not include(reply3) + expect(status.descendants(4, viewer)).to_not include(reply3) end it 'does not return replies from blocked domains' do viewer.block_domain!('example.com') - expect(status.descendants(viewer)).to_not include(reply2) + expect(status.descendants(4, viewer)).to_not include(reply2) end end end diff --git a/spec/views/stream_entries/show.html.haml_spec.rb b/spec/views/stream_entries/show.html.haml_spec.rb index 6074bbc2e..560039ffa 100644 --- a/spec/views/stream_entries/show.html.haml_spec.rb +++ b/spec/views/stream_entries/show.html.haml_spec.rb @@ -24,6 +24,7 @@ describe 'stream_entries/show.html.haml', without_verify_partial_doubles: true d assign(:stream_entry, status.stream_entry) assign(:account, alice) assign(:type, status.stream_entry.activity_type.downcase) + assign(:descendant_threads, []) render @@ -49,7 +50,7 @@ describe 'stream_entries/show.html.haml', without_verify_partial_doubles: true d assign(:account, alice) assign(:type, reply.stream_entry.activity_type.downcase) assign(:ancestors, reply.stream_entry.activity.ancestors(1, bob) ) - assign(:descendants, reply.stream_entry.activity.descendants(bob)) + assign(:descendant_threads, [{ statuses: reply.stream_entry.activity.descendants(1)}]) render @@ -75,6 +76,7 @@ describe 'stream_entries/show.html.haml', without_verify_partial_doubles: true d assign(:stream_entry, status.stream_entry) assign(:account, alice) assign(:type, status.stream_entry.activity_type.downcase) + assign(:descendant_threads, []) render -- cgit From 9d4710ed0059b2f789e6b32b9f81d4ce90b98907 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 25 Apr 2018 02:10:02 +0200 Subject: Add RSS feeds for end-users (#7259) * Add RSS feed for accounts * Add RSS feeds for hashtags * Fix code style issues * Fix code style issues --- app/controllers/accounts_controller.rb | 10 ++- app/controllers/tags_controller.rb | 11 ++- app/helpers/stream_entries_helper.rb | 12 +-- app/lib/rss_builder.rb | 130 ++++++++++++++++++++++++++++++ app/serializers/rss/account_serializer.rb | 39 +++++++++ app/serializers/rss/tag_serializer.rb | 37 +++++++++ 6 files changed, 230 insertions(+), 9 deletions(-) create mode 100644 app/lib/rss_builder.rb create mode 100644 app/serializers/rss/account_serializer.rb create mode 100644 app/serializers/rss/tag_serializer.rb (limited to 'app/controllers') diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index 7bf35825f..1152d4aca 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -20,9 +20,10 @@ class AccountsController < ApplicationController @pinned_statuses = cache_collection(@account.pinned_statuses, Status) if show_pinned_statuses? @statuses = filtered_status_page(params) @statuses = cache_collection(@statuses, Status) + unless @statuses.empty? - @older_url = older_url if @statuses.last.id > filtered_statuses.last.id - @newer_url = newer_url if @statuses.first.id < filtered_statuses.first.id + @older_url = older_url if @statuses.last.id > filtered_statuses.last.id + @newer_url = newer_url if @statuses.first.id < filtered_statuses.first.id end end @@ -31,6 +32,11 @@ class AccountsController < ApplicationController render xml: OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.feed(@account, @entries.reject { |entry| entry.status.nil? })) end + format.rss do + @statuses = cache_collection(default_statuses.without_reblogs.without_replies.limit(PAGE_SIZE), Status) + render xml: RSS::AccountSerializer.render(@account, @statuses) + end + format.json do skip_session! diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb index 9f3090e37..014a5c9b8 100644 --- a/app/controllers/tags_controller.rb +++ b/app/controllers/tags_controller.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class TagsController < ApplicationController + PAGE_SIZE = 20 + before_action :set_body_classes before_action :set_instance_presenter @@ -13,8 +15,15 @@ class TagsController < ApplicationController @initial_state_json = serializable_resource.to_json end + format.rss do + @statuses = Status.as_tag_timeline(@tag).limit(PAGE_SIZE) + @statuses = cache_collection(@statuses, Status) + + render xml: RSS::TagSerializer.render(@tag, @statuses) + end + format.json do - @statuses = Status.as_tag_timeline(@tag, current_account, params[:local]).paginate_by_max_id(20, params[:max_id]) + @statuses = Status.as_tag_timeline(@tag, current_account, params[:local]).paginate_by_max_id(PAGE_SIZE, params[:max_id]) @statuses = cache_collection(@statuses, Status) render json: collection_presenter, diff --git a/app/helpers/stream_entries_helper.rb b/app/helpers/stream_entries_helper.rb index 8254ef4dc..c6f12ecd4 100644 --- a/app/helpers/stream_entries_helper.rb +++ b/app/helpers/stream_entries_helper.rb @@ -12,17 +12,17 @@ module StreamEntriesHelper prepend_str = [ [ number_to_human(account.statuses_count, strip_insignificant_zeros: true), - t('accounts.posts'), + I18n.t('accounts.posts'), ].join(' '), [ number_to_human(account.following_count, strip_insignificant_zeros: true), - t('accounts.following'), + I18n.t('accounts.following'), ].join(' '), [ number_to_human(account.followers_count, strip_insignificant_zeros: true), - t('accounts.followers'), + I18n.t('accounts.followers'), ].join(' '), ].join(', ') @@ -40,16 +40,16 @@ module StreamEntriesHelper end end - text = attachments.to_a.reject { |_, value| value.zero? }.map { |key, value| t("statuses.attached.#{key}", count: value) }.join(' · ') + text = attachments.to_a.reject { |_, value| value.zero? }.map { |key, value| I18n.t("statuses.attached.#{key}", count: value) }.join(' · ') return if text.blank? - t('statuses.attached.description', attached: text) + I18n.t('statuses.attached.description', attached: text) end def status_text_summary(status) return if status.spoiler_text.blank? - t('statuses.content_warning', warning: status.spoiler_text) + I18n.t('statuses.content_warning', warning: status.spoiler_text) end def status_description(status) diff --git a/app/lib/rss_builder.rb b/app/lib/rss_builder.rb new file mode 100644 index 000000000..63ddba2e8 --- /dev/null +++ b/app/lib/rss_builder.rb @@ -0,0 +1,130 @@ +# frozen_string_literal: true + +class RSSBuilder + class ItemBuilder + def initialize + @item = Ox::Element.new('item') + end + + def title(str) + @item << (Ox::Element.new('title') << str) + + self + end + + def link(str) + @item << Ox::Element.new('guid').tap do |guid| + guid['isPermalink'] = 'true' + guid << str + end + + @item << (Ox::Element.new('link') << str) + + self + end + + def pub_date(date) + @item << (Ox::Element.new('pubDate') << date.to_formatted_s(:rfc822)) + + self + end + + def description(str) + @item << (Ox::Element.new('description') << str) + + self + end + + def enclosure(url, type, size) + @item << Ox::Element.new('enclosure').tap do |enclosure| + enclosure['url'] = url + enclosure['length'] = size + enclosure['type'] = type + end + + self + end + + def to_element + @item + end + end + + def initialize + @document = Ox::Document.new(version: '1.0') + @channel = Ox::Element.new('channel') + + @document << (rss << @channel) + end + + def title(str) + @channel << (Ox::Element.new('title') << str) + + self + end + + def link(str) + @channel << (Ox::Element.new('link') << str) + + self + end + + def image(str) + @channel << Ox::Element.new('image').tap do |image| + image << (Ox::Element.new('url') << str) + image << (Ox::Element.new('title') << '') + image << (Ox::Element.new('link') << '') + end + + @channel << (Ox::Element.new('webfeeds:icon') << str) + + self + end + + def cover(str) + @channel << Ox::Element.new('webfeeds:cover').tap do |cover| + cover['image'] = str + end + + self + end + + def logo(str) + @channel << (Ox::Element.new('webfeeds:logo') << str) + + self + end + + def accent_color(str) + @channel << (Ox::Element.new('webfeeds:accentColor') << str) + + self + end + + def description(str) + @channel << (Ox::Element.new('description') << str) + + self + end + + def item + @channel << ItemBuilder.new.tap do |item| + yield item + end.to_element + + self + end + + def to_xml + ('' + Ox.dump(@document, effort: :tolerant)).force_encoding('UTF-8') + end + + private + + def rss + Ox::Element.new('rss').tap do |rss| + rss['version'] = '2.0' + rss['xmlns:webfeeds'] = 'http://webfeeds.org/rss/1.0' + end + end +end diff --git a/app/serializers/rss/account_serializer.rb b/app/serializers/rss/account_serializer.rb new file mode 100644 index 000000000..bde360a41 --- /dev/null +++ b/app/serializers/rss/account_serializer.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +class RSS::AccountSerializer + include ActionView::Helpers::NumberHelper + include StreamEntriesHelper + include RoutingHelper + + def render(account, statuses) + builder = RSSBuilder.new + + builder.title("#{display_name(account)} (@#{account.local_username_and_domain})") + .description(account_description(account)) + .link(TagManager.instance.url_for(account)) + .logo(full_asset_url(asset_pack_path('logo.svg'))) + .accent_color('2b90d9') + + builder.image(full_asset_url(account.avatar.url(:original))) if account.avatar? + builder.cover(full_asset_url(account.header.url(:original))) if account.header? + + statuses.each do |status| + builder.item do |item| + item.title(status.title) + .link(TagManager.instance.url_for(status)) + .pub_date(status.created_at) + .description(status.spoiler_text.presence || Formatter.instance.format(status).to_str) + + status.media_attachments.each do |media| + item.enclosure(full_asset_url(media.file.url(:original, false)), media.file.content_type, length: media.file.size) + end + end + end + + builder.to_xml + end + + def self.render(account, statuses) + new.render(account, statuses) + end +end diff --git a/app/serializers/rss/tag_serializer.rb b/app/serializers/rss/tag_serializer.rb new file mode 100644 index 000000000..7680a8da5 --- /dev/null +++ b/app/serializers/rss/tag_serializer.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +class RSS::TagSerializer + include ActionView::Helpers::NumberHelper + include ActionView::Helpers::SanitizeHelper + include StreamEntriesHelper + include RoutingHelper + + def render(tag, statuses) + builder = RSSBuilder.new + + builder.title("##{tag.name}") + .description(strip_tags(I18n.t('about.about_hashtag_html', hashtag: tag.name))) + .link(tag_url(tag)) + .logo(full_asset_url(asset_pack_path('logo.svg'))) + .accent_color('2b90d9') + + statuses.each do |status| + builder.item do |item| + item.title(status.title) + .link(TagManager.instance.url_for(status)) + .pub_date(status.created_at) + .description(status.spoiler_text.presence || Formatter.instance.format(status).to_str) + + status.media_attachments.each do |media| + item.enclosure(full_asset_url(media.file.url(:original, false)), media.file.content_type, length: media.file.size) + end + end + end + + builder.to_xml + end + + def self.render(tag, statuses) + new.render(tag, statuses) + end +end -- cgit From da61352fab2e59ba42caf59d4e2e33d62eebe060 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 30 Apr 2018 01:59:42 +0200 Subject: Fix "Show more" URL on paginated threads for remote statuses (#7285) * Fix URL of "Show more" link in paginated threads (ancestors side) Increase item limits in threads Fix #7268 * Fix "Show more" link in paginated threads (descendants side) --- app/controllers/statuses_controller.rb | 11 ++++++----- app/controllers/stream_entries_controller.rb | 1 + app/views/stream_entries/_status.html.haml | 20 +++++++++++--------- 3 files changed, 18 insertions(+), 14 deletions(-) (limited to 'app/controllers') diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb index 01dac35e4..645995c2a 100644 --- a/app/controllers/statuses_controller.rb +++ b/app/controllers/statuses_controller.rb @@ -4,9 +4,9 @@ class StatusesController < ApplicationController include SignatureAuthentication include Authorization - ANCESTORS_LIMIT = 20 - DESCENDANTS_LIMIT = 20 - DESCENDANTS_DEPTH_LIMIT = 4 + ANCESTORS_LIMIT = 40 + DESCENDANTS_LIMIT = 60 + DESCENDANTS_DEPTH_LIMIT = 20 layout 'public' @@ -71,7 +71,7 @@ class StatusesController < ApplicationController end def set_descendants - @max_descendant_thread_id = params[:max_descendant_thread_id]&.to_i + @max_descendant_thread_id = params[:max_descendant_thread_id]&.to_i @since_descendant_thread_id = params[:since_descendant_thread_id]&.to_i descendants = cache_collection( @@ -84,11 +84,12 @@ class StatusesController < ApplicationController ), Status ) + @descendant_threads = [] if descendants.present? statuses = [descendants.first] - depth = 1 + depth = 1 descendants.drop(1).each_with_index do |descendant, index| if descendants[index].id == descendant.in_reply_to_id diff --git a/app/controllers/stream_entries_controller.rb b/app/controllers/stream_entries_controller.rb index 97cf85079..8568b151c 100644 --- a/app/controllers/stream_entries_controller.rb +++ b/app/controllers/stream_entries_controller.rb @@ -23,6 +23,7 @@ class StreamEntriesController < ApplicationController skip_session! expires_in 3.minutes, public: true end + render xml: OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.entry(@stream_entry, true)) end end diff --git a/app/views/stream_entries/_status.html.haml b/app/views/stream_entries/_status.html.haml index 8decdf6b5..9764bc74d 100644 --- a/app/views/stream_entries/_status.html.haml +++ b/app/views/stream_entries/_status.html.haml @@ -5,18 +5,19 @@ is_successor ||= false direct_reply_id ||= false parent_id ||= false - is_direct_parent = direct_reply_id == status.id - is_direct_child = parent_id == status.in_reply_to_id - centered ||= include_threads && !is_predecessor && !is_successor - h_class = microformats_h_class(status, is_predecessor, is_successor, include_threads) - style_classes = style_classes(status, is_predecessor, is_successor, include_threads) - mf_classes = microformats_classes(status, is_direct_parent, is_direct_child) - entry_classes = h_class + ' ' + mf_classes + ' ' + style_classes + is_direct_parent = direct_reply_id == status.id + is_direct_child = parent_id == status.in_reply_to_id + centered ||= include_threads && !is_predecessor && !is_successor + h_class = microformats_h_class(status, is_predecessor, is_successor, include_threads) + style_classes = style_classes(status, is_predecessor, is_successor, include_threads) + mf_classes = microformats_classes(status, is_direct_parent, is_direct_child) + entry_classes = h_class + ' ' + mf_classes + ' ' + style_classes - if status.reply? && include_threads - if @next_ancestor .entry{ class: entry_classes } - = render 'stream_entries/more', url: short_account_status_url(@next_ancestor.account.username, @next_ancestor) + = render 'stream_entries/more', url: TagManager.instance.url_for(@next_ancestor) + = render partial: 'stream_entries/status', collection: @ancestors, as: :status, locals: { is_predecessor: true, direct_reply_id: status.in_reply_to_id } .entry{ class: entry_classes } @@ -44,9 +45,10 @@ = render 'stream_entries/more', url: short_account_status_url(status.account.username, status, max_descendant_thread_id: @since_descendant_thread_id + 1) - @descendant_threads.each do |thread| = render partial: 'stream_entries/status', collection: thread[:statuses], as: :status, locals: { is_successor: true, parent_id: status.id } + - if thread[:next_status] .entry{ class: entry_classes } - = render 'stream_entries/more', url: short_account_status_url(thread[:next_status].account.username, thread[:next_status]) + = render 'stream_entries/more', url: TagManager.instance.url_for(thread[:next_status]) - if @next_descendant_thread .entry{ class: entry_classes } = render 'stream_entries/more', url: short_account_status_url(status.account.username, status, since_descendant_thread_id: @max_descendant_thread_id - 1) -- cgit From 54f34d3f2ab4dbe3a4b572d60af18a4b49358ec6 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 30 Apr 2018 09:12:36 +0200 Subject: Return HTTP 410 for suspended accounts in GET /api/v1/accounts/:id (#7287) Fix #7243 --- app/controllers/api/v1/accounts_controller.rb | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'app/controllers') diff --git a/app/controllers/api/v1/accounts_controller.rb b/app/controllers/api/v1/accounts_controller.rb index d64325944..b7133ca8e 100644 --- a/app/controllers/api/v1/accounts_controller.rb +++ b/app/controllers/api/v1/accounts_controller.rb @@ -5,6 +5,7 @@ class Api::V1::AccountsController < Api::BaseController before_action -> { doorkeeper_authorize! :follow }, only: [:follow, :unfollow, :block, :unblock, :mute, :unmute] before_action :require_user!, except: [:show] before_action :set_account + before_action :check_account_suspension, only: [:show] respond_to :json @@ -54,4 +55,8 @@ class Api::V1::AccountsController < Api::BaseController def relationships(**options) AccountRelationshipsPresenter.new([@account.id], current_user.account_id, options) end + + def check_account_suspension + gone if @account.suspended? + end end -- cgit From f62ee1ddb0364d749e9df5559a243ebe3570cd2a Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 30 Apr 2018 09:13:14 +0200 Subject: Disable API access when login is disabled (#7289) --- app/controllers/api/base_controller.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'app/controllers') diff --git a/app/controllers/api/base_controller.rb b/app/controllers/api/base_controller.rb index 7b5168b31..b5c084e14 100644 --- a/app/controllers/api/base_controller.rb +++ b/app/controllers/api/base_controller.rb @@ -66,8 +66,10 @@ class Api::BaseController < ApplicationController end def require_user! - if current_user + if current_user && !current_user.disabled? set_user_activity + elsif current_user + render json: { error: 'Your login is currently disabled' }, status: 403 else render json: { error: 'This method requires an authenticated user' }, status: 422 end -- cgit From cae933510cbc64db27aeb44e205ce17ff4974da7 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 2 May 2018 15:57:37 +0200 Subject: Allow updating bio fields via PUT /api/v1/accounts/update_credentials (#7288) Add raw bio fields to the source attribute on GET /api/v1/accounts/verify_credentials --- app/controllers/api/v1/accounts/credentials_controller.rb | 2 +- app/models/account.rb | 4 ++++ app/serializers/rest/credential_account_serializer.rb | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) (limited to 'app/controllers') diff --git a/app/controllers/api/v1/accounts/credentials_controller.rb b/app/controllers/api/v1/accounts/credentials_controller.rb index 062d490a7..a3c4008e6 100644 --- a/app/controllers/api/v1/accounts/credentials_controller.rb +++ b/app/controllers/api/v1/accounts/credentials_controller.rb @@ -21,7 +21,7 @@ class Api::V1::Accounts::CredentialsController < Api::BaseController private def account_params - params.permit(:display_name, :note, :avatar, :header, :locked) + params.permit(:display_name, :note, :avatar, :header, :locked, fields_attributes: [:name, :value]) end def user_settings_params diff --git a/app/models/account.rb b/app/models/account.rb index 0cd2a10d5..a166fb474 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -273,6 +273,10 @@ class Account < ApplicationRecord @value = attr['value'] @errors = {} end + + def to_h + { name: @name, value: @value } + end end class << self diff --git a/app/serializers/rest/credential_account_serializer.rb b/app/serializers/rest/credential_account_serializer.rb index 870d8b71f..56857cba8 100644 --- a/app/serializers/rest/credential_account_serializer.rb +++ b/app/serializers/rest/credential_account_serializer.rb @@ -5,10 +5,12 @@ class REST::CredentialAccountSerializer < REST::AccountSerializer def source user = object.user + { privacy: user.setting_default_privacy, sensitive: user.setting_default_sensitive, note: object.note, + fields: object.fields.map(&:to_h), } end end -- cgit From cb5b5cb5f79bb2187d8124df91af4c8e1bfd7256 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 2 May 2018 18:58:48 +0200 Subject: Slightly reduce RAM usage (#7301) * No need to re-require sidekiq plugins, they are required via Gemfile * Add derailed_benchmarks tool, no need to require TTY gems in Gemfile * Replace ruby-oembed with FetchOEmbedService Reduce startup by 45382 allocated objects * Remove preloaded JSON-LD in favour of caching HTTP responses Reduce boot RAM by about 6 MiB * Fix tests * Fix test suite by stubbing out JSON-LD contexts --- Gemfile | 12 +- Gemfile.lock | 22 +- app/controllers/api/web/embeds_controller.rb | 11 +- .../settings/follower_domains_controller.rb | 2 - app/helpers/jsonld_helper.rb | 17 +- app/lib/provider_discovery.rb | 47 --- app/services/fan_out_on_write_service.rb | 2 - app/services/fetch_link_card_service.rb | 38 +- app/services/fetch_oembed_service.rb | 71 ++++ app/workers/scheduler/backup_cleanup_scheduler.rb | 1 - .../scheduler/doorkeeper_cleanup_scheduler.rb | 1 - app/workers/scheduler/email_scheduler.rb | 1 - app/workers/scheduler/feed_cleanup_scheduler.rb | 1 - app/workers/scheduler/ip_cleanup_scheduler.rb | 1 - app/workers/scheduler/media_cleanup_scheduler.rb | 1 - .../scheduler/subscriptions_cleanup_scheduler.rb | 2 - app/workers/scheduler/subscriptions_scheduler.rb | 3 - app/workers/scheduler/user_cleanup_scheduler.rb | 1 - app/workers/soft_block_domain_followers_worker.rb | 2 - config/initializers/json_ld.rb | 5 - config/initializers/oembed.rb | 4 - lib/json_ld/activitystreams.rb | 153 -------- lib/json_ld/identity.rb | 86 ----- lib/json_ld/security.rb | 50 --- lib/tasks/mastodon.rake | 2 + spec/fixtures/requests/json-ld.activitystreams.txt | 391 +++++++++++++++++++++ spec/fixtures/requests/json-ld.identity.txt | 100 ++++++ spec/fixtures/requests/json-ld.security.txt | 61 ++++ spec/lib/activitypub/linked_data_signature_spec.rb | 4 + spec/lib/provider_discovery_spec.rb | 118 ------- spec/rails_helper.rb | 14 + spec/services/account_search_service_spec.rb | 2 +- .../fetch_remote_account_service_spec.rb | 2 +- .../fetch_remote_status_service_spec.rb | 2 +- .../activitypub/process_account_service_spec.rb | 2 +- .../activitypub/process_collection_service_spec.rb | 2 +- spec/services/after_block_service_spec.rb | 2 +- spec/services/authorize_follow_service_spec.rb | 2 +- .../services/batched_remove_status_service_spec.rb | 2 +- .../block_domain_from_account_service_spec.rb | 2 +- spec/services/block_domain_service_spec.rb | 2 +- spec/services/block_service_spec.rb | 2 +- spec/services/bootstrap_timeline_service_spec.rb | 2 +- spec/services/fan_out_on_write_service_spec.rb | 2 +- spec/services/favourite_service_spec.rb | 2 +- spec/services/fetch_atom_service_spec.rb | 2 +- spec/services/fetch_link_card_service_spec.rb | 2 +- spec/services/fetch_oembed_service_spec.rb | 125 +++++++ spec/services/fetch_remote_account_service_spec.rb | 2 +- spec/services/fetch_remote_status_service_spec.rb | 2 +- spec/services/follow_service_spec.rb | 2 +- spec/services/mute_service_spec.rb | 2 +- spec/services/notify_service_spec.rb | 2 +- spec/services/post_status_service_spec.rb | 2 +- spec/services/precompute_feed_service_spec.rb | 2 +- spec/services/process_feed_service_spec.rb | 2 +- spec/services/process_interaction_service_spec.rb | 2 +- spec/services/process_mentions_service_spec.rb | 2 +- .../pubsubhubbub/subscribe_service_spec.rb | 2 +- .../pubsubhubbub/unsubscribe_service_spec.rb | 2 +- spec/services/reblog_service_spec.rb | 2 +- spec/services/reject_follow_service_spec.rb | 2 +- spec/services/remove_status_service_spec.rb | 2 +- spec/services/report_service_spec.rb | 2 +- spec/services/resolve_account_service_spec.rb | 2 +- spec/services/resolve_url_service_spec.rb | 2 +- spec/services/search_service_spec.rb | 2 +- spec/services/send_interaction_service_spec.rb | 2 +- spec/services/subscribe_service_spec.rb | 2 +- spec/services/suspend_account_service_spec.rb | 2 +- spec/services/unblock_domain_service_spec.rb | 2 +- spec/services/unblock_service_spec.rb | 2 +- spec/services/unfollow_service_spec.rb | 2 +- spec/services/unmute_service_spec.rb | 2 +- spec/services/unsubscribe_service_spec.rb | 2 +- .../services/update_remote_profile_service_spec.rb | 2 +- spec/spec_helper.rb | 12 +- 77 files changed, 881 insertions(+), 568 deletions(-) delete mode 100644 app/lib/provider_discovery.rb create mode 100644 app/services/fetch_oembed_service.rb delete mode 100644 config/initializers/json_ld.rb delete mode 100644 config/initializers/oembed.rb delete mode 100644 lib/json_ld/activitystreams.rb delete mode 100644 lib/json_ld/identity.rb delete mode 100644 lib/json_ld/security.rb create mode 100644 spec/fixtures/requests/json-ld.activitystreams.txt create mode 100644 spec/fixtures/requests/json-ld.identity.txt create mode 100644 spec/fixtures/requests/json-ld.security.txt delete mode 100644 spec/lib/provider_discovery_spec.rb create mode 100644 spec/services/fetch_oembed_service_spec.rb (limited to 'app/controllers') diff --git a/Gemfile b/Gemfile index a33748568..f1665ce95 100644 --- a/Gemfile +++ b/Gemfile @@ -54,7 +54,7 @@ gem 'httplog', '~> 1.0' gem 'idn-ruby', require: 'idn' gem 'kaminari', '~> 1.1' gem 'link_header', '~> 0.0' -gem 'mime-types', '~> 3.1' +gem 'mime-types', '~> 3.1', require: 'mime/types/columnar' gem 'nokogiri', '~> 1.8' gem 'nsa', '~> 0.2' gem 'oj', '~> 3.5' @@ -70,7 +70,6 @@ gem 'rails-settings-cached', '~> 0.6' gem 'redis', '~> 4.0', require: ['redis', 'redis/connection/hiredis'] gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock' gem 'rqrcode', '~> 0.10' -gem 'ruby-oembed', '~> 0.12', require: 'oembed' gem 'ruby-progressbar', '~> 1.4' gem 'sanitize', '~> 4.6' gem 'sidekiq', '~> 5.1' @@ -82,14 +81,14 @@ gem 'simple_form', '~> 4.0' gem 'sprockets-rails', '~> 3.2', require: 'sprockets/railtie' gem 'stoplight', '~> 2.1.3' gem 'strong_migrations', '~> 0.2' -gem 'tty-command', '~> 0.8' -gem 'tty-prompt', '~> 0.16' +gem 'tty-command', '~> 0.8', require: false +gem 'tty-prompt', '~> 0.16', require: false gem 'twitter-text', '~> 1.14' gem 'tzinfo-data', '~> 1.2018' gem 'webpacker', '~> 3.4' gem 'webpush' -gem 'json-ld-preloaded', '~> 2.2' +gem 'json-ld', '~> 2.2' gem 'rdf-normalize', '~> 0.3' group :development, :test do @@ -135,6 +134,9 @@ group :development do gem 'capistrano-rails', '~> 1.3' gem 'capistrano-rbenv', '~> 2.1' gem 'capistrano-yarn', '~> 2.0' + + gem 'derailed_benchmarks' + gem 'stackprof' end group :production do diff --git a/Gemfile.lock b/Gemfile.lock index d96165dcf..94ab0b7ca 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -75,6 +75,7 @@ GEM aws-sigv4 (~> 1.0) aws-sigv4 (1.0.2) bcrypt (3.1.11) + benchmark-ips (2.7.2) better_errors (2.4.0) coderay (>= 1.0.0) erubi (>= 1.0.0) @@ -138,6 +139,14 @@ GEM css_parser (1.6.0) addressable debug_inspector (0.0.3) + derailed_benchmarks (1.3.4) + benchmark-ips (~> 2) + get_process_mem (~> 0) + heapy (~> 0) + memory_profiler (~> 0) + rack (>= 1) + rake (> 10, < 13) + thor (~> 0.19) devise (4.4.3) bcrypt (~> 3.0) orm_adapter (~> 0.1) @@ -206,6 +215,7 @@ GEM fuubar (2.3.1) rspec-core (~> 3.0) ruby-progressbar (~> 1.4) + get_process_mem (0.2.1) globalid (0.4.1) activesupport (>= 4.2.0) goldfinger (2.1.0) @@ -226,6 +236,7 @@ GEM concurrent-ruby (~> 1.0) hashdiff (0.3.7) hashie (3.5.7) + heapy (0.1.3) highline (1.7.10) hiredis (0.6.1) hitimes (1.2.6) @@ -264,10 +275,6 @@ GEM json-ld (2.2.1) multi_json (~> 1.12) rdf (>= 2.2.8, < 4.0) - json-ld-preloaded (2.2.3) - json-ld (>= 2.2, < 4.0) - multi_json (~> 1.12) - rdf (>= 2.2, < 4.0) jsonapi-renderer (0.2.0) jwt (2.1.0) kaminari (1.1.1) @@ -502,7 +509,6 @@ GEM rainbow (>= 2.2.2, < 4.0) ruby-progressbar (~> 1.7) unicode-display_width (~> 1.0, >= 1.0.1) - ruby-oembed (0.12.0) ruby-progressbar (1.9.0) ruby-saml (1.7.2) nokogiri (>= 1.5.10) @@ -557,6 +563,7 @@ GEM sshkit (1.16.0) net-scp (>= 1.1.2) net-ssh (>= 2.8.0) + stackprof (0.2.11) statsd-ruby (1.2.1) stoplight (2.1.3) streamio-ffmpeg (3.0.2) @@ -645,6 +652,7 @@ DEPENDENCIES chewy (~> 5.0) cld3 (~> 3.2.0) climate_control (~> 0.2) + derailed_benchmarks devise (~> 4.4) devise-two-factor (~> 3.0) devise_pam_authenticatable2 (~> 9.1) @@ -668,7 +676,7 @@ DEPENDENCIES i18n-tasks (~> 0.9) idn-ruby iso-639 - json-ld-preloaded (~> 2.2) + json-ld (~> 2.2) kaminari (~> 1.1) letter_opener (~> 1.4) letter_opener_web (~> 1.3) @@ -714,7 +722,6 @@ DEPENDENCIES rspec-retry (~> 0.5) rspec-sidekiq (~> 3.0) rubocop (~> 0.55) - ruby-oembed (~> 0.12) ruby-progressbar (~> 1.4) sanitize (~> 4.6) scss_lint (~> 0.57) @@ -726,6 +733,7 @@ DEPENDENCIES simple_form (~> 4.0) simplecov (~> 0.16) sprockets-rails (~> 3.2) + stackprof stoplight (~> 2.1.3) streamio-ffmpeg (~> 3.0) strong_migrations (~> 0.2) diff --git a/app/controllers/api/web/embeds_controller.rb b/app/controllers/api/web/embeds_controller.rb index f2fe74b17..987290a14 100644 --- a/app/controllers/api/web/embeds_controller.rb +++ b/app/controllers/api/web/embeds_controller.rb @@ -9,9 +9,12 @@ class Api::Web::EmbedsController < Api::Web::BaseController status = StatusFinder.new(params[:url]).status render json: status, serializer: OEmbedSerializer, width: 400 rescue ActiveRecord::RecordNotFound - oembed = OEmbed::Providers.get(params[:url]) - render json: Oj.dump(oembed.fields) - rescue OEmbed::NotFound - render json: {}, status: :not_found + oembed = FetchOEmbedService.new.call(params[:url]) + + if oembed + render json: oembed + else + render json: {}, status: :not_found + end end end diff --git a/app/controllers/settings/follower_domains_controller.rb b/app/controllers/settings/follower_domains_controller.rb index 213d9e96d..91b521e7f 100644 --- a/app/controllers/settings/follower_domains_controller.rb +++ b/app/controllers/settings/follower_domains_controller.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require 'sidekiq-bulk' - class Settings::FollowerDomainsController < ApplicationController layout 'admin' diff --git a/app/helpers/jsonld_helper.rb b/app/helpers/jsonld_helper.rb index a3cfdadb8..e9056166c 100644 --- a/app/helpers/jsonld_helper.rb +++ b/app/helpers/jsonld_helper.rb @@ -48,7 +48,7 @@ module JsonLdHelper end def canonicalize(json) - graph = RDF::Graph.new << JSON::LD::API.toRdf(json) + graph = RDF::Graph.new << JSON::LD::API.toRdf(json, documentLoader: method(:load_jsonld_context)) graph.dump(:normalize) end @@ -90,4 +90,19 @@ module JsonLdHelper request.add_headers('Accept' => 'application/activity+json, application/ld+json') request end + + def load_jsonld_context(url, _options = {}, &_block) + json = Rails.cache.fetch("jsonld:context:#{url}", expires_in: 30.days, raw: true) do + request = Request.new(:get, url) + request.add_headers('Accept' => 'application/ld+json') + + request.perform do |res| + raise JSON::LD::JsonLdError::LoadingDocumentFailed unless res.code == 200 && res.mime_type == 'application/ld+json' + res.body_with_limit + end + end + + doc = JSON::LD::API::RemoteDocument.new(url, json) + block_given? ? yield(doc) : doc + end end diff --git a/app/lib/provider_discovery.rb b/app/lib/provider_discovery.rb deleted file mode 100644 index 3bec7211b..000000000 --- a/app/lib/provider_discovery.rb +++ /dev/null @@ -1,47 +0,0 @@ -# frozen_string_literal: true - -class ProviderDiscovery < OEmbed::ProviderDiscovery - class << self - def get(url, **options) - provider = discover_provider(url, options) - - options.delete(:html) - - provider.get(url, options) - end - - def discover_provider(url, **options) - format = options[:format] - - html = if options[:html] - Nokogiri::HTML(options[:html]) - else - Request.new(:get, url).perform do |res| - raise OEmbed::NotFound, url if res.code != 200 || res.mime_type != 'text/html' - Nokogiri::HTML(res.body_with_limit) - end - end - - if format.nil? || format == :json - provider_endpoint ||= html.at_xpath('//link[@type="application/json+oembed"]')&.attribute('href')&.value - format ||= :json if provider_endpoint - end - - if format.nil? || format == :xml - provider_endpoint ||= html.at_xpath('//link[@type="text/xml+oembed"]')&.attribute('href')&.value - format ||= :xml if provider_endpoint - end - - raise OEmbed::NotFound, url if provider_endpoint.nil? - begin - provider_endpoint = Addressable::URI.parse(provider_endpoint) - provider_endpoint.query = nil - provider_endpoint = provider_endpoint.to_s - rescue Addressable::URI::InvalidURIError - raise OEmbed::NotFound, url - end - - OEmbed::Provider.new(provider_endpoint, format) - end - end -end diff --git a/app/services/fan_out_on_write_service.rb b/app/services/fan_out_on_write_service.rb index 0f77556dc..510b80c82 100644 --- a/app/services/fan_out_on_write_service.rb +++ b/app/services/fan_out_on_write_service.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require 'sidekiq-bulk' - class FanOutOnWriteService < BaseService # Push a status into home and mentions feeds # @param [Status] status diff --git a/app/services/fetch_link_card_service.rb b/app/services/fetch_link_card_service.rb index d5920a417..77d4aa538 100644 --- a/app/services/fetch_link_card_service.rb +++ b/app/services/fetch_link_card_service.rb @@ -85,42 +85,40 @@ class FetchLinkCardService < BaseService end def attempt_oembed - embed = OEmbed::Providers.get(@url, html: @html) + embed = FetchOEmbedService.new.call(@url, html: @html) - return false unless embed.respond_to?(:type) + return false if embed.nil? - @card.type = embed.type - @card.title = embed.respond_to?(:title) ? embed.title : '' - @card.author_name = embed.respond_to?(:author_name) ? embed.author_name : '' - @card.author_url = embed.respond_to?(:author_url) ? embed.author_url : '' - @card.provider_name = embed.respond_to?(:provider_name) ? embed.provider_name : '' - @card.provider_url = embed.respond_to?(:provider_url) ? embed.provider_url : '' + @card.type = embed[:type] + @card.title = embed[:title] || '' + @card.author_name = embed[:author_name] || '' + @card.author_url = embed[:author_url] || '' + @card.provider_name = embed[:provider_name] || '' + @card.provider_url = embed[:provider_url] || '' @card.width = 0 @card.height = 0 case @card.type when 'link' - @card.image_remote_url = embed.thumbnail_url if embed.respond_to?(:thumbnail_url) + @card.image_remote_url = embed[:thumbnail_url] if embed[:thumbnail_url].present? when 'photo' - return false unless embed.respond_to?(:url) + return false if embed[:url].blank? - @card.embed_url = embed.url - @card.image_remote_url = embed.url - @card.width = embed.width.presence || 0 - @card.height = embed.height.presence || 0 + @card.embed_url = embed[:url] + @card.image_remote_url = embed[:url] + @card.width = embed[:width].presence || 0 + @card.height = embed[:height].presence || 0 when 'video' - @card.width = embed.width.presence || 0 - @card.height = embed.height.presence || 0 - @card.html = Formatter.instance.sanitize(embed.html, Sanitize::Config::MASTODON_OEMBED) - @card.image_remote_url = embed.thumbnail_url if embed.respond_to?(:thumbnail_url) + @card.width = embed[:width].presence || 0 + @card.height = embed[:height].presence || 0 + @card.html = Formatter.instance.sanitize(embed[:html], Sanitize::Config::MASTODON_OEMBED) + @card.image_remote_url = embed[:thumbnail_url] if embed[:thumbnail_url].present? when 'rich' # Most providers rely on