From 1c6741c83f83ad3a6d66b8547933f7cba4d59027 Mon Sep 17 00:00:00 2001 From: claudiofullscreen Date: Tue, 22 Jul 2014 08:54:53 -0700 Subject: [PATCH] Allow angle brackets when editing videos/playlists Angle brackets are not characters allowed by YouTube, but instead of failing, Yt replaces them with Unicode characters that look very similar and are accepted by YouTube. --- Gemfile.lock | 2 +- HISTORY.md | 1 + README.md | 6 ++-- lib/yt/models/playlist.rb | 3 +- lib/yt/models/resource.rb | 34 +++++++++++++++++++---- lib/yt/models/video.rb | 2 +- lib/yt/version.rb | 2 +- spec/requests/as_account/playlist_spec.rb | 11 ++++++++ spec/requests/as_account/video_spec.rb | 11 ++++++++ 9 files changed, 59 insertions(+), 13 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index a8785c97..fce8018e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - yt (0.9.3) + yt (0.9.4) activesupport GEM diff --git a/HISTORY.md b/HISTORY.md index 2cabfd5d..50e5bd0f 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -6,6 +6,7 @@ v0.9 - 2014/07/28 * Add content_owner.policies to list ContentID policies * Let 'update' methods understand both under_score and camelCased parameters * Add claim.third_party? +* Allow angle brackets when editing title, description, tags and replace them with similar characters allowed by YouTube v0.8 - 2014/07/18 diff --git a/README.md b/README.md index f9124d93..6b022633 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ To install on your system, run To use inside a bundled Ruby project, add this line to the Gemfile: - gem 'yt', '~> 0.9.3' + gem 'yt', '~> 0.9.4' Since the gem follows [Semantic Versioning](http://semver.org), indicating the full version in your Gemfile (~> *major*.*minor*.*patch*) @@ -280,7 +280,7 @@ video.like #=> true account = Yt::Account.new access_token: 'ya29.1.ABCDEFGHIJ' video = Yt::Video.new id: 'MESycYJytkU', auth: account -video.update title: 'A title', description: 'A description', tags: ['a tag'], categoryId: '21' +video.update title: 'A title', description: 'A description ', tags: ['a tag'], categoryId: '21' video.views since: 7.days.ago #=> {Wed, 28 May 2014 => 12.0, Thu, 29 May 2014 => 3.0, …} video.comments until: 2.days.ago #=> {Wed, 28 May 2014 => 9.0, Thu, 29 May 2014 => 4.0, …} @@ -346,7 +346,7 @@ playlist.playlist_items.first #=> # *The methods above do not require authentication.* ```ruby -playlist.update title: 'title', description: 'desc', tags: ['new tag'], privacy_status: 'private' +playlist.update title: 'A with angle brackets', description: 'desc', tags: ['new tag'], privacy_status: 'private' playlist.add_video 'MESycYJytkU' playlist.add_videos ['MESycYJytkU', 'MESycYJytkU'] playlist.delete_playlist_items title: 'Fullscreen Creator Platform' #=> [true] diff --git a/lib/yt/models/playlist.rb b/lib/yt/models/playlist.rb index be741cc9..6e069fe3 100644 --- a/lib/yt/models/playlist.rb +++ b/lib/yt/models/playlist.rb @@ -68,7 +68,8 @@ def delete_playlist_items(attrs = {}) # @see https://developers.google.com/youtube/v3/docs/playlists/update def update_parts - snippet = {keys: [:title, :description, :tags], required: true} + keys = [:title, :description, :tags] + snippet = {keys: keys, required: true, sanitize_brackets: true} status = {keys: [:privacy_status]} {snippet: snippet, status: status} end diff --git a/lib/yt/models/resource.rb b/lib/yt/models/resource.rb index c20c9eed..8c46241b 100644 --- a/lib/yt/models/resource.rb +++ b/lib/yt/models/resource.rb @@ -1,3 +1,5 @@ +# encoding: UTF-8 + require 'yt/models/base' require 'yt/models/url' @@ -56,13 +58,22 @@ def delete_params end def build_update_body(attributes = {}) - body = {} - update_parts.each do |name, part| - body[name] = {}.tap do |hash| - part[:keys].map{|k| hash[camelize k] = attributes.fetch k, send(k)} - end if should_include_part_in_update?(part, attributes) + {}.tap do |body| + update_parts.each do |name, part| + if should_include_part_in_update? part, attributes + body[name] = build_update_body_part part, attributes + sanitize_brackets! body[name] if part[:sanitize_brackets] + end + end + end + end + + def build_update_body_part(part, attributes = {}) + {}.tap do |body_part| + part[:keys].map do |key| + body_part[camelize key] = attributes.fetch key, send(key) + end end - body end def should_include_part_in_update?(part, attributes = {}) @@ -75,6 +86,17 @@ def underscore_keys!(hash) hash.dup.each_key{|key| hash[underscore key] = hash.delete key} end + # @return [Hash] the original hash with angle brackets characters in its + # values replaced with similar Unicode characters accepted by Youtube. + # @see https://support.google.com/youtube/answer/57404?hl=en + def sanitize_brackets!(source) + case source + when String then source.gsub('<', '‹').gsub('>', '›') + when Array then source.map{|string| sanitize_brackets! string} + when Hash then source.each{|k,v| source[k] = sanitize_brackets! v} + end + end + def camelize(value) value.to_s.camelize(:lower).to_sym end diff --git a/lib/yt/models/video.rb b/lib/yt/models/video.rb index d21e84a7..24b31a2c 100644 --- a/lib/yt/models/video.rb +++ b/lib/yt/models/video.rb @@ -158,7 +158,7 @@ def reports_params # @todo: Add status, recording details keys def update_parts snippet_keys = [:title, :description, :tags, :category_id] - {snippet: {keys: snippet_keys}} + {snippet: {keys: snippet_keys, sanitize_brackets: true}} end end end diff --git a/lib/yt/version.rb b/lib/yt/version.rb index 440ecd40..30073adb 100644 --- a/lib/yt/version.rb +++ b/lib/yt/version.rb @@ -1,3 +1,3 @@ module Yt - VERSION = '0.9.3' + VERSION = '0.9.4' end \ No newline at end of file diff --git a/spec/requests/as_account/playlist_spec.rb b/spec/requests/as_account/playlist_spec.rb index 4bb308ed..97284b80 100644 --- a/spec/requests/as_account/playlist_spec.rb +++ b/spec/requests/as_account/playlist_spec.rb @@ -91,6 +91,17 @@ end end + context 'given I update title, description and/or tags using angle brackets' do + let(:attrs) { {title: "Yt Test < >", description: '< >', tags: ['<tag>']} } + + specify 'updates them replacing angle brackets with similar unicode characters accepted by YouTube' do + expect(update).to be true + expect(playlist.title).to eq 'Yt Test ‹ ›' + expect(playlist.description).to eq '‹ ›' + expect(playlist.tags).to eq ['‹tag›'] + end + end + context 'given I update the privacy status' do let!(:new_privacy_status) { old_privacy_status == 'private' ? 'unlisted' : 'private' } diff --git a/spec/requests/as_account/video_spec.rb b/spec/requests/as_account/video_spec.rb index d83a902c..da6d2046 100644 --- a/spec/requests/as_account/video_spec.rb +++ b/spec/requests/as_account/video_spec.rb @@ -166,6 +166,17 @@ end end + context 'given I update title, description and/or tags using angle brackets' do + let(:attrs) { {title: "Yt Test < >", description: '< >', tags: ['<tag>']} } + + specify 'updates them replacing angle brackets with similar unicode characters accepted by YouTube' do + expect(update).to be true + expect(video.title).to eq 'Yt Test ‹ ›' + expect(video.description).to eq '‹ ›' + expect(video.tags).to eq ['‹tag›'] + end + end + it 'returns valid reports for video-related metrics' do # Some reports are only available to Content Owners. # See content ownere test for more details about what the methods return.