Skip to content

Commit

Permalink
Allow angle brackets when editing videos/playlists
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
claudiofullscreen committed Aug 1, 2014
1 parent 4539fa2 commit e412f0a
Show file tree
Hide file tree
Showing 11 changed files with 67 additions and 15 deletions.
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
yt (0.9.3)
yt (0.9.4)
activesupport

GEM
Expand Down
1 change: 1 addition & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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*)
Expand Down Expand Up @@ -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 <with angle brackets>', 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, …}
Expand Down Expand Up @@ -346,7 +346,7 @@ playlist.playlist_items.first #=> #<Yt::Models::PlaylistItem @id=...>
*The methods above do not require authentication.*

```ruby
playlist.update title: 'title', description: 'desc', tags: ['new tag'], privacy_status: 'private'
playlist.update title: 'A <title> 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]
Expand Down
3 changes: 2 additions & 1 deletion lib/yt/models/playlist.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
34 changes: 28 additions & 6 deletions lib/yt/models/resource.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# encoding: UTF-8

require 'yt/models/base'
require 'yt/models/url'

Expand Down Expand Up @@ -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 = {})
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion lib/yt/models/video.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion lib/yt/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module Yt
VERSION = '0.9.3'
VERSION = '0.9.4'
end
6 changes: 6 additions & 0 deletions spec/requests/as_account/account_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@
let(:path_or_url) { 'https://bit.ly/yt_test' }

it { expect(video).to be_a Yt::Video }

# {"error"=>{"errors"=>[{"domain"=>"youtube.video", "reason"=>"invalidTitle", "message"=>"Bad Request", "locationType"=>"other", "location"=>"body.snippet.title"}], "code"=>400, "message"=>"Bad Request"}}
describe 'allows < and > in title, description and tags' do
let(:video_params) { {title: "Test < >", description: '< >', tags: ['<tag>'], privacy_status: 'private'} }
it { expect(video).to be_a Yt::Video }
end
end
end
end
4 changes: 2 additions & 2 deletions spec/requests/as_account/channel_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@

context 'given my own channel' do
let(:id) { $account.channel.id }
let(:title) { 'Yt Test title' }
let(:description) { 'Yt Test description' }
let(:title) { 'Yt Test < title' }
let(:description) { 'Yt Test > description' }
let(:tags) { ['Yt Test Tag 1', 'Yt Test Tag 2'] }
let(:privacy_status) { 'unlisted' }
let(:params) { {title: title, description: description, tags: tags, privacy_status: privacy_status} }
Expand Down
11 changes: 11 additions & 0 deletions spec/requests/as_account/playlist_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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' }

Expand Down
11 changes: 11 additions & 0 deletions spec/requests/as_account/video_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit e412f0a

Please sign in to comment.