AnimeDL 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []