YPBT 0.1.5 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 61ca8e47aa6832f4964feb5183284194e1eb1ba3
4
- data.tar.gz: 031b9c6f56a7e5c12b9083ac7e8037e04f66a9aa
3
+ metadata.gz: 2af780eba918c5d863f386c47a56487d155bb9bd
4
+ data.tar.gz: 20fd1fafe9e5e595edb071ffda8b7f9beec74d7f
5
5
  SHA512:
6
- metadata.gz: dad8c87e97632e19d7af64ea144e8ddb39d946eed78f968f8ea087cf18e49af0f006ae1b000c5d3b9a7bf7c08980f09d2853a44ddd91b6e084ce600a510abae0
7
- data.tar.gz: 3f8942eeb84ade640a7c48b1cdc6627cee180c67be553cff9ec24d752051192f2b8a8d4984d479be9fd307ede7c94ec2ac43af09ce6f95b553be8f634a5d2925
6
+ metadata.gz: dbd7823b446f6bba748db5988292e754ceaa8933722d6947b959c422c8edacf38d24c5719ba1610a832907defb13bbfe5e181d2efb06779f1afb5deb56ed35c4
7
+ data.tar.gz: d424ff4da46cdfb44b8a23e66cd2fad2c5fa4e6c88b022459a378ff309b86bdb791e530c2e840d2c10e22ea577026a7e1850e23227431d544931addbca52ec6d
data/.travis.yml CHANGED
@@ -1,8 +1,6 @@
1
1
  language: ruby
2
2
  rvm:
3
3
  - 2.3.1
4
- - 2.2
5
- - 2.1
6
4
  branches:
7
5
  only:
8
6
  - master
data/Rakefile CHANGED
@@ -24,8 +24,9 @@ namespace :credentials do
24
24
  end
25
25
 
26
26
  desc 'run tests'
27
- task :spec do
28
- sh 'ruby spec/video_spec.rb'
27
+ Rake::TestTask.new(:spec) do |t|
28
+ t.pattern = 'spec/*_spec.rb'
29
+ t.warning = false
29
30
  end
30
31
 
31
32
  desc 'delete cassette fixtures'
data/YPBT.gemspec CHANGED
@@ -19,7 +19,7 @@ Gem::Specification.new do |s|
19
19
  s.executables << 'YPBT'
20
20
 
21
21
  s.add_runtime_dependency 'http', '~> 2.0'
22
-
22
+ s.add_runtime_dependency 'ruby-duration', '~>3.2.3'
23
23
  s.add_development_dependency 'minitest', '~> 5.9'
24
24
  s.add_development_dependency 'minitest-rg', '~> 5.2'
25
25
  s.add_development_dependency 'rake', '~> 11.3'
@@ -29,7 +29,6 @@ Gem::Specification.new do |s|
29
29
  s.add_development_dependency 'flog', '~> 4.4'
30
30
  s.add_development_dependency 'flay', '~> 2.8'
31
31
  s.add_development_dependency 'rubocop', '~> 0.42'
32
-
33
32
  s.homepage = 'https://github.com/RubyStarts3/YPBT'
34
33
  s.license = 'MIT'
35
34
  end
data/lib/YPBT/author.rb CHANGED
@@ -6,10 +6,10 @@ module YoutubeVideo
6
6
  :like_count
7
7
  def initialize(data)
8
8
  return unless data
9
- @author_name = data[0]['snippet']['authorDisplayName']
10
- @author_image_url = data[0]['snippet']['authorProfileImageUrl']
11
- @author_channel_url = data[0]['snippet']['authorChannelUrl']
12
- @like_count = data[0]['snippet']['likeCount'].to_i
9
+ @author_name = data['authorDisplayName']
10
+ @author_image_url = data['authorProfileImageUrl']
11
+ @author_channel_url = data['authorChannelUrl']
12
+ @like_count = data['likeCount'].to_i
13
13
  end
14
14
  end
15
15
  end
data/lib/YPBT/comment.rb CHANGED
@@ -6,18 +6,13 @@ require_relative 'author'
6
6
  module YoutubeVideo
7
7
  # signle comment on video's comment threads
8
8
  class Comment
9
- attr_reader :comment_id, :updated_at, :text_display, :published_at
9
+ attr_reader :comment_id, :updated_at, :text_display, :published_at, :author,
10
+ :time_tags, :like_count
10
11
 
11
12
  def initialize(data: nil)
12
13
  load_data(data)
13
14
  end
14
15
 
15
- def author
16
- return @author if @author
17
- author_data = YtApi.authors_info(@comment_id)
18
- @author = YoutubeVideo::Author.new(author_data)
19
- end
20
-
21
16
  def self.find(comment_id:)
