diff --git a/docs/_entities/embed.md b/docs/_entities/embed.md new file mode 100644 index 0000000..b9307af --- /dev/null +++ b/docs/_entities/embed.md @@ -0,0 +1,69 @@ +--- +title: Embed +--- + +This entity represents the embed information about an URL that should be +embedded, it is nested in a [StatusMessage][status_message]. To embed a URL +means to keep an embedded representation or a preview of a third party +resource referenced by the URL inside the status message. + +* If this entity is present, the receiving server should only embed the included +`url` and not search for other URLs to embed. +* If the included `url` is a +trusted oEmbed provider, the server should query the oEmbed data. +* If `title`, `description` or `image` are missing, the server should query the +information from the URL (oEmbed or OpenGraph). +* If `nothing` is `true` the server should not embed any URLs. + +A link to the embedded resource should also be included in the `text` of the +[StatusMessage][status_message] for accessibility reasons, otherwise it could +happen that some people don't see the link, for example when this entity isn't +implemented or where no embeds are supported at all. However, it is possible +that the link in the `text` and the `url` here are different, because some sites +have different URLs in `og:url` as requested. + +## Optional Properties + +All properties are optional, but either `url` is required or `nothing` must be `true`. + +| Property | Type (Length) | Description | +| ------------- | ------------------------ | ------------------------------------- | +| `url` | [URL][url] (65535) | The URL that should be embedded. | +| `title` | [String][string] (255) | The title of the embedded URL. | +| `description` | [String][string] (65535) | The description of the embedded URL. | +| `image` | [URL][url] (65535) | The image of the embedded URL. | +| `nothing` | [Boolean][boolean] | `true` if nothing should be embedded. | + +## Example + +### Only `url` + +~~~xml + + https://example.org/ + +~~~ + +### With metadata + +~~~xml + + https://example.org/ + Example Website + This is an example! + https://example.org/example.png + +~~~ + +### With `nothing` + +~~~xml + + true + +~~~ + +[string]: {{ site.baseurl }}/federation/types.html#string +[url]: {{ site.baseurl }}/federation/types.html#url +[boolean]: {{ site.baseurl }}/federation/types.html#url +[status_message]: {{ site.baseurl }}/entities/status_message.html diff --git a/docs/_entities/status_message.md b/docs/_entities/status_message.md index 6bb1fb7..3e2f8b2 100644 --- a/docs/_entities/status_message.md +++ b/docs/_entities/status_message.md @@ -24,6 +24,7 @@ This entity represents a reshare of a status message. It inherits from [Post][po | `photo` | [Photo][photo]s | ✔ | The attached Photos of the status message, the `status_message_guid` and the `author` need to match the status message. | | `poll` | [Poll][poll] | ✘ | The attached Poll of the status message. | | `event` | [Event][event] | ✘ | The attached Event of the status message. | +| `embed` | [Embed][embed] | ✔ | The Embed information of an URL that should be embedded in the status message. | ## Examples @@ -47,12 +48,12 @@ This entity represents a reshare of a status message. It inherits from [Post][po c3893bf029e7013487753131731751e9 2016-07-11T22:50:18Z I am a very interesting status update + true
Vienna, Austria
48.208174 16.373819
- true ~~~ @@ -64,6 +65,7 @@ This entity represents a reshare of a status message. It inherits from [Post][po e05828d029e7013487753131731751e9 2016-07-11T22:52:56Z I am a very interesting status update + true 0788070029e8013487753131731751e9 alice@example.org @@ -86,7 +88,6 @@ This entity represents a reshare of a status message. It inherits from [Post][po 480 800 - true ~~~ @@ -99,6 +100,7 @@ This entity represents a reshare of a status message. It inherits from [Post][po 378473f029e9013487753131731751e9 2016-07-11T23:00:42Z I am a very interesting status update + true 2a22d6c029e9013487753131731751e9 Select an answer @@ -115,7 +117,24 @@ This entity represents a reshare of a status message. It inherits from [Post][po Maybe + +~~~ + +### With [Embed][embed] + +~~~xml + + alice@example.org + 378473f029e9013487753131731751e9 + 2016-07-11T23:00:42Z + I am a very interesting status update true + + https://example.org/ + Example Website + This is an example! + https://example.org/example.png + ~~~ @@ -128,6 +147,7 @@ This entity represents a reshare of a status message. It inherits from [Post][po 2016-07-11T23:02:24Z mobile i am a very interesting status update + true 0788070029e8013487753131731751e9 alice@example.org @@ -171,7 +191,12 @@ This entity represents a reshare of a status message. It inherits from [Post][po Maybe - true + + https://example.org/ + Example Website + This is an example! + https://example.org/example.png + ~~~ @@ -186,3 +211,4 @@ This entity represents a reshare of a status message. It inherits from [Post][po [photo]: {{ site.baseurl }}/entities/photo.html [poll]: {{ site.baseurl }}/entities/poll.html [event]: {{ site.baseurl }}/entities/event.html +[embed]: {{ site.baseurl }}/entities/embed.html diff --git a/lib/diaspora_federation/entities.rb b/lib/diaspora_federation/entities.rb index 66bb1aa..a7689d0 100644 --- a/lib/diaspora_federation/entities.rb +++ b/lib/diaspora_federation/entities.rb @@ -30,6 +30,7 @@ require "diaspora_federation/entities/poll_answer" require "diaspora_federation/entities/poll" require "diaspora_federation/entities/poll_participation" +require "diaspora_federation/entities/embed" require "diaspora_federation/entities/location" require "diaspora_federation/entities/event" diff --git a/lib/diaspora_federation/entities/embed.rb b/lib/diaspora_federation/entities/embed.rb new file mode 100644 index 0000000..166b99a --- /dev/null +++ b/lib/diaspora_federation/entities/embed.rb @@ -0,0 +1,44 @@ +module DiasporaFederation + module Entities + # This entity is used to specify embed information about an URL that should be embedded. + # + # @see Validators::EmbedValidator + class Embed < Entity + # @!attribute [r] url + # URL that should be embedded. + # @return [String] url + property :url, :string, optional: true + + # @!attribute [r] title + # The title of the embedded URL. + # @return [String] title + property :title, :string, optional: true + + # @!attribute [r] description + # The description of the embedded URL. + # @return [String] description + property :description, :string, optional: true + + # @!attribute [r] image + # The image of the embedded URL. + # @return [String] image + property :image, :string, optional: true + + # @!attribute [r] nothing + # True, if nothing should be embedded. + # @return [String] nothing + property :nothing, :boolean, optional: true + + # @return [String] string representation of this object + def to_s + "Embed#{":#{url}" if url}" + end + + def validate + super + + raise ValidationError, "Either 'url' must be set or 'nothing' must be 'true'" unless nothing ^ url + end + end + end +end diff --git a/lib/diaspora_federation/entities/status_message.rb b/lib/diaspora_federation/entities/status_message.rb index c2eda40..6a50b61 100644 --- a/lib/diaspora_federation/entities/status_message.rb +++ b/lib/diaspora_federation/entities/status_message.rb @@ -36,6 +36,11 @@ module DiasporaFederation # @return [Entities::Event] event entity :event, Entities::Event, optional: true + # @!attribute [r] embed + # Optional embed information of an URL that should be embedded in the status message + # @return [Entities::Embed] embed + entity :embed, Entities::Embed, optional: true + private def validate diff --git a/lib/diaspora_federation/schemas/federation_entities.json b/lib/diaspora_federation/schemas/federation_entities.json index 7196fdd..42ecf23 100644 --- a/lib/diaspora_federation/schemas/federation_entities.json +++ b/lib/diaspora_federation/schemas/federation_entities.json @@ -10,6 +10,7 @@ {"$ref": "#/definitions/reshare"}, {"$ref": "#/definitions/profile"}, {"$ref": "#/definitions/location"}, + {"$ref": "#/definitions/embed"}, {"$ref": "#/definitions/photo"}, {"$ref": "#/definitions/poll"}, {"$ref": "#/definitions/poll_answer"} @@ -373,6 +374,26 @@ ] } } + }, + + "embed": { + "type": "object", + "properties": { + "entity_type": { + "type": "string", + "pattern": "^embed$" + }, + "entity_data": { + "type": "object", + "properties": { + "url": { "type": ["string", "null"] }, + "title": { "type": ["string", "null"] }, + "description": { "type": ["string", "null"] }, + "image": { "type": ["string", "null"] }, + "nothing": { "type": "boolean" } + } + } + } } } } diff --git a/lib/diaspora_federation/test/factories.rb b/lib/diaspora_federation/test/factories.rb index 40744fb..38e8d99 100644 --- a/lib/diaspora_federation/test/factories.rb +++ b/lib/diaspora_federation/test/factories.rb @@ -207,6 +207,14 @@ module DiasporaFederation edited_at { Time.now.utc } end + Fabricator(:embed_entity, class_name: DiasporaFederation::Entities::Embed) do + url "https://example.org/" + title "Example Website" + description "This is an example!" + image "https://example.org/example.png" + nothing nil + end + Fabricator(:related_entity, class_name: DiasporaFederation::Entities::RelatedEntity) do author { Fabricate.sequence(:diaspora_id) } local true diff --git a/lib/diaspora_federation/validators.rb b/lib/diaspora_federation/validators.rb index 864ab8e..986daef 100644 --- a/lib/diaspora_federation/validators.rb +++ b/lib/diaspora_federation/validators.rb @@ -46,6 +46,7 @@ require "diaspora_federation/validators/account_migration_validator" require "diaspora_federation/validators/comment_validator" require "diaspora_federation/validators/contact_validator" require "diaspora_federation/validators/conversation_validator" +require "diaspora_federation/validators/embed_validator" require "diaspora_federation/validators/event_participation_validator" require "diaspora_federation/validators/event_validator" require "diaspora_federation/validators/h_card_validator" diff --git a/lib/diaspora_federation/validators/embed_validator.rb b/lib/diaspora_federation/validators/embed_validator.rb new file mode 100644 index 0000000..de766cc --- /dev/null +++ b/lib/diaspora_federation/validators/embed_validator.rb @@ -0,0 +1,13 @@ +module DiasporaFederation + module Validators + # This validates a {Entities::Embed}. + class EmbedValidator < OptionalAwareValidator + include Validation + + rule :url, :URI + rule :title, length: {maximum: 255} + rule :description, length: {maximum: 65_535} + rule :image, URI: %i[host path] + end + end +end diff --git a/spec/lib/diaspora_federation/entities/embed_spec.rb b/spec/lib/diaspora_federation/entities/embed_spec.rb new file mode 100644 index 0000000..7b857e4 --- /dev/null +++ b/spec/lib/diaspora_federation/entities/embed_spec.rb @@ -0,0 +1,56 @@ +module DiasporaFederation + describe Entities::Embed do + let(:data) { Fabricate.attributes_for(:embed_entity) } + + let(:xml) { <<-XML } + + #{data[:url]} + #{data[:title]} + #{data[:description]} + #{data[:image]} + +XML + + let(:json) { <<-JSON } +{ + "entity_type": "embed", + "entity_data": { + "url": "#{data[:url]}", + "title": "#{data[:title]}", + "description": "#{data[:description]}", + "image": "#{data[:image]}" + } +} +JSON + + let(:string) { "Embed:#{data[:url]}" } + + it_behaves_like "an Entity subclass" + + it_behaves_like "an XML Entity" + + it_behaves_like "a JSON Entity" + + describe "#validate" do + it "allows 'url' to be set if 'nothing' is not true" do + expect { Entities::Embed.new(data) }.not_to raise_error + end + + it "allows 'url' to be missing if 'nothing' is true" do + expect { Entities::Embed.new(nothing: true) }.not_to raise_error + end + + it "doesn't allow 'url' to be set if 'nothing' is true" do + expect { + Entities::Embed.new(data.merge(nothing: true)) + }.to raise_error Entity::ValidationError, "Either 'url' must be set or 'nothing' must be 'true'" + end + + it "doesn't allow 'url' to be missing if 'nothing' is not true" do + expect { + Entities::Embed.new({}) + }.to raise_error Entity::ValidationError, "Either 'url' must be set or 'nothing' must be 'true'" + end + end + end +end diff --git a/spec/lib/diaspora_federation/entities/status_message_spec.rb b/spec/lib/diaspora_federation/entities/status_message_spec.rb index 52d550a..4fe600a 100644 --- a/spec/lib/diaspora_federation/entities/status_message_spec.rb +++ b/spec/lib/diaspora_federation/entities/status_message_spec.rb @@ -10,6 +10,7 @@ module DiasporaFederation location: location, poll: nil, event: nil, + embed: nil, provider_display_name: "something" ) } @@ -137,6 +138,8 @@ XML expect(parsed_instance.photos).to eq([]) expect(parsed_instance.location).to be_nil expect(parsed_instance.poll).to be_nil + expect(parsed_instance.event).to be_nil + expect(parsed_instance.embed).to be_nil expect(parsed_instance.public).to be_falsey expect(parsed_instance.provider_display_name).to be_nil end diff --git a/spec/lib/diaspora_federation/validators/embed_validator_spec.rb b/spec/lib/diaspora_federation/validators/embed_validator_spec.rb new file mode 100644 index 0000000..d6045b3 --- /dev/null +++ b/spec/lib/diaspora_federation/validators/embed_validator_spec.rb @@ -0,0 +1,40 @@ +module DiasporaFederation + describe Validators::EmbedValidator do + let(:entity) { :embed_entity } + it_behaves_like "a common validator" + + describe "#url" do + it_behaves_like "a property with a value validation/restriction" do + let(:property) { :url } + let(:wrong_values) { %w[https://asdf$%.com example.com] } + let(:correct_values) { [nil, "https://example.org", "https://example.org/index.html"] } + end + end + + describe "#title" do + it_behaves_like "a length validator" do + let(:property) { :title } + let(:length) { 255 } + end + end + + describe "#description" do + it_behaves_like "a length validator" do + let(:property) { :description } + let(:length) { 65_535 } + end + end + + describe "#image" do + it_behaves_like "a property with a value validation/restriction" do + let(:property) { :image } + let(:wrong_values) { %w[https://asdf$%.com example.com] } + let(:correct_values) { [nil] } + end + + it_behaves_like "a url path validator" do + let(:property) { :image } + end + end + end +end diff --git a/spec/lib/diaspora_federation/validators/web_finger_validator_spec.rb b/spec/lib/diaspora_federation/validators/web_finger_validator_spec.rb index bad3334..8aaf77b 100644 --- a/spec/lib/diaspora_federation/validators/web_finger_validator_spec.rb +++ b/spec/lib/diaspora_federation/validators/web_finger_validator_spec.rb @@ -27,7 +27,7 @@ module DiasporaFederation describe "##{prop}" do it_behaves_like "a property with a value validation/restriction" do let(:property) { prop } - let(:wrong_values) { %w[https://asdf$.com example.com] } + let(:wrong_values) { %w[https://asdf$%.com example.com] } let(:correct_values) { [nil] } end diff --git a/spec/support/shared_validator_specs.rb b/spec/support/shared_validator_specs.rb index 97f46ee..862148f 100644 --- a/spec/support/shared_validator_specs.rb +++ b/spec/support/shared_validator_specs.rb @@ -277,6 +277,13 @@ shared_examples "a url validator without path" do end shared_examples "a url path validator" do + it "validates url with a path" do + validator = described_class.new(entity_stub(entity, property => "https://example.com/some/path")) + + expect(validator).to be_valid + expect(validator.errors).to be_empty + end + it "fails for url with special chars" do validator = described_class.new(entity_stub(entity, property => "https://asdf$%.com/some/path"))