AnimeDL 0.1.1

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 749ae8ea30b343616260eede819cda37c01148b5
4
+ data.tar.gz: 623dd8328333bbc3cab908b0ddf2340889ccdd38
5
+ SHA512:
6
+ metadata.gz: f91608397485ae824c5a24307d5f5105869c506dbf1245d177738dafc3cba0388e17fb5ee867d5da3359f1696161e9f1162120097b75bcaf9cba7f5978b4bc71
7
+ data.tar.gz: b098d18960bd46965e632504383e5efe2e9afe569f9d96b160509f48c54258eb73315b49ce56f434d1ed75b54a06a87c338fc394fd3c8e6d86e9b3fdce248284
@@ -0,0 +1 @@
1
+ test*
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Anirudh Sundar
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,26 @@
1
+ # AnimeDL
2
+
3
+ ## Installation
4
+
5
+ Add this line to your application's Gemfile:
6
+
7
+ ```
8
+ gem 'AnimeDL'
9
+ ```
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install AnimeDL
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+
24
+ ## License
25
+
26
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -0,0 +1,18 @@
1
+ require "anime_dl/version"
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "AnimeDL"
5
+ spec.version = "0.1.1"
6
+ spec.authors = ["Anirudh Sundar"]
7
+ spec.email = "anirudhsundar@hotmail.com"
8
+
9
+ spec.summary = %q{The AnimeDL gem is used to get episode links or download episodes for a particular anime}
10
+ spec.homepage = "https://github.com/anirudhsundar98/AnimeDL"
11
+ spec.license = "MIT"
12
+
13
+ spec.require_paths = ["lib"]
14
+ spec.files = `git ls-files`.split("\n")
15
+ spec.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
16
+
17
+ spec.add_runtime_dependency 'mechanize', '~> 2.7'
18
+ end
@@ -0,0 +1,276 @@
1
+ #!/usr/bin/env ruby
2
+ require "anime_dl"
3
+ require "optparse"
4
+
5
+ # APP OPTIONS
6
+ options = { option: "link", episode: true, file: nil, quiet: false }
7
+ help_statements = {
8
+ option: %Q[Option (default: "link"). Values: "l" (or) "link", "d" (or) "download"],
9
+ episode: %Q[Prevents the app from asking for an Episodes prompt. Gets detals for all episodes.],
10
+ file: %Q[Provide location to store extracted details. Exits if path does not exist. (Usage Examples below).],
11
+ quiet: %Q[Suppresses large, in-program help statements.],
12
+ help: %Q[Show this message and exit.]
13
+ }
14
+
15
+ ARGV.options do |opts|
16
+ opts.banner = "AnimeDL: An anime link scraper and downloader"
17
+ opts.define_head "Usage: #{File.basename($PROGRAM_NAME)} [options] file"
18
+ opts.separator ""
19
+ opts.separator "User can either download episodes or get links to video sources of episodes of a particular anime."
20
+ opts.separator "On running the command the program prompts the user for a search query and prints a list of search results."
21
+ opts.separator "The user can then choose a particular anime. Details for all episodes are scraped."
22
+ opts.separator "To select particular episodes call the app with the '-e' option."
23
+ opts.separator ""
24
+ opts.separator "Links: "
25
+ opts.separator " To output links to a file, provide the path to a file as the last argument."
26
+ opts.separator " If argument is a directory, a new text file will be created in that directory."
27
+ opts.separator "Downloads: "
28
+ opts.separator " To download videos to particular directory, specify the directory as the last argument."
29
+ opts.separator " If no directory is specified the videos are downloaded to ~/AnimeDL/anime_name/"
30
+ opts.separator ""
31
+ opts.separator "Options:"
32
+
33
+ opts.on("-o option", "--option option", help_statements[:option]) do |option|
34
+ unless (['l', 'link', 'd', 'download'].include? option)
35
+ puts "Invalid argument to --option"
36
+ puts %Q[Valid options: "l" (or) "link", "d" (or) "download"]
37
+ exit
38
+ end
39
+
40
+ options[:option] = option
41
+ end
42
+
43
+ opts.on("-e", "--episode", help_statements[:episode]) do
44
+ options[:episode] = false
45
+ end
46
+
47
+ opts.on("-f file", "--file=file", help_statements[:file]) do |file|
48
+ options[:file] = file
49
+ end
50
+
51
+ opts.on("-q", "--quiet", help_statements[:quiet]) do
52
+ options[:quiet] = true
53
+ end
54
+
55
+
56
+ opts.on( "-h", "--help", help_statements[:help]) do
57
+ puts opts
58
+ exit
59
+ end
60
+
61
+ opts.separator ""
62
+ opts.separator "Examples:"
63
+ opts.separator %Q[ `anime_dl` (outputs link urls on the terminal)]
64
+ opts.separator %Q[ `anime_dl -f rel/path/to/filename.txt` (outputs link urls to 'rel/path/to/filename.txt')]
65
+ opts.separator %Q[ `anime_dl -o link -f rel/path/to/directory/` (outputs link urls to 'rel/path/to/directory/anime_name.txt')]
66
+ opts.separator %Q[ `anime_dl -o download` (downloads videos to './anime_name/')]
67
+ opts.separator %Q[ `anime_dl -o download -f .` (downloads videos to './')]
68
+ opts.separator %Q[ `anime_dl -o d -f /abs/path/to/directory` (downloads videos to '/abs/path/to/directory/anime_name/')]
69
+ opts.separator %Q[ `anime_dl -o download -f /abs/path/to/directory/` (downloads videos to '/abs/path/to/directory/anime_name/')]
70
+ opts.separator ""
71
+
72
+ opts.separator "Example App Usage:"
73
+ opts.separator "`anime_dl -q`"
74
+ opts.separator " Enter Search Query: fullmetal alchemist b"
75
+ opts.separator " 0. (Exit)"
76
+ opts.separator " 1. Fullmetal Alchemist Brotherhood Dubbed | Hagane no Renkinjutsushi (2009) Dubbed"
77
+ opts.separator " 2. Fullmetal Alchemist Brotherhood | Hagane no Renkinjutsushi (2009)"
78
+ opts.separator " 3. (Enter new Search Query)"
79
+ opts.separator ""
80
+ opts.separator " Choice: 4"
81
+ opts.separator " (Empty response gets all episodes, '-1' to exit)."
82
+ opts.separator " Total number of Episodes - 20 (0-19)"
83
+ opts.separator " Episodes: -1"
84
+
85
+ opts.separator ""
86
+ opts.separator ""
87
+ opts.separator "When the '-e' option is not provided, the app asks for 'Episodes' after retrieving the user's anime choice."
88
+ opts.separator "The user can provide induvidual space-seperated episodes."
89
+ opts.separator "The user can also provide multiple space-seperated ranges with each range having the format 'start-end'."
90
+ opts.separator "Below are a few examples (assuming anime has more than 18 episodes)."
91
+ opts.separator %Q[ `Episodes: 2 4 5` - Retrieves details for episodes 2, 4 and 5]
92
+ opts.separator %Q[ `Episodes: 4-8` - Retrieves details for episodes 4 to 8]
93
+ opts.separator %Q[ `Episodes: 8-l` - Retrieves details for episodes 8 to the last episode]
94
+ opts.separator %Q[ `Episodes: 2 9-13 18-l` - Retrieves details for episodes 2, 9 to 13 and 18 to end]
95
+ opts.separator ""
96
+ opts.separator "NOTE: "
97
+ opts.separator "The page being scraped from, \"animeheaven.eu\", only allows a certain number of page views per day (>150, <300)."
98
+ opts.separator "Once that limit is exceed, you will have tochange networks or try again in 24 hours."
99
+
100
+ begin
101
+ opts.parse!
102
+ rescue OptionParser::MissingArgument, OptionParser::InvalidArgument => error
103
+ puts "Error: #{error}"
104
+ puts "Enter `anime_dl - h` for usage options"
105
+ exit
106
+ rescue => error
107
+ puts "Error: #{error}"
108
+ puts "Enter `anime_dl - h` for usage options"
109
+ exit
110
+ end
111
+ end
112
+
113
+
114
+ # File Check
115
+ if (options[:file])
116
+
117
+ if (options[:option][0] == "l")
118
+ if ( File.directory?(options[:file]) )
119
+ unless (File.exists?(options[:file]))
120
+ puts "Directory does not exist"
121
+ exit
122
+ end
123
+ elsif ( !File.exists?( File.split(options[:file])[0] ) )
124
+ puts "Directory does not exist"
125
+ exit
126
+ end
127
+
128
+ elsif ( !File.exists?(options[:file]) || !File.directory?(options[:file]) )
129
+ puts "Directory does not exist (or) argument is not a directory"
130
+ puts "For --option=download , argument to --file must be a directory"
131
+ exit
132
+
133
+ end
134
+
135
+ end
136
+
137
+
138
+ # New Instance
139
+ instance = AnimeDL::AnimeHeaven.new
140
+
141
+ # App Prompts/Help Text
142
+ unless (options[:quiet])
143
+ $user_prompts = {
144
+ search: "\nNo Results Found. Please check your search query.\n(Its worth noting that AnimeHeaven does not have some old anime)\n",
145
+ episodes: "\nEnter the desired episodes.\nYou can provide induvidual space-seperated episodes.\n" +
146
+ "Or you can also provide multiple space-seperated ranges with each range having the format 'start-end'.\n" +
147
+ "Examples (assuming anime has more than 18 episodes):\n" +
148
+ " `Episodes: 2 4 5` - Retrieves details for episodes 2, 4 and 5\n" +
149
+ " `Episodes: 4-8` - Retrieves details for episodes 4 to 8\n" +
150
+ " `Episodes: 8-l` - Retrieves details for episodes 8 to the last episode\n" +
151
+ " `Episodes: 2 9-13 18-l` - Retrieves details for episodes 2, 9 to 13 and 18 to end\n" +
152
+ "(Enter empty response to get all episodes, '-1' exits).\n\n"
153
+ }
154
+ else
155
+ $user_prompts = {
156
+ search: "No Results Found.\n",
157
+ episodes: "\n(Empty response gets all episodes, '-1' exits).\n"
158
+ }
159
+ end
160
+
161
+
162
+ # UTILITY METHODS
163
+ # Result Print
164
+ def output(results)
165
+ puts "0. (Exit)\n"
166
+ for i in 0...results.length do
167
+ print i+1, ". ", results[i].text
168
+ puts
169
+ end
170
+
171
+ puts "#{results.length + 1}. (Enter new Search Query)\n"
172
+ print $user_prompts[:search] if (results.length == 0)
173
+ puts "\n"
174
+ end
175
+
176
+
177
+ # Option Check
178
+ def check(input, max)
179
+ exit if (input == "0")
180
+ return 1 if ( (1..(max + 1)).include? input.to_i )
181
+
182
+ puts "Invalid input.\n\n"
183
+ end
184
+
185
+
186
+ # Write links to file
187
+ def file_write(file, links, choice)
188
+ details = ""
189
+ puts links.pop.link if (links.last.number == nil)
190
+ exit if links.empty?
191
+
192
+ for l in links
193
+ details += l.details
194
+ end
195
+
196
+ puts "Writing"
197
+ if ( File.directory?(file) )
198
+ file = File.new( File.join(file, choice.content.split(" | ")[0] + ".txt"), "a+" )
199
+ file.write(details + "\n")
200
+ file.close
201
+ else
202
+ file = File.new(file, "a+")
203
+ file.write(details + "\n")
204
+ file.close
205
+ end
206
+ end
207
+
208
+
209
+
210
+ # APP LOGIC
211
+ # Search
212
+ while true
213
+ print "Enter Search Query: "
214
+ search_query = gets.strip.split(" ").join("+")
215
+ puts "\n"
216
+ results = instance.search(search_query)
217
+ output(results)
218
+ puts "\n"
219
+
220
+ # Anime Choice
221
+ while true
222
+ print "Choice: "
223
+ input = gets.strip
224
+ break if (check(input, results.length) == 1)
225
+ end
226
+
227
+ # Re-enter search query
228
+ break unless (input.to_i == results.length + 1)
229
+ puts "\n"
230
+ end
231
+
232
+ # Choice as Nokogiri Element
233
+ choice = results[input.to_i - 1]
234
+
235
+ # Range
236
+ range = []
237
+ if (options[:episode])
238
+ puts "\n"
239
+ print $user_prompts[:episodes]
240
+ puts "\n"
241
+
242
+ first, last = instance.getTotal(choice)
243
+ puts "Total number of Episodes - #{last - first + 1} (#{first}-#{last})"
244
+ print "Episodes: "
245
+ range = gets.strip
246
+ exit if range == "-1"
247
+
248
+ range = range.split(" ")
249
+ end
250
+
251
+
252
+ # Data Retrieval
253
+ puts "Scraping: " + choice.content + "\n\n"
254
+
255
+ if ( options[:option][0] == 'l' && !options[:file])
256
+ to_print = true
257
+ else
258
+ to_print = false
259
+ end
260
+
261
+ # Links
262
+ links = instance.getLinks(choice, range, options[:quiet], to_print)
263
+ file_write(options[:file], links, choice) if (options[:file] && options[:option][0] == 'l')
264
+
265
+ # Downloads
266
+ if ( options[:option][0] == 'd' )
267
+ if (options[:file] == nil)
268
+ options[:file] = choice.content.split(" | ")[0]
269
+ Dir.mkdir( options[:file] )
270
+ end
271
+
272
+ AnimeDL.download(options[:file], links)
273
+ end
274
+
275
+
276
+ END { puts "Exiting" }
@@ -0,0 +1,90 @@
1
+ require "mechanize"
2
+ require "anime_dl/version"
3
+ require "anime_dl/anime_heaven"
4
+ require "episode"
5
+ # Methods to get links in 'anime_dl/#{anime_site}.rb'
6
+
7
+ module AnimeDL
8
+ class AnimeHeaven
9
+ def initialize
10
+ @agent = Mechanize.new
11
+ @agent.user_agent_alias = 'Windows Chrome'
12
+ @anime_page = nil
13
+ end
14
+
15
+ # Search
16
+ def search(query)
17
+ results_page = @agent.get("http://animeheaven.eu/search.php?q=#{query}")
18
+ return results_page.search(".iepsan")
19
+ end
20
+
21
+ # Returns the total number of episodes
22
+ def getTotal(option = nil)
23
+ @anime_page = @agent.get(option.attributes['href'].value) unless (@anime_page)
24
+ episodes = @anime_page.search(".infoepbox").search("a")
25
+
26
+ first = episodes.last.search(".infoept2")[0].content.to_i
27
+ last = episodes.first.search(".infoept2")[0].content.to_i
28
+ return first, last
29
+ end
30
+
31
+
32
+ # UTILITY
33
+ def limit_exceeded(quiet)
34
+ return "Unfortunately \"animeheaven.eu\" only allows a certain number of page views per day.\n" +
35
+ "It seems you have exceeded that limit :(.\n" +
36
+ "Please change networks or try again in 24 hours." if (!quiet)
37
+
38
+ return "(Limit exceeded)"
39
+ end
40
+
41
+ end
42
+
43
+ # Download
44
+ def self.download(path, episodes)
45
+ episodes.each do |episode|
46
+ if ( File.exists?( File.join( path, "Episode #{episode.number}.mp4" )) )
47
+ puts "Skipping Episode #{episode.number} (already exists)"
48
+ next
49
+ else
50
+ puts "Downloading Episode #{episode.number}"
51
+ end
52
+
53
+ status = episode.download_to(path)
54
+ return if status == -1
55
+ end
56
+ end
57
+
58
+ # Episode Range Handler
59
+ def self.range_handle(range, first, last)
60
+ temp = []
61
+
62
+ range.each do |arg|
63
+ # Basic REGEX match
64
+ unless (arg.match(/\A[0-9]+\Z|\A[0-9]+\-[0-9l]+\Z/))
65
+ puts "'#{arg}' is an invalid input for 'Episodes'"
66
+ next
67
+ end
68
+
69
+ if (arg.include?("-"))
70
+ f, l = arg.split("-").collect(&:to_i)
71
+ l = last if (arg[-1] == "l")
72
+
73
+ next unless (f && l)
74
+ next if f > last
75
+ l = last if l > last
76
+
77
+ (f..l).each do |n|
78
+ temp << n if ( (first..last).include? n )
79
+ end
80
+
81
+ else
82
+ temp << arg.to_i if ( (first..last).include? arg.to_i )
83
+
84
+ end
85
+ end
86
+
87
+ return temp.uniq.sort
88
+ end
89
+
90
+ end
@@ -0,0 +1,48 @@
1
+ module AnimeDL
2
+ class AnimeHeaven
3
+
4
+ # Return the required page links based on user input
5
+ def getPageLinks(option, range)
6
+ @anime_page = @agent.get(option.attributes['href'].value) unless (@anime_page)
7
+ page_links = @anime_page.search(".infoepbox").search("a").reverse
8
+ return page_links if range.empty?
9
+
10
+ links_required = []
11
+ first, last = getTotal(option)
12
+ range = AnimeDL.range_handle(range, first, last)
13
+ range.each { |num| links_required << page_links[num - first] }
14
+
15
+ return links_required
16
+ end
17
+
18
+ # Video Links
19
+ def getLinks(option, range, quiet = false, output = false)
20
+ episode_links = getPageLinks(option, range)
21
+ episodes = []
22
+
23
+ puts "Fetching Links..." unless (output)
24
+
25
+ episode_links.each do |link|
26
+ episode_page = @agent.get(link.attributes['href'].value)
27
+
28
+ # Limit Check
29
+ if (episode_page.search(".c").search("b").length != 0)
30
+ episodes << Episode.new(nil, limit_exceeded(quiet))
31
+ puts limit_exceeded(quiet) if (output)
32
+ break
33
+ end
34
+
35
+ # Video Link Retrieval
36
+ episode_no = episode_page.uri.to_s[/e=.+/][2..-1] # (Regex for safety)
37
+ video_src = episode_page.search("script")[2].to_s[/src='http.*?'/][5...-1]
38
+
39
+ episode = Episode.new(episode_no, video_src)
40
+ episodes << episode
41
+ puts episode.details if (output)
42
+ end
43
+
44
+ return episodes
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1,3 @@
1
+ module AnimeDL
2
+ VERSION = "0.1.1"
3
+ end
@@ -0,0 +1,30 @@
1
+ require "open-uri"
2
+
3
+ class Episode
4
+ def initialize(episode_no, link)
5
+ @number = episode_no
6
+ @link = link
7
+ end
8
+ attr_reader :number, :link
9
+
10
+ def download_to(path = ".")
11
+ # Limit Check
12
+ unless @number
13
+ puts @link
14
+ return -1
15
+ end
16
+
17
+ open("#{@link}") do |video|
18
+ file = File.new( File.join( path, "Episode #{@number}.mp4" ), 'w')
19
+ puts "Writing"
20
+ file.write(video.read)
21
+ file.close
22
+ end
23
+
24
+ return 1
25
+ end
26
+
27
+ def details
28
+ return "Episode #{@number}: #{@link}\n"
29
+ end
30
+ end
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: AnimeDL
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Anirudh Sundar
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-08-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: mechanize
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.7'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.7'
27
+ description:
28
+ email: anirudhsundar@hotmail.com
29
+ executables:
30
+ - anime_dl
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - ".gitignore"
35
+ - LICENSE.txt
36
+ - README.md
37
+ - anime_dl.gemspec
38
+ - bin/anime_dl
39
+ - lib/anime_dl.rb
40
+ - lib/anime_dl/anime_heaven.rb
41
+ - lib/anime_dl/version.rb
42
+ - lib/episode.rb
43
+ homepage: https://github.com/anirudhsundar98/AnimeDL
44
+ licenses:
45
+ - MIT
46
+ metadata: {}
47
+ post_install_message:
48
+ rdoc_options: []
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ requirements: []
62
+ rubyforge_project:
63
+ rubygems_version: 2.6.8
64
+ signing_key:
65
+ specification_version: 4
66
+ summary: The AnimeDL gem is used to get episode links or download episodes for a particular
67
+ anime
68
+ test_files: []