22
17
  comment_data = YoutubeVideo::YtApi.comment_info(comment_id)
23
18
  new(data: comment_data)
@@ -26,10 +21,13 @@ module YoutubeVideo
26
21
  private
27
22
 
28
23
  def load_data(comment_data)
29
- @comment_id = comment_data['id']
30
- @updated_at = comment_data['snippet']['updateAt']
31
- @text_display = comment_data['snippet']['textDisplay']
32
- @published_at = comment_data['snippet']['publishedAt']
24
+ @comment_id = comment_data['id']
25
+ @like_count = comment_data['likeCount'].to_i
26
+ @updated_at = comment_data['updateAt']
27
+ @text_display = comment_data['textDisplay']
28
+ @published_at = comment_data['publishedAt']
29
+ @author = YoutubeVideo::Author.new(comment_data)
30
+ @time_tags = YoutubeVideo::Timetag.find(comment: self)
33
31
  end
34
32
  end
35
33
  end
data/lib/YPBT/runner.rb CHANGED
@@ -19,7 +19,7 @@ module YoutubeVideo
19
19
  title = video.title
20
20
  separator = Array.new(video.title.length) { '-' }.join
21
21
  video_info =
22
- video.commentthreads.first(3).map.with_index do |comment, index|
22
+ video.comments.map.with_index do |comment, index|
23
23
  comment_info(comment, index)
24
24
  end.join
25
25
 
@@ -28,7 +28,7 @@ module YoutubeVideo
28
28
 
29
29
  def self.comment_info(comment, index)
30
30
  "#{index + 1}:\n"\
31
- " Autor: #{comment.author.author_name}\n"\
31
+ " Author: #{comment.author.author_name}\n"\
32
32
  " Comment: #{comment.text_display}\n"\
33
33
  " LIKE: #{comment.author.like_count}\n"\
34
34
  " AuthorChannelUrl: #{comment.author.author_channel_url}\n"
@@ -0,0 +1,54 @@
1
+ require 'ruby-duration'
2
+
3
+ module YoutubeVideo
4
+ TAG_TYPES = { MUSIC: 'music', VIDEO: 'video' }.freeze
5
+ # comment's time tag infomation
6
+ class Timetag
7
+ attr_reader :start_time, :end_time, :tag_type, :duration, :comment
8
+ def initialize(start_time:, comment:, end_time: nil, like_count: nil,
9
+ tag_type: nil)
10
+ @start_time = string_to_time start_time
11
+ @end_time = end_time
12
+ @like_count = like_count ? like_count : comment.like_count
13
+ @tag_type = tag_type
14
+ end
15
+
16
+ def start_time
17
+ @start_time&.iso8601
18
+ end
19
+
20
+ def end_time=(end_time)
21
+ @end_time = string_to_time end_time if end_time
22
+ end
23
+
24
+ def end_time
25
+ @end_time&.iso8601 if @end_time
26
+ end
27
+
28
+ def duration
29
+ @duration = @end_time - @start_time if @end_time && @start_time
30
+ @duration&.iso8601 if @duration
31
+ end
32
+
33
+ def tag_type=(tag_type)
34
+ @tag_type = TAG_TYPES[tag_type.to_sym] if tag_type
35
+ end
36
+
37
+ def self.find(comment:)
38
+ time_tag_pattern = /http.+?youtube.+?\?.+?t=.+?\>([0-9:]+)<\/a>/
39
+ start_times_string = comment.text_display.scan time_tag_pattern
40
+ tags = start_times_string.map do |match_parts|
41
+ Timetag.new(start_time: match_parts[0], comment: comment)
42
+ end
43
+ tags
44
+ end
45
+
46
+ private
47
+
48
+ def string_to_time(time_string)
49
+ time_unit = [:seconds, :minutes, :hours, :day, :weeks]
50
+ time_array = time_string.scan(/[0-9]+/).map(&:to_i).reverse
51
+ Duration.new(Hash[time_unit.zip(time_array)])
52
+ end
53
+ end
54
+ end
data/lib/YPBT/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module YoutubeVideo
4
- VERSION = '0.1.5'
4
+ VERSION = '0.2.2'
5
5
  end
data/lib/YPBT/video.rb CHANGED
@@ -14,18 +14,15 @@ module YoutubeVideo
14
14
  @description = data['snippet']['description']
15
15
  @dislike_count = data['statistics']['dislikeCount'].to_i
16
16
  @like_count = data['statistics']['likeCount'].to_i
