diff --git a/Changelog.md b/Changelog.md index da66cf97c..162cf51d1 100644 --- a/Changelog.md +++ b/Changelog.md @@ -16,6 +16,7 @@ ## Features * Deleted comments will be removed when loading more comments [#7045](https://github.com/diaspora/diaspora/pull/7045) * The "subscribe" indicator on a post now gets toggled when you like or rehsare a post [#7040](https://github.com/diaspora/diaspora/pull/7040) +* Add OpenGraph video support [#7043](https://github.com/diaspora/diaspora/pull/7043) # 0.6.0.0 diff --git a/app/assets/javascripts/app/helpers/open_graph.js b/app/assets/javascripts/app/helpers/open_graph.js deleted file mode 100644 index f106b681f..000000000 --- a/app/assets/javascripts/app/helpers/open_graph.js +++ /dev/null @@ -1,12 +0,0 @@ -// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later - -(function(){ - app.helpers.openGraph = { - html : function (open_graph_cache) { - if (!open_graph_cache) { return ""; } - return ''; - }, - }; -})(); -// @license-end - diff --git a/app/assets/javascripts/app/views/content_view.js b/app/assets/javascripts/app/views/content_view.js index 76fb62739..485fca01c 100644 --- a/app/assets/javascripts/app/views/content_view.js +++ b/app/assets/javascripts/app/views/content_view.js @@ -151,6 +151,10 @@ app.views.OEmbed = app.views.Base.extend({ app.views.OpenGraph = app.views.Base.extend({ templateName : "opengraph", + events: { + "click .video-overlay": "loadVideo" + }, + initialize: function() { this.truncateDescription(); }, @@ -161,6 +165,12 @@ app.views.OpenGraph = app.views.Base.extend({ var ogdesc = this.model.get('open_graph_cache'); ogdesc.description = app.helpers.truncate(ogdesc.description, 250); } + }, + + loadVideo: function() { + this.$(".opengraph-container").html( + "" + ); } }); diff --git a/app/assets/stylesheets/opengraph.scss b/app/assets/stylesheets/opengraph.scss index b0f877e4c..3e7bd5c02 100644 --- a/app/assets/stylesheets/opengraph.scss +++ b/app/assets/stylesheets/opengraph.scss @@ -10,20 +10,41 @@ overflow: hidden; a { color: #000; - img { - margin: 5px 5px 5px 0px; - float: left; - max-width: 150px; - padding-right: 5px; - } .og-title { - margin-bottom: 5px; font-weight: bold; } } a:hover { color: $blue; } + + .thumb { + float: left; + margin: 5px; + margin-left: 0; + max-width: 150px; + padding-right: 5px; + + .video-overlay { + cursor: pointer; + height: 145px; + width: 145px; + } + + .overlay { + background-color: rgba($black, .2); + background-image: image-url('buttons/playbtn.png'); + background-position: center; + background-repeat: no-repeat; + background-size: 60px 60px; + display: inline-block; + height: 145px; + position: relative; + top: -145px; + width: 145px; + } + } + .og-description { color: $text-grey; } diff --git a/app/assets/templates/opengraph_tpl.jst.hbs b/app/assets/templates/opengraph_tpl.jst.hbs index fd1a66853..57fe9fc76 100644 --- a/app/assets/templates/opengraph_tpl.jst.hbs +++ b/app/assets/templates/opengraph_tpl.jst.hbs @@ -1,12 +1,20 @@ {{#unless o_embed_cache}} {{#if open_graph_cache}}
+
+ {{#if open_graph_cache.video_url }} +
+ +
+
+ {{else}} + + {{/if}} +
-

{{open_graph_cache.title}}

{{open_graph_cache.description}}

{{/if}} {{/unless}} - diff --git a/app/models/open_graph_cache.rb b/app/models/open_graph_cache.rb index 7bf381f01..ffae81c94 100644 --- a/app/models/open_graph_cache.rb +++ b/app/models/open_graph_cache.rb @@ -13,6 +13,7 @@ class OpenGraphCache < ActiveRecord::Base t.add :image t.add :description t.add :url + t.add :video_url end def image @@ -39,8 +40,15 @@ class OpenGraphCache < ActiveRecord::Base self.image = object.og.image.url self.url = object.og.url self.description = object.og.description + if object.og.video.try(:secure_url) && secure_video_url?(object.og.video.secure_url) + self.video_url = object.og.video.secure_url + end self.save rescue OpenGraphReader::NoOpenGraphDataError, OpenGraphReader::InvalidObjectError end + + def secure_video_url?(url) + SECURE_OPENGRAPH_VIDEO_URLS.any? {|u| u =~ url } + end end diff --git a/config/initializers/open_graph_reader.rb b/config/initializers/open_graph_reader.rb index f1ec34134..182dd4bdb 100644 --- a/config/initializers/open_graph_reader.rb +++ b/config/initializers/open_graph_reader.rb @@ -5,3 +5,18 @@ OpenGraphReader.configure do |config| config.synthesize_image_url = true config.guess_datetime_format = true end + +og_video_urls = [] +og_providers = YAML.load_file(Rails.root.join("config", "open_graph_providers.yml")) +og_providers.each do |_, provider| + provider["video_urls"].each do |video_url| + # taken from https://github.com/ruby-oembed/ruby-oembed/blob/fe2b63c/lib/oembed/provider.rb#L68 + _, scheme, domain, path = *video_url.match(%r{([^:]*)://?([^/?]*)(.*)}) + domain = Regexp.escape(domain).gsub("\\*", "(.*?)").gsub("(.*?)\\.", "([^\\.]+\\.)?") + path = Regexp.escape(path).gsub("\\*", "(.*?)") + url = Regexp.new("^#{Regexp.escape(scheme)}://#{domain}#{path}") + og_video_urls << url + end if provider["video_urls"] +end + +SECURE_OPENGRAPH_VIDEO_URLS = og_video_urls diff --git a/config/open_graph_providers.yml b/config/open_graph_providers.yml new file mode 100644 index 000000000..00c94955d --- /dev/null +++ b/config/open_graph_providers.yml @@ -0,0 +1,5 @@ +# SECURITY NOTICE! CROSS-SITE SCRIPTING! +# these endpoints may inject html code into our page +bandcamp: + video_urls: + - https://bandcamp.com/EmbeddedPlayer/*/* diff --git a/db/migrate/20160901072443_add_video_url_to_open_graph_cache.rb b/db/migrate/20160901072443_add_video_url_to_open_graph_cache.rb new file mode 100644 index 000000000..487248a26 --- /dev/null +++ b/db/migrate/20160901072443_add_video_url_to_open_graph_cache.rb @@ -0,0 +1,5 @@ +class AddVideoUrlToOpenGraphCache < ActiveRecord::Migration + def change + add_column :open_graph_caches, :video_url, :text + end +end diff --git a/db/schema.rb b/db/schema.rb index 593703cb4..f911b7660 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160822212739) do +ActiveRecord::Schema.define(version: 20160901072443) do create_table "account_deletions", force: :cascade do |t| t.string "diaspora_handle", limit: 255 @@ -300,6 +300,7 @@ ActiveRecord::Schema.define(version: 20160822212739) do t.text "image", limit: 65535 t.text "url", limit: 65535 t.text "description", limit: 65535 + t.text "video_url", limit: 65535 end create_table "participations", force: :cascade do |t| diff --git a/spec/factories.rb b/spec/factories.rb index 4b5363f70..3d4fd827f 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -257,6 +257,7 @@ FactoryGirl.define do title "Some article" ob_type "article" description "This is the article lead" + video_url "http://example.com/videos/123.html" end factory(:tag_following) do diff --git a/spec/javascripts/app/views/open_graph_view_spec.js b/spec/javascripts/app/views/open_graph_view_spec.js index 77bba41a1..e40c49ae9 100644 --- a/spec/javascripts/app/views/open_graph_view_spec.js +++ b/spec/javascripts/app/views/open_graph_view_spec.js @@ -1,30 +1,72 @@ describe("app.views.OpenGraph", function() { - var open_graph_cache = { - "url": "http://example.com/articles/123", - "title": "Example title", - "description": "Test description", - "image": "http://example.com/thumb.jpg", - "ob_type": "article" - }; - - beforeEach(function(){ - this.statusMessage = factory.statusMessage({ - "open_graph_cache": open_graph_cache + context("without a video_url", function() { + beforeEach(function() { + this.openGraphCache = { + "url": "http://example.com/articles/123", + "title": "Example title", + "description": "Test description", + "image": "http://example.com/thumb.jpg", + "ob_type": "article" + }; + this.statusMessage = factory.statusMessage({ + "open_graph_cache": this.openGraphCache + }); + this.view = new app.views.OpenGraph({model: this.statusMessage}); }); - this.view = new app.views.OpenGraph({model : this.statusMessage}); - }); + describe("rendering", function() { + it("shows the preview based on the opengraph data", function() { + this.view.render(); + var html = this.view.$el.html(); - describe("rendering", function(){ - it("shows the preview based on the opengraph data", function(){ - this.view.render(); - var html = this.view.$el.html(); - - expect(html).toContain(open_graph_cache.url); - expect(html).toContain(open_graph_cache.title); - expect(html).toContain(open_graph_cache.description); - expect(html).toContain(open_graph_cache.image); + expect(html).toContain(this.openGraphCache.url); + expect(html).toContain(this.openGraphCache.title); + expect(html).toContain(this.openGraphCache.description); + expect(html).toContain(this.openGraphCache.image); + expect(html).not.toContain("video-overlay"); + }); }); }); + context("with a video_url", function() { + beforeEach(function() { + this.openGraphCache = { + "url": "http://example.com/articles/123", + "title": "Example title", + "description": "Test description", + "image": "http://example.com/thumb.jpg", + "ob_type": "article", + "video_url": "http://example.com" + }; + this.statusMessage = factory.statusMessage({ + "open_graph_cache": this.openGraphCache + }); + this.view = new app.views.OpenGraph({model: this.statusMessage}); + }); + + describe("rendering", function() { + it("shows the preview based on the opengraph data", function() { + this.view.render(); + var html = this.view.$el.html(); + + expect(html).toContain(this.openGraphCache.url); + expect(html).toContain(this.openGraphCache.title); + expect(html).toContain(this.openGraphCache.description); + expect(html).toContain(this.openGraphCache.image); + expect(html).toContain(this.openGraphCache.video_url); + expect(html).toContain("video-overlay"); + }); + }); + + describe("loadVideo", function() { + it("adds an iframe with the video", function() { + this.view.render(); + spec.content().html(this.view.$el); + expect($("iframe").length).toBe(0); + $(".video-overlay").click(); + expect($("iframe").length).toBe(1); + expect($("iframe").attr("src")).toBe(this.openGraphCache.video_url); + }); + }); + }); }); diff --git a/spec/models/open_graph_cache_spec.rb b/spec/models/open_graph_cache_spec.rb new file mode 100644 index 000000000..adbf98a03 --- /dev/null +++ b/spec/models/open_graph_cache_spec.rb @@ -0,0 +1,61 @@ +# Copyright (c) 2010-2011, Diaspora Inc. This file is +# licensed under the Affero General Public License version 3 or later. See +# the COPYRIGHT file. + +require "spec_helper" + +describe OpenGraphCache, type: :model do + describe "fetch_and_save_opengraph_data!" do + context "with an unsecure video url" do + it "doesn't save the video url" do + expect(OpenGraphReader).to receive(:fetch!).with("https://example.com/article/123").and_return( + double( + og: double( + description: "This is the article lead", + image: double(url: "https://example.com/image/123.jpg"), + title: "Some article", + type: "article", + url: "https://example.com/acticle/123-seo-foo", + video: double(secure_url: "https://example.com/videos/123.html") + ) + ) + ) + ogc = OpenGraphCache.new(url: "https://example.com/article/123") + ogc.fetch_and_save_opengraph_data! + + expect(ogc.description).to eq("This is the article lead") + expect(ogc.image).to eq("https://example.com/image/123.jpg") + expect(ogc.title).to eq("Some article") + expect(ogc.ob_type).to eq("article") + expect(ogc.url).to eq("https://example.com/acticle/123-seo-foo") + expect(ogc.video_url).to be_nil + end + end + + context "with a secure video url" do + it "saves the video url" do + expect(OpenGraphReader).to receive(:fetch!).with("https://example.com/article/123").and_return( + double( + og: double( + description: "This is the article lead", + image: double(url: "https://example.com/image/123.jpg"), + title: "Some article", + type: "article", + url: "https://example.com/acticle/123-seo-foo", + video: double(secure_url: "https://bandcamp.com/EmbeddedPlayer/v=2/track=12/size=small") + ) + ) + ) + ogc = OpenGraphCache.new(url: "https://example.com/article/123") + ogc.fetch_and_save_opengraph_data! + + expect(ogc.description).to eq("This is the article lead") + expect(ogc.image).to eq("https://example.com/image/123.jpg") + expect(ogc.title).to eq("Some article") + expect(ogc.ob_type).to eq("article") + expect(ogc.url).to eq("https://example.com/acticle/123-seo-foo") + expect(ogc.video_url).to eq("https://bandcamp.com/EmbeddedPlayer/v=2/track=12/size=small") + end + end + end +end diff --git a/spec/presenters/post_presenter_spec.rb b/spec/presenters/post_presenter_spec.rb index 5c9c52728..6bd2ca689 100644 --- a/spec/presenters/post_presenter_spec.rb +++ b/spec/presenters/post_presenter_spec.rb @@ -133,4 +133,25 @@ describe PostPresenter do expect(PostPresenter.new(reshare).send(:description)).to eq(nil) end end + + describe "#build_open_graph_cache" do + it "returns a dummy og cache if the og cache is missing" do + expect(@presenter.build_open_graph_cache.image).to be_nil + end + + context "with an open graph cache" do + it "delegates to as_api_response" do + og_cache = double("open_graph_cache") + expect(og_cache).to receive(:as_api_response).with(:backbone) + @presenter.post = double(open_graph_cache: og_cache) + @presenter.send(:build_open_graph_cache) + end + + it "returns the open graph cache data" do + open_graph_cache = FactoryGirl.create(:open_graph_cache) + post = FactoryGirl.create(:status_message, public: true, open_graph_cache: open_graph_cache) + expect(PostPresenter.new(post).send(:build_open_graph_cache)).to eq(open_graph_cache.as_api_response(:backbone)) + end + end + end end