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
+
+~~~
+
+### With metadata
+
+~~~xml
+
+~~~
+
+### With `nothing`
+
+~~~xml
+
+~~~
+
+[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
+
~~~
@@ -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
+
~~~
@@ -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 }
+
+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"))