17
- @comment_count = data['statistics']['commentCount'].to_i
18
17
  @view_count = data['statistics']['viewCount'].to_i
18
+ @duration = data['contentDetails']['duration']
19
19
  end
20
20
 
21
- def commentthreads
22
- return @commentthreads if @commentthreads
23
- raw_threads = YoutubeVideo::YtApi.video_commentthreads_info(@id)
24
- @commentthreads = raw_threads.map do |comment|
25
- YoutubeVideo::Comment.new(
26
- data: comment['snippet']['topLevelComment']
27
- )
28
- end
21
+ def comments
22
+ # contain only the comments which have time tag.
23
+ return @comments if @comments
24
+ raw_comments = YtApi.time_tags_info(@id)
25
+ @comments = raw_comments.map { |comment| Comment.new(data: comment) }
29
26
  end
30
27
 
31
28
  def embed_url
@@ -34,7 +31,7 @@ module YoutubeVideo
34
31
  end
35
32
 
36
33
  def self.find(video_id:)
37
- video_data = YoutubeVideo::YtApi.video_info(video_id)
34
+ video_data = YtApi.video_info(video_id)
38
35
  new(data: video_data)
39
36
  end
40
37
  end
@@ -10,7 +10,7 @@ module YoutubeVideo
10
10
  YT_COMPANY_URL = URI.join(YT_URL, "#{YT_COMPANY}/")
11
11
  API_VER = 'v3'
12
12
  YT_API_URL = URI.join(YT_COMPANY_URL, "#{API_VER}/")
13
-
13
+ TIME_TAG_PATTERN = /http.+?youtube.+?\?.+?t=.+?\>([0-9:]+)<\/a>/
14
14
  def self.api_key
15
15
  return @api_key if @api_key
16
16
  @api_key = ENV['YOUTUBE_API_KEY']
@@ -22,42 +22,68 @@ module YoutubeVideo
22
22
 
23
23
  def self.video_info(video_id)
24
24
  field = 'items(id,snippet(channelId,description,publishedAt,title),'\
25
- 'statistics)'
25
+ 'statistics(likeCount,dislikeCount,viewCount),'\
26
+ 'contentDetails(duration))'
26
27
  video_response = HTTP.get(yt_resource_url('videos'),
27
28
  params: { id: video_id,
28
29
  key: api_key,
29
- part: 'snippet,statistics',
30
+ part: 'snippet,statistics,
31
+ contentDetails',
30
32
  fields: field })
31
33
  JSON.parse(video_response.to_s)['items'].first
32
34
  end
33
35
 
34
- def self.video_commentthreads_info(video_id)
36
+ def self.comment_info(comment_id)
37
+ comment_response = HTTP.get(yt_resource_url('comments'),
38
+ params: { id: comment_id,
39
+ key: api_key,
40
+ part: 'snippet' })
41
+ item = JSON.parse(comment_response.to_s)['items'].first
42
+ comment = item['snippet']
43
+ comment['id'] = comment_id
44
+ comment
45
+ end
46
+
47
+ def self.video_comments_info(video_id, page_token = '', max_results = 100)
35
48
  comment_threads_response = HTTP.get(yt_resource_url('commentThreads'),
36
49
  params: { videoId: video_id,
37
50
  key: api_key,
38
51
  order: 'relevance',
39
- part: 'snippet' })
40
- JSON.parse(comment_threads_response.to_s)['items']
52
+ part: 'snippet',
53
+ maxResults: max_results,
54
+ pageToken: page_token })
55
+ comment_threads = JSON.parse(comment_threads_response.to_s)
56
+ comments = extract_comment(comment_threads)
57
+ next_page_token = comment_threads['nextPageToken']
58
+ [next_page_token, comments]
41
59
  end
42
60
 
43
- def self.comment_info(comment_id)
44
- comment_response = HTTP.get(yt_resource_url('comments'),
45
- params: { id: comment_id,
46
- key: api_key,
47
- part: 'snippet' })
48
- JSON.parse(comment_response.to_s)['items'][0]
61
+ def self.extract_comment(comment_threads)
62
+ comments = comment_threads['items'].map do |item|
63
+ comment = item['snippet']['topLevelComment']['snippet']
64
+ comment['id'] = item['id']
65
+ comment
66
+ end
67
+ comments
49
68
  end
50
69
 
