imdb-terminal 0.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.
- checksums.yaml +7 -0
- data/README.md +60 -0
- data/bin/imdb +787 -0
- metadata +69 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 668a4e977795d0075d70382890a72e911688c452d67cb02ba432f1cfefdacd01
|
4
|
+
data.tar.gz: 4d1ac685489c8b509349841fc55e00285576ed9be0815802740731e5762cb954
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 627fbd556497607248a0ebb78012d0ce9b5577eab31a0b8f8446c0b65b1aaba6bb78cc25216d0c115db672a6b179e21b468f86727ce09a2f397d98a0a8ceb893
|
7
|
+
data.tar.gz: 87531c704b0b7b1d5bea5d2a8bd91b656f08a6ded7d4639c2441ca3923a6c282920bdbe74c430a70615e117e58c272fa468f7f27bda0a7c1735ec5f8e4a6fbb8
|
data/README.md
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
|
2
|
+
 [](https://badge.fury.io/rb/imdb-terminal)  
|
3
|
+
|
4
|
+
# IMDB for the terminal
|
5
|
+
|
6
|
+
Finding the right moviie to watch or series to binge can be time consuming. My
|
7
|
+
girlfriend asked if I could create an application that would cut down the time
|
8
|
+
spent on searching in favor of time spent watching and cuddling. Meet
|
9
|
+
IMDB for the terminal.
|
10
|
+
|
11
|
+
Narrow down your preferences from a 1000 movies and almost 500 series. Select
|
12
|
+
a minimum IMDB rating, range of production years, genres you like and dislike
|
13
|
+
to get your preferred list. Get detailed information on movies and series and
|
14
|
+
where you can stream them. Even the movie poster in the terminal.
|
15
|
+
|
16
|
+
IMDB for the terminal uses three APIs - one to scrape IMDB for the top 1000
|
17
|
+
lists, one to give you details on the movies and series and one to show you
|
18
|
+
where you can stream it. When you first run the application, it will guide you
|
19
|
+
through the process of registering free accounts and get the three API keys
|
20
|
+
needed. I would be happy to keys in the application, but there is a limit to
|
21
|
+
the number of calls each day, so you need your own keys. The key to scrape the
|
22
|
+
IMDB top 1000 is via `page2api`. You will get a certain amounts of credits for
|
23
|
+
free when you sign up, and it will last you a very long time since the program
|
24
|
+
caches the movies and series lists. You could probably do well with refreshing
|
25
|
+
the lists once per month or so (using the key `I`).
|
26
|
+
|
27
|
+
As for the application itself, it's a terminal curses program that is best
|
28
|
+
installed with `gem install imdb-terminal` and run the program `imdb`in the
|
29
|
+
terminal. Or you can clone this repo and run the single file `imdb`. As usual,
|
30
|
+
I try to keep the requirements to a minimum and the program to a single file.
|
31
|
+
|
32
|
+
# Screenshot
|
33
|
+
The screenshot gives you an overview of the layout of the panes:
|
34
|
+

|
35
|
+
|
36
|
+
# Keymap
|
37
|
+
|
38
|
+
Keys | Function
|
39
|
+
-----------|--------------------
|
40
|
+
TAB | Cycle the panes (active pane is gray)
|
41
|
+
Shift-TAB | Cycle backward
|
42
|
+
Arrow keys | UP, DOWN, PgUP, PgDOWN, HOME, END in lists
|
43
|
+
\+ or - | Depends on pane (intuitive)
|
44
|
+
I | Load fresh IMDB data (be patient)
|
45
|
+
m or s | Show MOVIES or SERIES
|
46
|
+
r | Select MINimum IMDB rating (in bottom line)
|
47
|
+
y or Y | Select MINimum or MAXimum production year
|
48
|
+
/ or \\ | Enter or clear search for movie/series title
|
49
|
+
G | Set genres to match every movie/series
|
50
|
+
d | Get details on selected movie/series
|
51
|
+
D | Show where you can stream the movie/series
|
52
|
+
R | Refresh all panes
|
53
|
+
w or W | Write changes to config or save IMDB data
|
54
|
+
q or Q | Quit w/saving config or w/o saving config
|
55
|
+
|
56
|
+
# Suggestions and improvements
|
57
|
+
If you have any suggestions for improvement or cool features, create an issue
|
58
|
+
in this repository.
|
59
|
+
|
60
|
+
For more stuff, check out https://isene.com
|
data/bin/imdb
ADDED
@@ -0,0 +1,787 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
# PROGRAM INFO
|
5
|
+
# Name: IMDB-term
|
6
|
+
# Language: Pure Ruby, best viewed in VIM
|
7
|
+
# Author: Geir Isene <g@isene.com>
|
8
|
+
# Web_site: http://isene.com/
|
9
|
+
# Github: https://github.com/isene/imdb-term
|
10
|
+
# License: I release all copyright claims. This code is in the public domain.
|
11
|
+
# Permission is granted to use, copy modify, distribute, and sell
|
12
|
+
# this software for any purpose. I make no guarantee about the
|
13
|
+
# suitability of this software for any purpose and I am not liable
|
14
|
+
# for any damages resulting from its use. Further, I am under no
|
15
|
+
# obligation to maintain or extend this software. It is provided
|
16
|
+
# on an 'as is' basis without any expressed or implied warranty.
|
17
|
+
|
18
|
+
# PRELIMINARIES
|
19
|
+
@help = <<HELPTEXT
|
20
|
+
IMDB-term (https://github.com/isene/IMDB) Help text:
|
21
|
+
|
22
|
+
Keys | Function
|
23
|
+
-----------+--------------------
|
24
|
+
TAB | Cycle the panes (active pane is gray)
|
25
|
+
Shift-TAB | Cycle backward
|
26
|
+
Arrow keys | UP, DOWN, PgUP, PgDOWN, HOME, END in lists
|
27
|
+
+ or - | Depends on pane (intuitive)
|
28
|
+
I | Load fresh IMDB data (be patient)
|
29
|
+
m or s | Show MOVIES or SERIES
|
30
|
+
r | Select MINimum IMDB rating (in bottom line)
|
31
|
+
y or Y | Select MINimum or MAXimum production year
|
32
|
+
/ or \\ | Enter or clear search for movie/series title
|
33
|
+
G | Set genres to match every movie/series
|
34
|
+
d | Get details on selected movie/series
|
35
|
+
D | Show where you can stream the movie/series
|
36
|
+
R | Refresh all panes
|
37
|
+
w or W | Write changes to config or save IMDB data
|
38
|
+
q or Q | Quit w/saving config or w/o saving config
|
39
|
+
HELPTEXT
|
40
|
+
begin # BASIC SETUP
|
41
|
+
require 'net/http'
|
42
|
+
require 'open-uri'
|
43
|
+
require 'rest_client'
|
44
|
+
require 'json'
|
45
|
+
require 'readline'
|
46
|
+
require 'io/console'
|
47
|
+
require 'curses'
|
48
|
+
include Curses
|
49
|
+
|
50
|
+
def cmd?(command)
|
51
|
+
system("which #{command} > /dev/null 2>&1")
|
52
|
+
end
|
53
|
+
if cmd?('/usr/lib/w3m/w3mimgdisplay')
|
54
|
+
@w3mimgdisplay = "/usr/lib/w3m/w3mimgdisplay"
|
55
|
+
@showimage = true
|
56
|
+
else
|
57
|
+
@showimage = false
|
58
|
+
end
|
59
|
+
@showimage = false unless (cmd?('xwininfo') and cmd?('xdotool'))
|
60
|
+
|
61
|
+
begin # Check if network is available
|
62
|
+
URI.open("https://www.google.com/", :open_timeout=>5)
|
63
|
+
rescue
|
64
|
+
puts "\nNo network. Running offline.\n\n"
|
65
|
+
end
|
66
|
+
|
67
|
+
# INITIALIZE BASIC VARIABLES
|
68
|
+
## These can be set in .imdb.conf
|
69
|
+
@rating = 0
|
70
|
+
@yearMin = 0
|
71
|
+
@yearMax = 2100
|
72
|
+
@myMY = []
|
73
|
+
@myMN = []
|
74
|
+
@mySY = []
|
75
|
+
@mySN = []
|
76
|
+
## These are IMDB specific
|
77
|
+
@urlmovies = "https://www.imdb.com/search/title/?groups=top_1000&start=[1, 1001, 50]"
|
78
|
+
@urlseries = "https://www.imdb.com/search/title/?title_type=tv_series&start=[1, 1001, 50]"
|
79
|
+
@country = "no"
|
80
|
+
@lang = "en"
|
81
|
+
@genres = ["Action", "Adult", "Adventure", "Animation", "Biography", "Comedy", "Crime", "Documentary", "Drama",
|
82
|
+
"Family", "Fantasy", "Film Noir", "Game Show", "History", "Horror", "Musical", "Music", "Mystery",
|
83
|
+
"News", "Reality-TV", "Romance", "Sci-Fi", "Short", "Sport", "Talk-Show", "Thriller", "War", "Western"]
|
84
|
+
@genY = @genres.map(&:clone)
|
85
|
+
@genN = []
|
86
|
+
## These are internal variables
|
87
|
+
@imdbmovies = []
|
88
|
+
@imdbseries = []
|
89
|
+
@imdbsel = []
|
90
|
+
@search = ''
|
91
|
+
@new = false
|
92
|
+
@movies = true
|
93
|
+
@noimage = false
|
94
|
+
|
95
|
+
## Color constants (e.g. Window x fg)
|
96
|
+
WtMfg = 232
|
97
|
+
WtSfg = 255
|
98
|
+
WtMbg = 214
|
99
|
+
WtMbgS = 202
|
100
|
+
WtSbg = 23
|
101
|
+
WtSbgS = 58
|
102
|
+
Wifg = 230
|
103
|
+
Wgfg = 195
|
104
|
+
WgYfg = 34
|
105
|
+
WgNfg = 241
|
106
|
+
Wmfg = 117
|
107
|
+
Wnfg = 204
|
108
|
+
Wdfg = 255
|
109
|
+
Wpfg = 242
|
110
|
+
Wbfg = 252
|
111
|
+
Wbbg = 238
|
112
|
+
Mark = 240
|
113
|
+
WAbg = 233
|
114
|
+
WIbg = 232
|
115
|
+
end
|
116
|
+
|
117
|
+
# CLASSES
|
118
|
+
class Curses::Window # CLASS EXTENSION
|
119
|
+
attr_accessor :fg, :bg, :attr, :text, :index, :list, :update
|
120
|
+
# General extensions (see https://github.com/isene/Ruby-Curses-Class-Extension)
|
121
|
+
def clr
|
122
|
+
self.setpos(0, 0)
|
123
|
+
self.maxy.times {self.deleteln()}
|
124
|
+
self.refresh
|
125
|
+
self.setpos(0, 0)
|
126
|
+
end
|
127
|
+
def fill # Fill window with color as set by :bg
|
128
|
+
self.setpos(0, 0)
|
129
|
+
self.bg = 0 if self.bg == nil
|
130
|
+
self.fg = 255 if self.fg == nil
|
131
|
+
xor = self.fg ^ self.bg
|
132
|
+
init_pair(xor, self.fg, self.bg)
|
133
|
+
blank = " " * self.maxx
|
134
|
+
self.maxy.times {self.attron(color_pair(xor)) {self << blank}}
|
135
|
+
self.refresh
|
136
|
+
self.setpos(0, 0)
|
137
|
+
end
|
138
|
+
def write # Write context of :text to window with attributes :attr
|
139
|
+
self.bg = 0 if self.bg == nil
|
140
|
+
self.fg = 255 if self.fg == nil
|
141
|
+
xor = self.fg ^ self.bg
|
142
|
+
init_pair(xor, self.fg, self.bg)
|
143
|
+
self.attr = 0 if self.attr == nil
|
144
|
+
self.attron(color_pair(xor) | self.attr) { self << self.text }
|
145
|
+
self.refresh
|
146
|
+
self.text = ""
|
147
|
+
end
|
148
|
+
def p(fg, bg, attr, text)
|
149
|
+
init_pair(fg ^ bg, fg, bg)
|
150
|
+
self.attron(color_pair(fg ^ bg) | attr) { self << text }
|
151
|
+
self.refresh
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
# GENERIC FUNCTIONS
|
156
|
+
def firstrun
|
157
|
+
puts "Welcome to IMDB-term, the IMDB application for the terminal."
|
158
|
+
puts "\nFind your next movie or series to binge. Narrow down your preferences from a 1000 movies and almost 500 series."
|
159
|
+
puts "Select a minimum IMDB rating, range of production years, genres you like and dislike to get your preferred list."
|
160
|
+
puts "Get detailed information on movies and series and where you can stream them. Even the movie poster in the terminal."
|
161
|
+
puts "\nLet's first look at the help text (accessible in the program via the key '?'):\n\n"
|
162
|
+
puts @help
|
163
|
+
print "\nPress any key... "; STDIN.getch
|
164
|
+
system("clear")
|
165
|
+
puts "We will now walk you through the steps you need to do to make use of this application:"
|
166
|
+
puts "\n 1. Go to the website https://www.page2api.com/"
|
167
|
+
print " Create a free account and paste your API_KEY here (then press ENTER): "
|
168
|
+
conf = "@imdbkey = '"
|
169
|
+
conf += gets.chomp + "'\n"
|
170
|
+
puts "\n 2. Go to the website https://www.omdbapi.com/apikey.aspx"
|
171
|
+
print " Create a free account and paste your API KEY here (then press ENTER): "
|
172
|
+
conf += "@omdbkey = '"
|
173
|
+
conf += gets.chomp + "'\n"
|
174
|
+
puts "\n 3. Go to the website https://rapidapi.com/movie-of-the-night-movie-of-the-night-default/api/streaming-availability"
|
175
|
+
print " Create a free account and paste your X-RapidAPI-Key here (then press ENTER): "
|
176
|
+
conf += "@streamkey = '"
|
177
|
+
conf += gets.chomp + "'\n"
|
178
|
+
File.write(Dir.home+'/.imdb.conf', conf)
|
179
|
+
puts "\n\nYour keys have now been written to the configuration file (.imdb.conf). You can edit this file manually if needed."
|
180
|
+
print "\nPress 'y' to start imdb-term "; y = STDIN.getch
|
181
|
+
exit if y != "y"
|
182
|
+
end
|
183
|
+
def getchr # PROCESS KEY PRESSES
|
184
|
+
c = STDIN.getch #(min: 0, time: 1)
|
185
|
+
case c
|
186
|
+
when "\e" # ANSI escape sequences
|
187
|
+
case $stdin.getc
|
188
|
+
when '[' # CSI
|
189
|
+
case $stdin.getc
|
190
|
+
when 'A' then chr = "UP"
|
191
|
+
when 'B' then chr = "DOWN"
|
192
|
+
when 'C' then chr = "RIGHT"
|
193
|
+
when 'D' then chr = "LEFT"
|
194
|
+
when 'Z' then chr = "S-TAB"
|
195
|
+
when '2' then chr = "INS" ; STDIN.getc
|
196
|
+
when '3' then chr = "DEL" ; STDIN.getc
|
197
|
+
when '5' then chr = "PgUP" ; STDIN.getc
|
198
|
+
when '6' then chr = "PgDOWN" ; STDIN.getc
|
199
|
+
when '7' then chr = "HOME" ; STDIN.getc
|
200
|
+
when '8' then chr = "END" ; STDIN.getc
|
201
|
+
end
|
202
|
+
end
|
203
|
+
when "", "" then chr = "BACK"
|
204
|
+
when "" then chr = "C-C"
|
205
|
+
when "" then chr = "C-G"
|
206
|
+
when "" then chr = "C-T"
|
207
|
+
when "" then chr = "LDEL"
|
208
|
+
when "" then chr = "WBACK"
|
209
|
+
when "\r" then chr = "ENTER"
|
210
|
+
when "\t" then chr = "TAB"
|
211
|
+
when /./ then chr = c
|
212
|
+
end
|
213
|
+
return chr
|
214
|
+
end
|
215
|
+
def getkey # GET KEY FROM USER
|
216
|
+
chr = getchr
|
217
|
+
case chr
|
218
|
+
when '?' # Show helptext in right window
|
219
|
+
@w_d.fill
|
220
|
+
@w_d.text = @help
|
221
|
+
@w_d.write
|
222
|
+
@w_d.update = false
|
223
|
+
when 'UP'
|
224
|
+
@active.index = @active.index <= 0 ? @active.list.size - 1 : @active.index - 1
|
225
|
+
when 'DOWN'
|
226
|
+
@active.index = @active.index >= @active.list.size - 1 ? 0 : @active.index + 1
|
227
|
+
when 'PgUP'
|
228
|
+
@active.index -= @active.maxy - 2
|
229
|
+
@active.index = 0 if @active.index < 0
|
230
|
+
when 'PgDOWN'
|
231
|
+
@active.index += @active.maxy - 2
|
232
|
+
@active.index = @active.list.size - 1 if @active.index > @active.list.size
|
233
|
+
when 'HOME'
|
234
|
+
@active.index = 0
|
235
|
+
when 'END'
|
236
|
+
@active.index = @active.list.size - 1
|
237
|
+
when 'TAB'
|
238
|
+
case @active
|
239
|
+
when @w_i
|
240
|
+
@active = @w_g
|
241
|
+
when @w_g
|
242
|
+
@active = @w_m
|
243
|
+
when @w_m
|
244
|
+
@active = @w_n
|
245
|
+
when @w_n
|
246
|
+
@active = @w_i
|
247
|
+
end
|
248
|
+
when 'S-TAB'
|
249
|
+
case @active
|
250
|
+
when @w_i
|
251
|
+
@active = @w_n
|
252
|
+
when @w_n
|
253
|
+
@active = @w_m
|
254
|
+
when @w_m
|
255
|
+
@active = @w_g
|
256
|
+
when @w_g
|
257
|
+
@active = @w_i
|
258
|
+
end
|
259
|
+
when 'I'
|
260
|
+
w_b("Loading IMDB data...")
|
261
|
+
loadimdb
|
262
|
+
when 'm'
|
263
|
+
@movies = true
|
264
|
+
when 's'
|
265
|
+
@movies = false
|
266
|
+
when 'r'
|
267
|
+
r = w_b_getstr(" Set MINimum Rating: ", "")
|
268
|
+
@rating = r.to_f unless r == ""
|
269
|
+
when 'y'
|
270
|
+
y = w_b_getstr(" Set MINimum Year: ", "")
|
271
|
+
@yearMin = y.to_i unless y == ""
|
272
|
+
when 'Y'
|
273
|
+
y = w_b_getstr(" Set MAXimum Year: ", "")
|
274
|
+
@yearMax = y.to_i unless y == ""
|
275
|
+
when '/'
|
276
|
+
@search = w_b_getstr(" Search for title: ", "")
|
277
|
+
when '\\'
|
278
|
+
@search = ''
|
279
|
+
when 'G'
|
280
|
+
@genY = @genres.map(&:clone)
|
281
|
+
@genN = []
|
282
|
+
when '+'
|
283
|
+
case @active
|
284
|
+
when @w_i
|
285
|
+
@myY.push(@active.list[@active.index])
|
286
|
+
@active.index = 0 if @active.index > @active.list.size - 2
|
287
|
+
when @w_g
|
288
|
+
@genY.push(@active.list[@active.index]) unless @genN.include?(@active.list[@active.index])
|
289
|
+
@genN.delete(@active.list[@active.index])
|
290
|
+
@active.index = @active.index >= @active.list.size - 1 ? 0 : @active.index + 1
|
291
|
+
when @w_m
|
292
|
+
@myN.delete(@active.list[@active.index])
|
293
|
+
when @w_n
|
294
|
+
@myY.push(@active.list[@active.index])
|
295
|
+
@myN.delete(@active.list[@active.index])
|
296
|
+
@active.index -= 1 if @active.index == @active.list.size
|
297
|
+
end
|
298
|
+
when '-'
|
299
|
+
case @active
|
300
|
+
when @w_i
|
301
|
+
@myN.push(@active.list[@active.index])
|
302
|
+
@active.index = 0 if @active.index > @active.list.size - 2
|
303
|
+
when @w_g
|
304
|
+
@genN.push(@active.list[@active.index]) unless @genY.include?(@active.list[@active.index])
|
305
|
+
@genY.delete(@active.list[@active.index])
|
306
|
+
@active.index = @active.index >= @active.list.size - 1 ? 0 : @active.index + 1
|
307
|
+
when @w_m
|
308
|
+
@myY.delete(@active.list[@active.index])
|
309
|
+
@active.index -= 1 if @active.index == @active.list.size
|
310
|
+
when @w_n
|
311
|
+
@myN.delete(@active.list[@active.index])
|
312
|
+
@active.index -= 1 if @active.index == @active.list.size
|
313
|
+
end
|
314
|
+
when 'd'
|
315
|
+
@w_d.fill
|
316
|
+
w_d(1)
|
317
|
+
@w_d.update = false
|
318
|
+
when 'D'
|
319
|
+
@w_d.fill
|
320
|
+
w_d(2)
|
321
|
+
@w_d.update = false
|
322
|
+
when 'R' # Refresh all windows
|
323
|
+
@break = true
|
324
|
+
when '@' # Enter "Ruby debug"
|
325
|
+
cmd = w_b_getstr("◆ ", "")
|
326
|
+
begin
|
327
|
+
@w_b.text = eval(cmd)
|
328
|
+
@w_b.fill
|
329
|
+
@w_b.write
|
330
|
+
rescue StandardError => e
|
331
|
+
w_b("Error: #{e.inspect}")
|
332
|
+
end
|
333
|
+
@w_b.update = false
|
334
|
+
when 'w'
|
335
|
+
saveconf
|
336
|
+
when 'W'
|
337
|
+
saveimdb
|
338
|
+
when 'q' # Exit
|
339
|
+
saveconf
|
340
|
+
exit 0
|
341
|
+
when 'Q' # EXIT
|
342
|
+
exit 0
|
343
|
+
end
|
344
|
+
end
|
345
|
+
def getimdb(url)
|
346
|
+
api_url = "https://www.page2api.com/api/v1/scrape"
|
347
|
+
payload = {
|
348
|
+
api_key: @imdbkey,
|
349
|
+
batch: {
|
350
|
+
urls: url,
|
351
|
+
concurrency: 1,
|
352
|
+
merge_results: true
|
353
|
+
},
|
354
|
+
parse: {
|
355
|
+
movies: [
|
356
|
+
{
|
357
|
+
title: ".lister-item-header >> text",
|
358
|
+
url: ".lister-item-header a >> href",
|
359
|
+
id: ".userRatingValue >> data-tconst",
|
360
|
+
year: ".lister-item-year >> text",
|
361
|
+
genre: ".genre >> text",
|
362
|
+
votes: "[name=nv] >> text",
|
363
|
+
rating: ".ratings-imdb-rating >> data-value",
|
364
|
+
_parent: ".lister-item",
|
365
|
+
runtime: ".runtime >> text",
|
366
|
+
certificate: ".certificate >> text"
|
367
|
+
}
|
368
|
+
]
|
369
|
+
},
|
370
|
+
datacenter_proxy: "us"
|
371
|
+
}
|
372
|
+
response = RestClient::Request.execute(
|
373
|
+
method: :post,
|
374
|
+
payload: payload.to_json,
|
375
|
+
url: api_url,
|
376
|
+
headers: { "Content-type" => "application/json" },
|
377
|
+
).body
|
378
|
+
res = JSON.parse(response)
|
379
|
+
imdb = []
|
380
|
+
res["result"]["movies"].each {|m| imdb.push [m["title"].sub(/^\d+\. /, ''), m["rating"].to_f, m["year"].delete("^0-9").to_i, m["genre"], m["id"]]}
|
381
|
+
imdb
|
382
|
+
end
|
383
|
+
def loadimdb
|
384
|
+
@imdbmovies = getimdb(@urlmovies)
|
385
|
+
@imdbseries = getimdb(@urlseries)
|
386
|
+
@new = true
|
387
|
+
end
|
388
|
+
def imdbmovies
|
389
|
+
if @search == ''
|
390
|
+
@imdbsel = @imdbmovies.map{|m| m if (m[1] >= @rating) and ((@yearMin..@yearMax) === m[2])}
|
391
|
+
else
|
392
|
+
@imdbsel = @imdbmovies.map{|m| m if (m[0] =~ /#{@search}/)}
|
393
|
+
end
|
394
|
+
@imdbsel.select! do |i|
|
395
|
+
ig = i[3].split(", ") unless i == nil
|
396
|
+
(ig & @genY).any? unless ig == nil
|
397
|
+
end
|
398
|
+
@imdbsel.select! {|i| (i[3].split(", ") & @genN).empty?}
|
399
|
+
@imdbsel = @imdbsel - @myMN - @myMY
|
400
|
+
@myMYsel = @myMY - @myMN
|
401
|
+
[@imdbsel, @myMYsel, @myMY, @myMN, @genY, @genN].each do |arr|
|
402
|
+
arr.uniq!
|
403
|
+
arr.compact!
|
404
|
+
arr.sort_by! {|m| m[0]}
|
405
|
+
end
|
406
|
+
@w_i.list = @imdbsel
|
407
|
+
@w_i.index = 0 if @w_i.index > @w_i.list.size - 2
|
408
|
+
@w_m.list = @myMYsel
|
409
|
+
@w_n.list = @myMN
|
410
|
+
@myN = @myMN
|
411
|
+
@myY = @myMY
|
412
|
+
end
|
413
|
+
def imdbseries
|
414
|
+
if @search == ''
|
415
|
+
@imdbsel = @imdbseries.map{|m| m if (m[1] > @rating) and ((@yearMin..@yearMax) === m[2])}
|
416
|
+
else
|
417
|
+
@imdbsel = @imdbseries.map{|m| m if (m[0] =~ /#{@search}/)}
|
418
|
+
end
|
419
|
+
@imdbsel.select! do |i|
|
420
|
+
ig = i[3].split(", ") unless i == nil
|
421
|
+
(ig & @genY).any? unless ig == nil
|
422
|
+
end
|
423
|
+
@imdbsel.select! {|i| (i[3].split(", ") & @genN).empty?}
|
424
|
+
@imdbsel = @imdbsel - @mySN - @mySY
|
425
|
+
@mySYsel = @mySY - @mySN
|
426
|
+
[@imdbsel, @mySYsel, @mySY, @mySN, @genY, @genN].each do |arr|
|
427
|
+
arr.uniq!
|
428
|
+
arr.compact!
|
429
|
+
arr.sort_by! {|m| m[0]}
|
430
|
+
end
|
431
|
+
@w_i.list = @imdbsel
|
432
|
+
@w_i.index = 0 if @w_i.index > @w_i.list.size - 2
|
433
|
+
@w_m.list = @mySYsel
|
434
|
+
@w_n.list = @mySN
|
435
|
+
@myN = @mySN
|
436
|
+
@myY = @mySY
|
437
|
+
end
|
438
|
+
def getomdb(id)
|
439
|
+
@urldetails = "http://www.omdbapi.com/?apikey=#{@omdbkey}&i=#{id}"
|
440
|
+
details = Net::HTTP.get(URI(@urldetails))
|
441
|
+
det = JSON.parse(details)
|
442
|
+
return det
|
443
|
+
end
|
444
|
+
def getstreaming(id) # Returns array of outlets
|
445
|
+
url = URI("https://streaming-availability.p.rapidapi.com/v2/get/basic?country=#{@country}&imdb_id=#{id}&output_language=#{@lang}")
|
446
|
+
http = Net::HTTP.new(url.host, url.port)
|
447
|
+
http.use_ssl = true
|
448
|
+
request = Net::HTTP::Get.new(url)
|
449
|
+
request["X-RapidAPI-Key"] = @streamkey
|
450
|
+
request["X-RapidAPI-Host"] = 'streaming-availability.p.rapidapi.com'
|
451
|
+
response = http.request(request)
|
452
|
+
res = JSON.parse(response.read_body)
|
453
|
+
outlets = []
|
454
|
+
begin
|
455
|
+
res["result"]["streamingInfo"][@country].each{|k,v| outlets.push(k)}
|
456
|
+
return outlets
|
457
|
+
rescue
|
458
|
+
return ["No outlet info"]
|
459
|
+
end
|
460
|
+
end
|
461
|
+
def saveconf
|
462
|
+
if File.exist?(Dir.home+'/.imdb.conf')
|
463
|
+
conf = File.read(Dir.home+'/.imdb.conf')
|
464
|
+
else
|
465
|
+
conf = ""
|
466
|
+
end
|
467
|
+
conf.gsub!(/^@rating.*\n/, "")
|
468
|
+
conf += "@rating = #{@rating}\n"
|
469
|
+
conf.gsub!(/^@yearMin.*\n/, "")
|
470
|
+
conf += "@yearMin = #{@yearMin}\n"
|
471
|
+
conf.gsub!(/^@yearMax.*\n/, "")
|
472
|
+
conf += "@yearMax = #{@yearMax}\n"
|
473
|
+
conf.gsub!(/^@genY.*\n/, "")
|
474
|
+
conf += "@genY = #{@genY}\n"
|
475
|
+
conf.gsub!(/^@genN.*\n/, "")
|
476
|
+
conf += "@genN = #{@genN}\n"
|
477
|
+
conf.gsub!(/^@myMY.*\n/, "")
|
478
|
+
conf += "@myMY = #{@myMY}\n"
|
479
|
+
conf.gsub!(/^@myMN.*\n/, "")
|
480
|
+
conf += "@myMN = #{@myMN}\n"
|
481
|
+
conf.gsub!(/^@mySY.*\n/, "")
|
482
|
+
conf += "@mySY = #{@mySY}\n"
|
483
|
+
conf.gsub!(/^@mySN.*\n/, "")
|
484
|
+
conf += "@mySN = #{@mySN}\n"
|
485
|
+
w_b("Configuration written to .imdb.conf")
|
486
|
+
File.write(Dir.home+'/.imdb.conf', conf)
|
487
|
+
end
|
488
|
+
def saveimdb
|
489
|
+
if File.exist?(Dir.home+'/.imdb')
|
490
|
+
data = File.read(Dir.home+'/.imdb')
|
491
|
+
else
|
492
|
+
data = ""
|
493
|
+
end
|
494
|
+
data.gsub!(/^@imdbmovies.*\n/, "")
|
495
|
+
data += "@imdbmovies = #{@imdbmovies}\n"
|
496
|
+
data.gsub!(/^@imdbseries.*\n/, "")
|
497
|
+
data += "@imdbseries = #{@imdbseries}\n"
|
498
|
+
w_b("IMDB data written to .imdb")
|
499
|
+
File.write(Dir.home+'/.imdb', data)
|
500
|
+
end
|
501
|
+
|
502
|
+
# BASIC WINDOW FUNCTIONS
|
503
|
+
def w_t # SHOW INFO IN @w_t
|
504
|
+
@movies ? @w_t.text = " MOVIES :: " : @w_t.text = " SERIES :: "
|
505
|
+
@w_t.text += "Rating MIN: #{@rating} - Year MIN: #{@yearMin} - Year MAX: #{@yearMax} :: Selection = #{@imdbsel.size}"
|
506
|
+
@w_t.write
|
507
|
+
end
|
508
|
+
def w_list(win) # LIST IN WINDOW
|
509
|
+
win == @active ? win.bg = WAbg : win.bg = WIbg
|
510
|
+
win.fill
|
511
|
+
ix = 0
|
512
|
+
ix = win.index - win.maxy/2 if win.index > win.maxy/2 and win.list.size > win.maxy - 1
|
513
|
+
while ix < win.list.size and ix < win.maxy do
|
514
|
+
str = win.list[ix][0]
|
515
|
+
str = win.list[ix] if win == @w_g
|
516
|
+
if ix == win.index and win == @active
|
517
|
+
win.text = "→ "
|
518
|
+
win.write
|
519
|
+
else
|
520
|
+
win.attr = 0
|
521
|
+
win.text = " "
|
522
|
+
win.write
|
523
|
+
end
|
524
|
+
if win == @w_g
|
525
|
+
if @genY.include?(str)
|
526
|
+
win.text = "+"
|
527
|
+
win.fg = WgYfg
|
528
|
+
win.attr = Curses::A_BOLD
|
529
|
+
win.write
|
530
|
+
elsif @genN.include?(str)
|
531
|
+
win.text = "-"
|
532
|
+
win.fg = WgNfg
|
533
|
+
win.write
|
534
|
+
else
|
535
|
+
win.text = " "
|
536
|
+
win.write
|
537
|
+
win.fg = Wgfg
|
538
|
+
end
|
539
|
+
end
|
540
|
+
win.attr = win.attr | Curses::A_UNDERLINE if ix == win.index
|
541
|
+
str = str[0..(win.maxx - 6)] + "…" if str.length > win.maxx - 4
|
542
|
+
win.text = str
|
543
|
+
win.write
|
544
|
+
win.attr = 0
|
545
|
+
win.text = " " * (win.maxx - str.length - 2)
|
546
|
+
win.text = win.text[0..-2] if win == @w_g
|
547
|
+
win.write
|
548
|
+
ix += 1
|
549
|
+
end
|
550
|
+
if win.index > win.maxy/2
|
551
|
+
win.setpos(0, win.maxx - 1)
|
552
|
+
win.text = "∆"
|
553
|
+
fg0 = win.fg
|
554
|
+
win.fg = Mark
|
555
|
+
win.write
|
556
|
+
win.fg = fg0
|
557
|
+
end
|
558
|
+
if win.list.length > win.maxy - 1 and win.list.length > win.index + win.maxy/2 - 1
|
559
|
+
win.setpos(win.maxy - 1, win.maxx - 1)
|
560
|
+
win.text = "∇"
|
561
|
+
fg0 = win.fg
|
562
|
+
win.fg = Mark
|
563
|
+
win.write
|
564
|
+
win.fg = fg0
|
565
|
+
end
|
566
|
+
end
|
567
|
+
def w_d(ext = 0) # SHOW INFO IN @w_d and @w_p
|
568
|
+
return if @active == @w_g
|
569
|
+
list = @active.list
|
570
|
+
return if list.empty? # Skip if list is empty
|
571
|
+
id = list[@active.index][4]
|
572
|
+
@w_d.text = "#{list[@active.index][0]}\n\n"
|
573
|
+
@w_d.attr = Curses::A_BOLD
|
574
|
+
@w_d.write
|
575
|
+
@w_d.attr = 0
|
576
|
+
@w_d.text += "Rating: " + list[@active.index][1].to_s.ljust(14) + "Genres: #{list[@active.index][3]}\n"
|
577
|
+
@w_d.write
|
578
|
+
return unless ext > 0 # Skip if no details are to be displayed
|
579
|
+
det = getomdb(id)
|
580
|
+
otl = getstreaming(id)
|
581
|
+
@w_d.text = "Rated: " + det["Rated"].ljust(14) + "Runtime: #{det["Runtime"]}"
|
582
|
+
@w_d.text += " (#{det["totalSeasons"]})" unless det["totalSeasons"] == nil
|
583
|
+
@w_d.text += "\n"
|
584
|
+
@w_d.text += "Released: " + det["Released"].ljust(14) + "(#{id})\n\n"
|
585
|
+
width = Curses.cols - 104
|
586
|
+
@w_d.text += det["Plot"].gsub!(/(.{1,#{width}})( +|$\n?)|(.{1,#{width}})/, "\\1\\3\n")
|
587
|
+
@w_d.text += "\n"
|
588
|
+
@w_d.text += "Awards: " + det["Awards"] + "\n"
|
589
|
+
@w_d.text += "Director: " + det["Director"] + "\n"
|
590
|
+
@w_d.text += "Actors: " + det["Actors"] + "\n\n"
|
591
|
+
@w_d.text += "Metascore: " + det["Metascore"] + "\n\n"
|
592
|
+
outlets = ""
|
593
|
+
@w_d.write
|
594
|
+
# Display the poster
|
595
|
+
poster = det["Poster"]
|
596
|
+
`curl -s "#{poster}" > /tmp/imdb.jpg`
|
597
|
+
image = "/tmp/imdb.jpg"
|
598
|
+
begin
|
599
|
+
terminfo = `xwininfo -id $(xdotool getactivewindow)`
|
600
|
+
term_w = terminfo.match(/Width: (\d+)/)[1].to_i
|
601
|
+
term_h = terminfo.match(/Height: (\d+)/)[1].to_i
|
602
|
+
char_w = term_w / Curses.cols
|
603
|
+
char_h = term_h / Curses.lines
|
604
|
+
img_x = char_w * 105
|
605
|
+
img_y = char_h * (Curses.lines / 2 + 2)
|
606
|
+
img_max_w = char_w * (Curses.cols - width - 2)
|
607
|
+
img_max_h = char_h * (@w_d.maxy - 2)
|
608
|
+
# Clear previous images
|
609
|
+
`echo "6;#{img_x};#{img_y};#{img_max_w+2};#{img_max_h+2};\n4;\n3;" | #{@w3mimgdisplay} 2>/dev/null`
|
610
|
+
img_w,img_h = `identify -format "%[fx:w]x%[fx:h]" #{image} 2>/dev/null`.split('x')
|
611
|
+
img_w = img_w.to_i
|
612
|
+
img_h = img_h.to_i
|
613
|
+
if img_w > img_max_w
|
614
|
+
img_h = img_h * img_max_w / img_w
|
615
|
+
img_w = img_max_w
|
616
|
+
end
|
617
|
+
if img_h > img_max_h
|
618
|
+
img_w = img_w * img_max_h / img_h
|
619
|
+
img_h = img_max_h
|
620
|
+
end
|
621
|
+
`echo "0;1;#{img_x};#{img_y};#{img_w};#{img_h};;;;;\"#{image}\"\n4;\n3;" | #{@w3mimgdisplay} 2>/dev/null`
|
622
|
+
rescue
|
623
|
+
w_b("Error showing image")
|
624
|
+
end
|
625
|
+
return unless ext > 1 # Skip if no outlets are to be displayed
|
626
|
+
otl.each{|o| outlets += "#{o} "}
|
627
|
+
@w_d.text += "Outlets: " + outlets
|
628
|
+
@w_d.write
|
629
|
+
end
|
630
|
+
def imageclear
|
631
|
+
begin
|
632
|
+
terminfo = `xwininfo -id $(xdotool getactivewindow)`
|
633
|
+
term_w = terminfo.match(/Width: (\d+)/)[1].to_i
|
634
|
+
term_h = terminfo.match(/Height: (\d+)/)[1].to_i
|
635
|
+
char_w = term_w / Curses.cols
|
636
|
+
char_h = term_h / Curses.lines
|
637
|
+
img_x = char_w * 105
|
638
|
+
img_y = char_h * (Curses.lines / 2 + 2)
|
639
|
+
img_max_w = char_w * (Curses.cols - (Curses.cols - 104) - 2)
|
640
|
+
img_max_h = char_h * (@w_d.maxy - 2)
|
641
|
+
`echo "6;#{img_x};#{img_y};#{img_max_w+2};#{img_max_h+2};\n4;\n3;" | #{@w3mimgdisplay} 2>/dev/null`
|
642
|
+
rescue
|
643
|
+
w_b("Error clearing image")
|
644
|
+
end
|
645
|
+
end
|
646
|
+
|
647
|
+
# BOTTOM WINDOW FUNCTIONS
|
648
|
+
def w_b(info) # SHOW INFO IN @W_B
|
649
|
+
@w_b.clr
|
650
|
+
info = "Choose window: i=IMDB list (+/- to add/remove from My list), g=Genres (+/- to add/remove), m=My list. " if info == nil
|
651
|
+
info = info[1..(@w_b.maxx - 3)] + "…" if info.length + 3 > @w_b.maxx
|
652
|
+
info += " " * (@w_b.maxx - info.length) if info.length < @w_b.maxx
|
653
|
+
@w_b.text = info
|
654
|
+
@w_b.write
|
655
|
+
@w_b.update = false
|
656
|
+
end
|
657
|
+
def w_b_getstr(pretext, text) # A SIMPLE READLINE-LIKE ROUTINE
|
658
|
+
Curses.curs_set(1)
|
659
|
+
Curses.echo
|
660
|
+
stk = 0
|
661
|
+
pos = text.length
|
662
|
+
chr = ""
|
663
|
+
while chr != "ENTER"
|
664
|
+
@w_b.setpos(0,0)
|
665
|
+
@w_b.text = pretext + text
|
666
|
+
@w_b.text += " " * (@w_b.maxx - text.length) if text.length < @w_b.maxx
|
667
|
+
@w_b.write
|
668
|
+
@w_b.setpos(0,pretext.length + pos)
|
669
|
+
@w_b.refresh
|
670
|
+
chr = getchr
|
671
|
+
case chr
|
672
|
+
when 'C-C', 'C-G'
|
673
|
+
return ""
|
674
|
+
when 'RIGHT'
|
675
|
+
pos += 1 unless pos > text.length
|
676
|
+
when 'LEFT'
|
677
|
+
pos -= 1 unless pos == 0
|
678
|
+
when 'HOME'
|
679
|
+
pos = 0
|
680
|
+
when 'END'
|
681
|
+
pos = text.length
|
682
|
+
when 'DEL'
|
683
|
+
text[pos] = ""
|
684
|
+
when 'BACK'
|
685
|
+
unless pos == 0
|
686
|
+
pos -= 1
|
687
|
+
text[pos] = ""
|
688
|
+
end
|
689
|
+
when 'LDEL'
|
690
|
+
text = ""
|
691
|
+
pos = 0
|
692
|
+
when /^.$/
|
693
|
+
text.insert(pos,chr)
|
694
|
+
pos += 1
|
695
|
+
end
|
696
|
+
end
|
697
|
+
Curses.curs_set(0)
|
698
|
+
Curses.noecho
|
699
|
+
return text
|
700
|
+
end
|
701
|
+
|
702
|
+
# BEFORE WE START
|
703
|
+
begin
|
704
|
+
if File.exist?(Dir.home+'/.imdb.conf')
|
705
|
+
load(Dir.home+'/.imdb.conf')
|
706
|
+
else
|
707
|
+
firstrun
|
708
|
+
end
|
709
|
+
if File.exist?(Dir.home+'/.imdb')
|
710
|
+
load(Dir.home+'/.imdb')
|
711
|
+
else
|
712
|
+
puts "Loading IMDB data... (this may take som time, go get some coffee)"
|
713
|
+
loadimdb
|
714
|
+
saveimdb
|
715
|
+
end
|
716
|
+
## Curses setup
|
717
|
+
Curses.init_screen
|
718
|
+
Curses.start_color
|
719
|
+
Curses.curs_set(0)
|
720
|
+
Curses.noecho
|
721
|
+
Curses.cbreak
|
722
|
+
Curses.stdscr.keypad = true
|
723
|
+
end
|
724
|
+
|
725
|
+
# MAIN PROGRAM
|
726
|
+
loop do # OUTER LOOP - (catching refreshes via 'r')
|
727
|
+
@break = false # Initialize @break variable (set if user hits 'r')
|
728
|
+
begin # Create the windows/panels
|
729
|
+
Curses.stdscr.bg = 234 # Use for borders
|
730
|
+
Curses.stdscr.fill
|
731
|
+
maxx = Curses.cols
|
732
|
+
maxy = Curses.lines
|
733
|
+
# Curses::Window.new( h, w, y, x )
|
734
|
+
@w_t = Curses::Window.new( 1, maxx, 0, 0 )
|
735
|
+
@w_i = Curses::Window.new( maxy-2, 40, 1, 0 )
|
736
|
+
@w_g = Curses::Window.new( maxy-2, 20, 1, 41 )
|
737
|
+
@w_m = Curses::Window.new( maxy/2-1, 40, 1, 62 )
|
738
|
+
@w_n = Curses::Window.new( maxy/2-1, 40, maxy/2+1, 62 )
|
739
|
+
@w_d = Curses::Window.new( maxy/2-1, maxx-103, 1, 103 )
|
740
|
+
@w_p = Curses::Window.new( maxy/2-1, maxx-103, maxy/2+1, 103 )
|
741
|
+
@w_b = Curses::Window.new( 1, maxx, maxy-1, 0 )
|
742
|
+
@w_i.fg, @w_i.bg = Wifg, WIbg
|
743
|
+
@w_g.fg, @w_g.bg = Wgfg, WIbg
|
744
|
+
@w_m.fg, @w_m.bg = Wmfg, WIbg
|
745
|
+
@w_n.fg, @w_n.bg = Wnfg, WIbg
|
746
|
+
@w_d.fg, @w_d.bg = Wdfg, WIbg
|
747
|
+
@w_p.fg, @w_p.bg = Wpfg, WIbg
|
748
|
+
@w_b.fg, @w_b.bg = Wbfg, Wbbg
|
749
|
+
[@w_i, @w_g, @w_m, @w_n].each{|w| w.index = 0}
|
750
|
+
@w_b.fill
|
751
|
+
@w_g.list = @genres
|
752
|
+
@w_b.update = true
|
753
|
+
@w_d.update = true
|
754
|
+
@active = @w_i
|
755
|
+
loop do # INNER, CORE LOOP
|
756
|
+
if @movies
|
757
|
+
imdbmovies
|
758
|
+
@w_t.fg = WtMfg
|
759
|
+
@search == '' ? @w_t.bg = WtMbg : @w_t.bg = WtMbgS
|
760
|
+
@w_t.attr = Curses::A_BOLD
|
761
|
+
else
|
762
|
+
imdbseries
|
763
|
+
@w_t.fg = WtSfg
|
764
|
+
@search == '' ? @w_t.bg = WtSbg : @w_t.bg = WtSbgS
|
765
|
+
@w_t.attr = Curses::A_BOLD
|
766
|
+
end
|
767
|
+
@w_t.fill; @w_i.fill; @w_g.fill; @w_m.fill; @w_n.fill; @w_p.fill
|
768
|
+
w_t; w_list(@w_i); w_list(@w_g); w_list(@w_m); w_list(@w_n)
|
769
|
+
if @w_d.update
|
770
|
+
@w_d.fill
|
771
|
+
w_d
|
772
|
+
end
|
773
|
+
imageclear if @w_d.update
|
774
|
+
@w_d.update = true
|
775
|
+
w_b(nil) if @w_b.update
|
776
|
+
@w_b.update = true
|
777
|
+
getkey # Get key from user
|
778
|
+
break if @break # Break to outer loop, redrawing windows, if user hit 'r'
|
779
|
+
break if Curses.cols != maxx or Curses.lines != maxy # break on terminal resize
|
780
|
+
end
|
781
|
+
ensure # On exit: clear image, close curses
|
782
|
+
image_show("clear")
|
783
|
+
close_screen
|
784
|
+
end
|
785
|
+
end
|
786
|
+
|
787
|
+
# vim: set sw=2 sts=2 et fdm=syntax fdn=2 fcs=fold\:\ :
|
metadata
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: imdb-terminal
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.1'
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Geir Isene
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-07-01 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: curses
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 1.3.2
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.3'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 1.3.2
|
33
|
+
description: Narrow down your preferences from a 1000 movies and almost 500 series.
|
34
|
+
Select a minimum IMDB rating, range of production years, genres you like and dislike
|
35
|
+
to get your preferred list. Get detailed information on movies and series and where
|
36
|
+
you can stream them. Even the movie poster in the terminal.
|
37
|
+
email: g@isene.com
|
38
|
+
executables:
|
39
|
+
- imdb
|
40
|
+
extensions: []
|
41
|
+
extra_rdoc_files: []
|
42
|
+
files:
|
43
|
+
- README.md
|
44
|
+
- bin/imdb
|
45
|
+
homepage: https://isene.com/
|
46
|
+
licenses:
|
47
|
+
- Unlicense
|
48
|
+
metadata:
|
49
|
+
source_code_uri: https://github.com/isene/imdb
|
50
|
+
post_install_message:
|
51
|
+
rdoc_options: []
|
52
|
+
require_paths:
|
53
|
+
- lib
|
54
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: '0'
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
64
|
+
requirements: []
|
65
|
+
rubygems_version: 3.3.5
|
66
|
+
signing_key:
|
67
|
+
specification_version: 4
|
68
|
+
summary: IMDB for the terminal
|
69
|
+
test_files: []
|