YPBT 0.2.6 → 0.2.12

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.
@@ -1,37 +1,38 @@
1
- # frozen_string_literal: true
2
-
3
- module YoutubeVideo
4
- # Executable code for file(s) in bin/ folder
5
- class Runner
6
- def self.run!(args)
7
- video_id = args[0] || ENV['YT_VIDEO_ID']
8
- unless video_id
9
- puts 'USAGE: YPBT [video_id]'
10
- exit(1)
11
- end
12
-
13
- video = YoutubeVideo::Video.find(video_id: video_id)
14
-
15
- output_info(video)
16
- end
17
-
18
- def self.output_info(video)
19
- title = video.title
20
- separator = Array.new(video.title.length) { '-' }.join
21
- video_info =
22
- video.comments.map.with_index do |comment, index|
23
- comment_info(comment, index)
24
- end.join
25
-
26
- [title, separator, video_info].join("\n")
27
- end
28
-
29
- def self.comment_info(comment, index)
30
- "#{index + 1}:\n"\
31
- " Author: #{comment.author.author_name}\n"\
32
- " Comment: #{comment.text_display}\n"\
33
- " LIKE: #{comment.author.like_count}\n"\
34
- " AuthorChannelUrl: #{comment.author.author_channel_url}\n"
35
- end
36
- end
37
- end
1
+ # frozen_string_literal: true
2
+
3
+ module YoutubeVideo
4
+ # Executable code for file(s) in bin/ folder
5
+ class Runner
6
+ def self.run!(args)
7
+ video_id = args[0] || ENV['YT_VIDEO_ID']
8
+ unless video_id
9
+ puts 'USAGE: YPBT [video_id]'
10
+ exit(1)
11
+ end
12
+
13
+ video = YoutubeVideo::Video.find(video_id: video_id)
14
+
15
+ output_info(video)
16
+ end
17
+
18
+ def self.output_info(video)
19
+ return 'Nothing found. (Invalid video id or api-key)' if video.nil?
20
+ title = video.title
21
+ separator = Array.new(video.title.length) { '-' }.join
22
+ video_info =
23
+ video.comments.map.with_index do |comment, index|
24
+ comment_info(comment, index)
25
+ end.join
26
+
27
+ [title, separator, video_info].join("\n")
28
+ end
29
+
30
+ def self.comment_info(comment, index)
31
+ "#{index + 1}:\n"\
32
+ " Author: #{comment.author.author_name}\n"\
33
+ " Comment: #{comment.text_display}\n"\
34
+ " LIKE: #{comment.author.like_count}\n"\
35
+ " AuthorChannelUrl: #{comment.author.author_channel_url}\n"
36
+ end
37
+ end
38
+ end
@@ -1,55 +1,55 @@
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
- :like_count
9
- def initialize(start_time:, comment:, end_time: nil, like_count: nil,
10
- tag_type: nil)
11
- @start_time = string_to_time start_time
12
- @end_time = end_time
13
- @like_count = like_count ? like_count : comment.like_count
14
- @tag_type = tag_type
15
- end
16
-
17
- def start_time
18
- @start_time&.iso8601
19
- end
20
-
21
- def end_time=(end_time)
22
- @end_time = string_to_time end_time if end_time
23
- end
24
-
25
- def end_time
26
- @end_time&.iso8601 if @end_time
27
- end
28
-
29
- def duration
30
- @duration = @end_time - @start_time if @end_time && @start_time
31
- @duration&.iso8601 if @duration
32
- end
33
-
34
- def tag_type=(tag_type)
35
- @tag_type = TAG_TYPES[tag_type.to_sym] if tag_type
36
- end
37
-
38
- def self.find(comment:)
39
- time_tag_pattern = /http.+?youtube.+?\?.+?t=.+?\>([0-9:]+)<\/a>/
40
- start_times_string = comment.text_display.scan time_tag_pattern
41
- tags = start_times_string.map do |match_parts|
42
- Timetag.new(start_time: match_parts[0], comment: comment)
43
- end
44
- tags
45
- end
46
-
47
- private
48
-
49
- def string_to_time(time_string)
50
- time_unit = [:seconds, :minutes, :hours, :day, :weeks]
51
- time_array = time_string.scan(/[0-9]+/).map(&:to_i).reverse
52
- Duration.new(Hash[time_unit.zip(time_array)])
53
- end
54
- end
55
- end
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
+ :like_count
9
+ def initialize(start_time:, comment:, end_time: nil, like_count: nil,
10
+ tag_type: nil)
11
+ @start_time = string_to_time start_time
12
+ @end_time = end_time
13
+ @like_count = like_count ? like_count : comment.like_count
14
+ @tag_type = tag_type
15
+ end
16
+
17
+ def start_time
18
+ @start_time&.iso8601
19
+ end
20
+
21
+ def end_time=(end_time)
22
+ @end_time = string_to_time end_time if end_time
23
+ end
24
+
25
+ def end_time
26
+ @end_time&.iso8601 if @end_time
27
+ end
28
+
29
+ def duration
30
+ @duration = @end_time - @start_time if @end_time && @start_time
31
+ @duration&.iso8601 if @duration
32
+ end
33
+
34
+ def tag_type=(tag_type)
35
+ @tag_type = TAG_TYPES[tag_type.to_sym] if tag_type
36
+ end
37
+
38
+ def self.find(comment:)
39
+ time_tag_pattern = /http.+?youtube.+?\?.+?t=.+?\>([0-9:]+)<\/a>/
40
+ start_times_string = comment.text_display.scan time_tag_pattern
41
+ tags = start_times_string.map do |match_parts|
42
+ Timetag.new(start_time: match_parts[0], comment: comment)
43
+ end
44
+ tags
45
+ end
46
+
47
+ private
48
+
49
+ def string_to_time(time_string)
50
+ time_unit = [:seconds, :minutes, :hours, :day, :weeks]
51
+ time_array = time_string.scan(/[0-9]+/).map(&:to_i).reverse
52
+ Duration.new(Hash[time_unit.zip(time_array)])
53
+ end
54
+ end
55
+ end
@@ -1,5 +1,5 @@
1
- # frozen_string_literal: true
2
-
3
- module YoutubeVideo
4
- VERSION = '0.2.6'
5
- end
1
+ # frozen_string_literal: true
2
+
3
+ module YoutubeVideo
4
+ VERSION = '0.2.12'
5
+ end
@@ -1,65 +1,73 @@
1
- # frozen_string_literal: true
2
- require_relative 'comment'
3
- require_relative 'youtube_api'
4
-
5
- module YoutubeVideo
6
- # Main class to setup a Video
7
- class Video
8
- attr_reader :title, :description, :dislike_count, :like_count,
9
- :comment_count, :view_count, :duration, :id, :channel_id
10
-
11
- def initialize(data:)
12
- @id = data['id']
13
- @title = data['snippet']['title']
14
- @channel_id = data['snippet']['channelId']
15
- @description = data['snippet']['description']
16
- @dislike_count = data['statistics']['dislikeCount'].to_i
17
- @like_count = data['statistics']['likeCount'].to_i
18
- @view_count = data['statistics']['viewCount'].to_i
19
- @duration = data['contentDetails']['duration']
20
- @is_channel = false
21
- end
22
-
23
- def comments
24
- # contain only the comments which have time tag.
25
- return @comments if @comments
26
- raw_comments = YtApi.time_tags_info(@id)
27
- @comments = raw_comments.map { |comment| Comment.new(data: comment) }
28
- end
29
-
30
- def channel_title
31
- load_channel_info unless @is_channel
32
- @channel_title
33
- end
34
-
35
- def channel_image_url
36
- load_channel_info unless @is_channel
37
- @channel_image_url
38
- end
39
-
40
- def channel_description
41
- load_channel_info unless @is_channel
42
- @channel_description
43
- end
44
-
45
- def embed_url
46
- return @embed_url if @embed_url
47
- @embed_url = "https://www.youtube.com/embed/#{@id}"
48
- end
49
-
50
- def self.find(video_id:)
51
- video_data = YtApi.video_info(video_id)
52
- new(data: video_data) if video_data
53
- end
54
-
55
- private
56
-
57
- def load_channel_info
58
- channel_data = YtApi.channel_info @channel_id
59
- @channel_title = channel_data['title'] if channel_data
60
- @channel_image_url = channel_data['image_url'] if channel_data
61
- @channel_description = channel_data['description'] if channel_data
62
- @is_channel = true
63
- end
64
- end
65
- end
1
+ # frozen_string_literal: true
2
+ require_relative 'comment'
3
+ require_relative 'youtube_api'
4
+
5
+ module YoutubeVideo
6
+ # Main class to setup a Video
7
+ class Video
8
+ attr_reader :title, :description, :dislike_count, :like_count,
9
+ :comment_count, :view_count, :duration, :id, :channel_id,
10
+ :thumbnail_url, :category_id
11
+
12
+ def initialize(data:)
13
+ @id = data['id']
14
+ @title = data['snippet']['title']
15
+ @channel_id = data['snippet']['channelId']
16
+ @description = data['snippet']['description']
17
+ @category_id = data['snippet']['categoryId']
18
+ @thumbnail_url = data['snippet']['thumbnails']['medium']['url']
19
+ @dislike_count = data['statistics']['dislikeCount'].to_i
20
+ @like_count = data['statistics']['likeCount'].to_i
21
+ @view_count = data['statistics']['viewCount'].to_i
22
+ @duration = data['contentDetails']['duration']
23
+ @is_channel = false
24
+ end
25
+
26
+ def comments
27
+ # contain only the comments which have time tag.
28
+ return @comments if @comments
29
+ raw_comments = YtApi.time_tags_info(@id)
30
+ @comments = raw_comments.map { |comment| Comment.new(data: comment) }
31
+ end
32
+
33
+ def channel_title
34
+ load_channel_info unless @is_channel
35
+ @channel_title
36
+ end
37
+
38
+ def channel_image_url
39
+ load_channel_info unless @is_channel
40
+ @channel_image_url
41
+ end
42
+
43
+ def channel_description
44
+ load_channel_info unless @is_channel
45
+ @channel_description
46
+ end
47
+
48
+ def embed_url
49
+ return @embed_url if @embed_url
50
+ @embed_url = "https://www.youtube.com/embed/#{@id}"
51
+ end
52
+
53
+ def self.find(video_id:)
54
+ video_data = YtApi.video_info(video_id)
55
+ new(data: video_data) if video_data
56
+ end
57
+
58
+ def self.find_popular(max_results: 25)
59
+ videos_data = YtApi.popular_videos_info(max_results)
60
+ videos_data.map { |data| new(data: data) } unless videos_data.empty?
61
+ end
62
+
63
+ private
64
+
65
+ def load_channel_info
66
+ channel_data = YtApi.channel_info @channel_id
67
+ @channel_title = channel_data['title'] if channel_data
68
+ @channel_image_url = channel_data['image_url'] if channel_data
69
+ @channel_description = channel_data['description'] if channel_data
70
+ @is_channel = true
71
+ end
72
+ end
73
+ end
@@ -1,110 +1,127 @@
1
- # frozen_string_literal: true
2
- require 'http'
3
- require 'json'
4
-
5
- module YoutubeVideo
6
- # Service for all Youtube API calls
7
- class YtApi
8
- YT_URL = 'https://www.googleapis.com'
9
- YT_COMPANY = 'youtube'
10
- YT_COMPANY_URL = URI.join(YT_URL, "#{YT_COMPANY}/")
11
- API_VER = 'v3'
12
- YT_API_URL = URI.join(YT_COMPANY_URL, "#{API_VER}/")
13
- TIME_TAG_PATTERN = /http.+?youtube.+?\?.+?t=.+?\>([0-9:]+)<\/a>/
14
- def self.api_key
15
- return @api_key if @api_key
16
- @api_key = ENV['YOUTUBE_API_KEY']
17
- end
18
-
19
- def self.config=(credentials)
20
- @config ? @config.update(credentials) : @config = credentials
21
- end
22
-
23
- def self.video_info(video_id)
24
- field = 'items(id,snippet(channelId,description,publishedAt,title),'\
25
- 'statistics(likeCount,dislikeCount,viewCount),'\
26
- 'contentDetails(duration))'
27
- video_response = HTTP.get(yt_resource_url('videos'),
28
- params: { id: video_id,
29
- key: api_key,
30
- part: 'snippet,statistics,
31
- contentDetails',
32
- fields: field })
33
- JSON.parse(video_response.to_s)['items'].first
34
- end
35
-
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)
48
- comment_threads_response = HTTP.get(yt_resource_url('commentThreads'),
49
- params: { videoId: video_id,
50
- key: api_key,
51
- order: 'relevance',
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]
59
- end
60
-
61
- def self.channel_info(channel_id)
62
- fields = 'items(id,snippet(title,description,thumbnails(default(url))))'
63
- channel_response = HTTP.get(yt_resource_url('channels'),
64
- params: { id: channel_id,
65
- key: api_key,
66
- part: 'snippet',
67
- fields: fields })
68
- channel_data = JSON.parse(channel_response.to_s)['items'].first
69
- if channel_data
70
- {
71
- 'title' => channel_data['snippet']['title'],
72
- 'description' => channel_data['snippet']['description'],
73
- 'image_url' => channel_data['snippet']['thumbnails']['default']['url']
74
- }
75
- end
76
- end
77
-
78
- def self.extract_comment(comment_threads)
79
- comments = comment_threads['items'].map do |item|
80
- comment = item['snippet']['topLevelComment']['snippet']
81
- comment['id'] = item['id']
82
- comment
83
- end
84
- comments
85
- end
86
-
87
- def self.time_tags_info(video_id, max_search_time = 5)
88
- next_page = ''
89
- comments_with_tags = []
90
- max_search_time.times do
91
- next_page, tmp_comments = video_comments_info(video_id, next_page)
92
- tmp_comments.each do |comment|
93
- comments_with_tags.push(comment) if time_tag? comment
94
- end
95
- break unless next_page
96
- end
97
- comments_with_tags
98
- end
99
-
100
-
101
- def self.time_tag?(comment)
102
- !(comment['textDisplay'] =~ TIME_TAG_PATTERN).nil?
103
- end
104
-
105
- private_class_method
106
- def self.yt_resource_url(resouce_name)
107
- URI.join(YT_API_URL, resouce_name.to_s)
108
- end
109
- end
110
- end
1
+ # frozen_string_literal: true
2
+ require 'http'
3
+ require 'json'
4
+
5
+ module YoutubeVideo
6
+ # Service for all Youtube API calls
7
+ class YtApi
8
+ YT_URL = 'https://youtube.googleapis.com'
9
+ YT_COMPANY = 'youtube'
10
+ YT_COMPANY_URL = URI.join(YT_URL, "#{YT_COMPANY}/")
11
+ API_VER = 'v3'
12
+ YT_API_URL = URI.join(YT_COMPANY_URL, "#{API_VER}/")
13
+ TIME_TAG_PATTERN = /http.+?youtube.+?\?.+?t=.+?\>([0-9:]+)<\/a>/
14
+ def self.api_key
15
+ return @api_key if @api_key
16
+ @api_key = ENV['YOUTUBE_API_KEY']
17
+ end
18
+
19
+ def self.config=(credentials)
20
+ @config ? @config.update(credentials) : @config = credentials
21
+ end
22
+
23
+ def self.video_info(video_id)
24
+ field = 'items(id,'\
25
+ 'snippet(thumbnails(medium),channelId,description,'\
26
+ 'publishedAt,title,categoryId),'\
27
+ 'statistics(likeCount,dislikeCount,viewCount),'\
28
+ 'contentDetails(duration))'
29
+ video_response = HTTP.get(yt_resource_url('videos'),
30
+ params: { id: video_id,
31
+ key: api_key,
32
+ part: 'snippet,statistics,'\
33
+ 'contentDetails',
34
+ fields: field })
35
+ JSON.parse(video_response.to_s)['items']&.first
36
+ end
37
+
38
+ def self.popular_videos_info(max_results = 25)
39
+ field = 'items(id,'\
40
+ 'snippet(thumbnails(medium),channelId,description,'\
41
+ 'publishedAt,title,categoryId),'\
42
+ 'statistics(likeCount,dislikeCount,viewCount),'\
43
+ 'contentDetails(duration))'
44
+ video_response = HTTP.get(yt_resource_url('videos'),
45
+ params: { chart: 'mostpopular',
46
+ key: api_key,
47
+ maxResults: max_results,
48
+ part: 'snippet,statistics,'\
49
+ 'contentDetails',
50
+ fields: field })
51
+ JSON.parse(video_response.to_s)['items']
52
+ end
53
+
54
+ def self.comment_info(comment_id)
55
+ comment_response = HTTP.get(yt_resource_url('comments'),
56
+ params: { id: comment_id,
57
+ key: api_key,
58
+ part: 'snippet' })
59
+ item = JSON.parse(comment_response.to_s)['items'].first
60
+ comment = item['snippet']
61
+ comment['id'] = comment_id
62
+ comment
63
+ end
64
+
65
+ def self.video_comments_info(video_id, page_token = '', max_results = 100)
66
+ comment_threads_response = HTTP.get(yt_resource_url('commentThreads'),
67
+ params: { videoId: video_id,
68
+ key: api_key,
69
+ order: 'relevance',
70
+ part: 'snippet',
71
+ maxResults: max_results,
72
+ pageToken: page_token })
73
+ comment_threads = JSON.parse(comment_threads_response.to_s)
74
+ comments = extract_comment(comment_threads)
75
+ next_page_token = comment_threads['nextPageToken']
76
+ [next_page_token, comments]
77
+ end
78
+
79
+ def self.channel_info(channel_id)
80
+ fields = 'items(id,snippet(title,description,thumbnails(default(url))))'
81
+ channel_response = HTTP.get(yt_resource_url('channels'),
82
+ params: { id: channel_id,
83
+ key: api_key,
84
+ part: 'snippet',
85
+ fields: fields })
86
+ channel_data = JSON.parse(channel_response.to_s)['items'].first
87
+ if channel_data
88
+ {
89
+ 'title' => channel_data['snippet']['title'],
90
+ 'description' => channel_data['snippet']['description'],
91
+ 'image_url' => channel_data['snippet']['thumbnails']['default']['url']
92
+ }
93
+ end
94
+ end
95
+
96
+ def self.extract_comment(comment_threads)
97
+ comments = comment_threads['items'].map do |item|
98
+ comment = item['snippet']['topLevelComment']['snippet']
99
+ comment['id'] = item['id']
100
+ comment
101
+ end
102
+ comments
103
+ end
104
+
105
+ def self.time_tags_info(video_id, max_search_time = 5)
106
+ next_page = ''
107
+ comments_with_tags = []
108
+ max_search_time.times do
109
+ next_page, tmp_comments = video_comments_info(video_id, next_page)
110
+ tmp_comments.each do |comment|
111
+ comments_with_tags.push(comment) if time_tag? comment
112
+ end
113
+ break unless next_page
114
+ end
115
+ comments_with_tags
116
+ end
117
+
118
+ def self.time_tag?(comment)
119
+ !(comment['textDisplay'] =~ TIME_TAG_PATTERN).nil?
120
+ end
121
+
122
+ private_class_method
123
+ def self.yt_resource_url(resouce_name)
124
+ URI.join(YT_API_URL, resouce_name.to_s)
125
+ end
126
+ end
127
+ end