51
- def self.authors_info(comment_id)
52
- field = 'items(snippet(authorChannelId,authorChannelUrl,'\
53
- 'authorDisplayName,authorProfileImageUrl,likeCount))'
54
- authors_response = HTTP.get(yt_resource_url('comments'),
55
- params: { id: comment_id,
56
- key: api_key,
57
- part: 'snippet',
58
- fields: field })
59
- JSON.parse(authors_response.to_s)['items']
70
+ def self.time_tags_info(video_id, max_search_time = 5)
71
+ next_page = ''
72
+ comments_with_tags = []
73
+ max_search_time.times do
74
+ next_page, tmp_comments = video_comments_info(video_id, next_page)
75
+ tmp_comments.each do |comment|
76
+ comments_with_tags.push(comment) if time_tag? comment
77
+ end
78
+ break unless next_page
79
+ end
80
+ comments_with_tags
81
+ end
82
+
83
+ def self.time_tag?(comment)
84
+ !(comment['textDisplay'] =~ TIME_TAG_PATTERN).nil?
60
85
  end
86
+
61
87
  private_class_method
62
88
  def self.yt_resource_url(resouce_name)
63
89
  URI.join(YT_API_URL, resouce_name.to_s)
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+ require_relative 'spec_helper.rb'
3
+
4
+ describe 'YtApi specifications' do
5
+ VCR.configure do |c|
6
+ c.cassette_library_dir = CASSETTES_FOLDER
7
+ c.hook_into :webmock
8
+
9
+ c.filter_sensitive_data('<API_KEY>') { ENV['YOUTUBE_API_KEY'] }
10
+ c.filter_sensitive_data('<API_KEY_ESCAPED>') do
11
+ URI.escape(ENV['YOUTUBE_API_KEY'])
12
+ end
13
+ end
14
+
15
+ before do
16
+ VCR.insert_cassette CASSETTE_FILE, record: :new_episodes
17
+ end
18
+
19
+ after do
20
+ VCR.eject_cassette
21
+ end
22
+
23
+ describe 'YtApi Credentials' do
24
+ it 'should be able to get a new api key with ENV credentials' do
25
+ YoutubeVideo::YtApi.api_key.length.must_be :>, 0
26
+ end
27
+ it 'should be able to get a new access token with file credentials' do
28
+ YoutubeVideo::YtApi.config = { api_key: ENV['YOUTUBE_API_KEY'] }
29
+ end
30
+ end
31
+
32
+ describe 'YtApi functions' do
33
+ it 'should be able to find video by video id' do
34
+ YoutubeVideo::YtApi.video_info(TEST_VIDEO_ID).must_be_instance_of Hash
35
+ end
36
+
37
+ it 'should be able find comment by comment id' do
38
+ result = YoutubeVideo::YtApi.comment_info(TEST_COMMENT_ID)
39
+ result.must_be_instance_of Hash
40
+ result['id'].must_equal TEST_COMMENT_ID
41
+ end
42
+
43
+ it 'should be able find comments by video id' do
44
+ next_page_token, comments = YoutubeVideo::YtApi
45
+ .video_comments_info(TEST_VIDEO_ID)
46
+ next_page_token.must_be_instance_of String
47
+ comments.must_be_instance_of Array
48
+ comments.length.must_be :>, 1
49
+ comments[0].must_be_instance_of Hash
50
+ end
51
+
52
+ it 'should be able find comments that contain time tags by video_id' do
53
+ comments = YoutubeVideo::YtApi.time_tags_info(TEST_VIDEO_ID)
54
+ comments.must_be_instance_of Array
55
+ comments.length.must_be :>, 1
56
+ comments[0].must_be_instance_of Hash
57
+ comments[0]['textDisplay'].must_match(/[0-9]+:\d+/)
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+ require_relative 'spec_helper.rb'
3
+
4
+ describe 'comment specifications' do
5
+ VCR.configure do |c|
6
+ c.cassette_library_dir = CASSETTES_FOLDER
7
+ c.hook_into :webmock
8
+
9
+ c.filter_sensitive_data('<API_KEY>') { ENV['YOUTUBE_API_KEY'] }
10
+ c.filter_sensitive_data('<API_KEY_ESCAPED>') do
11
+ URI.escape(ENV['YOUTUBE_API_KEY'])
12
+ end
13
+ end
14
+
15
+ before do
16
+ VCR.insert_cassette CASSETTE_FILE, record: :new_episodes
17
+ end
18
+
19
+ after do
20
+ VCR.eject_cassette
21
+ end
22
+
23
+ describe 'comment functions' do
24
+ it 'should has the abilit to find by comment id' do
25
+ comment = YoutubeVideo::Comment.find(comment_id: TEST_COMMENT_ID)
26
+ comment.must_be_instance_of YoutubeVideo::Comment
27
+ comment.comment_id.must_equal TEST_COMMENT_ID
28
+ end
29
+ end
30
+ end