From 0b927290e33913ae055d7e05c4f3ab0f69b9204f Mon Sep 17 00:00:00 2001 From: Benjamin Neff Date: Sat, 2 Sep 2017 04:18:31 +0200 Subject: [PATCH] Add DiasporaUrlParser to extract diaspora:// URLs from texts --- lib/diaspora_federation/federation.rb | 1 + .../federation/diaspora_url_parser.rb | 29 ++++++++ .../federation/diaspora_url_parser_spec.rb | 68 +++++++++++++++++++ 3 files changed, 98 insertions(+) create mode 100644 lib/diaspora_federation/federation/diaspora_url_parser.rb create mode 100644 spec/lib/diaspora_federation/federation/diaspora_url_parser_spec.rb diff --git a/lib/diaspora_federation/federation.rb b/lib/diaspora_federation/federation.rb index a55eed4..b279cfd 100644 --- a/lib/diaspora_federation/federation.rb +++ b/lib/diaspora_federation/federation.rb @@ -4,6 +4,7 @@ module DiasporaFederation end end +require "diaspora_federation/federation/diaspora_url_parser" require "diaspora_federation/federation/fetcher" require "diaspora_federation/federation/receiver" require "diaspora_federation/federation/sender" diff --git a/lib/diaspora_federation/federation/diaspora_url_parser.rb b/lib/diaspora_federation/federation/diaspora_url_parser.rb new file mode 100644 index 0000000..3fb4300 --- /dev/null +++ b/lib/diaspora_federation/federation/diaspora_url_parser.rb @@ -0,0 +1,29 @@ +module DiasporaFederation + module Federation + # This module is for parsing and fetching linked entities. + module DiasporaUrlParser + include Logging + + # Regex to find diaspora:// URLs + DIASPORA_URL_REGEX = %r{diaspora://(#{Entity::ENTITY_NAME_REGEX})/(#{Validation::Rule::Guid::VALID_CHARS})} + + # Parses all diaspora:// URLs from the text and fetches the entities from + # the remote server if needed. + # @param [String] sender the diaspora* ID of the sender of the entity + # @param [String] text text with diaspora:// URLs to fetch + def self.fetch_linked_entities(sender, text) + text.scan(DIASPORA_URL_REGEX).each do |type, guid| + fetch_entity(sender, type, guid) + end + end + + private_class_method def self.fetch_entity(sender, type, guid) + class_name = Entity.entity_class(type).to_s.rpartition("::").last + return if DiasporaFederation.callbacks.trigger(:fetch_related_entity, class_name, guid) + Fetcher.fetch_public(sender, type, guid) + rescue => e + logger.error "Failed to fetch linked entity #{type}:#{guid}: #{e.class}: #{e.message}" + end + end + end +end diff --git a/spec/lib/diaspora_federation/federation/diaspora_url_parser_spec.rb b/spec/lib/diaspora_federation/federation/diaspora_url_parser_spec.rb new file mode 100644 index 0000000..bce2b2d --- /dev/null +++ b/spec/lib/diaspora_federation/federation/diaspora_url_parser_spec.rb @@ -0,0 +1,68 @@ +module DiasporaFederation + describe Federation::DiasporaUrlParser do + let(:sender) { Fabricate.sequence(:diaspora_id) } + let(:guid) { Fabricate.sequence(:guid) } + + describe ".fetch_linked_entities" do + it "parses linked posts from the text" do + guid2 = Fabricate.sequence(:guid) + guid3 = Fabricate.sequence(:guid) + expect_callback(:fetch_related_entity, "Post", guid).and_return(double) + expect_callback(:fetch_related_entity, "Post", guid2).and_return(double) + expect_callback(:fetch_related_entity, "Post", guid3).and_return(double) + + text = "This is a [link to a post with markdown](diaspora://post/#{guid}) and one without " \ + "diaspora://post/#{guid2} and finally a last one diaspora://post/#{guid3}." + + Federation::DiasporaUrlParser.fetch_linked_entities(sender, text) + end + + it "ignores invalid diaspora:// urls" do + expect(DiasporaFederation.callbacks).not_to receive(:trigger) + + text = "This is an invalid link diaspora://Post/#{guid}) and another one: " \ + "diaspora://post/abcd." + + Federation::DiasporaUrlParser.fetch_linked_entities(sender, text) + end + + it "allows to link other entities" do + expect_callback(:fetch_related_entity, "Event", guid).and_return(double) + + text = "This is a link to an event diaspora://event/#{guid}." + + Federation::DiasporaUrlParser.fetch_linked_entities(sender, text) + end + + it "handles unknown entities gracefully" do + expect(DiasporaFederation.callbacks).not_to receive(:trigger) + + text = "This is a link to an event diaspora://unknown/#{guid}." + + Federation::DiasporaUrlParser.fetch_linked_entities(sender, text) + end + + it "fetches entities from sender when not found locally" do + expect_callback(:fetch_related_entity, "Post", guid).and_return(nil) + expect(Federation::Fetcher).to receive(:fetch_public).with(sender, "post", guid) + + text = "This is a link to a post: diaspora://post/#{guid}." + + Federation::DiasporaUrlParser.fetch_linked_entities(sender, text) + end + + it "handles fetch errors gracefully" do + expect_callback(:fetch_related_entity, "Post", guid).and_return(nil) + expect(Federation::Fetcher).to receive(:fetch_public).with( + sender, "post", guid + ).and_raise(Federation::Fetcher::NotFetchable, "Something went wrong!") + + text = "This is a link to a post: diaspora://post/#{guid}." + + expect { + Federation::DiasporaUrlParser.fetch_linked_entities(sender, text) + }.not_to raise_error + end + end + end +end