dontpanic.py 33.4 KB
Newer Older
1
#!/usr/bin/env python
2
# -*- coding: utf-8 -*-
prx's avatar
prx committed
3 4 5


"""
Tankypon's avatar
Tankypon committed
6
Auteur :      thuban <thuban@yeuxdelibad.net>
prx's avatar
prx committed
7 8 9 10 11 12
licence :     GNU General Public Licence v3

Description :
    Just watch your favorite show and keep track of them.
"""

13
import getpass
prx's avatar
prx committed
14
import gettext
15
import sys
16

17
from argparse import ArgumentParser
prx's avatar
prx committed
18
from bottle import *
19
from threading import Thread
prx's avatar
prx committed
20
from urllib.parse import quote as quoteurl
Tankypon's avatar
Tankypon committed
21

22
from database import DontPanicDB
23 24
from subtitles import get_hash, sch_subtitle, dl_subtitle
from torrentsession import TorrentSession
25 26
from utils import *
from webutils import *
prx's avatar
prx committed
27

prx's avatar
prx committed
28 29
gettext.bindtextdomain(progname, '/usr/share/locale')
gettext.textdomain(progname)
prx's avatar
prx committed
30 31
_ = gettext.gettext

32

Tankypon's avatar
Tankypon committed
33 34 35 36
# For python2 or 3
if sys.version_info < (3, 0):
    print("You need python3 to use dontpanic")
    sys.exit(0)
37

38 39
logger = logging.getLogger(__name__)

Tankypon's avatar
Tankypon committed
40

Tankypon's avatar
Tankypon committed
41
def search2html(keywords, extrawords=' ', searchengine='torrentz', page=0, isserie=0):
42 43 44 45 46 47 48 49 50 51 52 53 54
    """
    Show search result

    Args:
        keywords:
        extrawords:
        searchengine:
        page:
        isserie:

    Return:

    """
55
    sch = '{} {}'.format(keywords, extrawords).strip()
prx's avatar
prx committed
56
    if searchengine == 'btdigg':
57
        s = btdigg_sch(sch, page)
prx's avatar
prx committed
58
    elif searchengine == 'torrentproject':
59
        s = torrentproject_sch(sch)
prx's avatar
prx committed
60
    elif searchengine == 'torrentz':
61
        s = torrentz_sch(sch)
Tankypon's avatar
Tankypon committed
62
    else:
63
        s = torrentproject_sch(sch)
prx's avatar
prx committed
64

65 66
    # If there is results
    if s:
67
        html = '<table class="sortable, float-center">'
68
        html += '<tr>'
Tankypon's avatar
Tankypon committed
69 70 71 72
        html += '<th scope="col">{}</th>'.format(_('Name'))
        html += '<th scope="col">{}</th>'.format(_('Size'))
        html += '<th scope="col">{}</th>'.format(_('Leechs'))
        html += '<th scope="col">{}</th>'.format(_('Seeds'))
73
        html += '</tr>'
74 75 76 77 78

        for result in s:
            link = '/stream/{}/{}/{}'.format(keywords, quoteurl(result['magnet']), isserie)
            html += '<tr>'
            html += '<td><a href="{}">{}</a></td>'.format(link, result['name'])
prx's avatar
prx committed
79
            html += '<td class="row-center"><a href="{}">{}</a></td>'.format(link, result['size'])
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101

            html += '<td class="text-center"><a href="{}">{}</a></td>'.format(link, result['leechs'])

            seeds = int(result['seeds'])
            # color depending on seeds number
            color = 'white'
            if seeds <= 10:
                color = 'red'
            elif 40 >= seeds > 10:
                color = 'orange'
            elif 70 >= seeds > 40:
                color = 'yellow'
            elif 100 >= seeds > 70:
                color = 'blue'
            elif seeds > 100:
                color = 'green'
            html += '<td class="text-center" style="background:{};"><a href="{}">{}</a></td>'.format(
                color, link, result['seeds'])

            html += '</tr>'
        html += '</table>'
        if searchengine == 'btdigg':
Tankypon's avatar
Tankypon committed
102 103
            html += '<a href="/moreresults/{}/{}/{}/{}/{}">{}</a>'.format(
                keywords, int(page) + 1, searchengine, extrawords, isserie, _('More results'))
104 105 106 107
    else:  # If there is no result
        html = '<div class="text-center"><br>{}</div>'.format(
            _('There is no result for your search, try something else'))

Tankypon's avatar
Tankypon committed
108 109
    return html

prx's avatar
prx committed
110 111

def allocinesch2html(keywords, serie=False):
112 113 114 115 116 117 118 119 120 121
    """


    Args:
        keywords:
        serie:

    Return:
        Return a research on allocine in html code
    """
122
    results = allocine_sch(keywords, serie=serie)
prx's avatar
prx committed
123
    if len(results) == 0:
124
        return ''
prx's avatar
prx committed
125 126 127
    else:
        html = '<ul class="ca-menu">'
        for r in results:
Tankypon's avatar
Tankypon committed
128 129 130
            isserie = 1 if serie else 0
            html += '<li><a href="/whatsthat/{1}/{2}/{3}/{1}"><img src="{0}"><p>{1} <br /> {2}</p></a></li>'.format(
                    r['poster'], r['title'], r['year'], isserie)
prx's avatar
prx committed
131 132
        html += '</ul>'

Tankypon's avatar
Tankypon committed
133 134
        return html

prx's avatar
prx committed
135

Tankypon's avatar
Tankypon committed
136
def getseriestuff(keywords, year, extrawords='', user='', searchengine="torrentz"):
prx's avatar
prx committed
137
    serie_stuff = ''
138
    r = omdb_sch(keywords, year)
prx's avatar
prx committed
139
    if r['Response'] == 'True':
140
        eplist = get_episode_list(keywords, year)
141
        serie_stuff = '<table class="sortable, float-center">'
Tankypon's avatar
Tankypon committed
142 143 144 145 146
        serie_stuff += '<th scope="col" class="text-center">{}</th>'.format(_('Season'))
        serie_stuff += '<th scope="col" class="text-center">{}</th>'.format(_('Episode'))
        serie_stuff += '<th scope="col">{}</th>'.format(_('Title'))
        serie_stuff += '<th scope="col" class="text-right">{}</th>'.format(_('Release date'))
        serie_stuff += '<th scope="col" class="text-center">{}</th>'.format(_('Seen'))
147 148 149 150
        for season in eplist:
            for e in season['Episodes']:
                episode = 'S{0:02d}E{1:02d}'.format(int(season['Season']), int(e['Episode']))
                search = '{0} {1}'.format(keywords, episode)
Tankypon's avatar
Tankypon committed
151 152
                link = '/search?keywords={}&extrawords={}&nocatalog=1&isserie=1&searchengine={}'.format(
                    search, extrawords, searchengine)
153
                serie_stuff += '<tr>'
Tankypon's avatar
Tankypon committed
154 155
                serie_stuff += '<td class="text-center"><a href="{}">{:02d}</a></td>'.format(
                    link, int(season['Season']))
Tankypon's avatar
Tankypon committed
156
                serie_stuff += '<td class="text-center"><a href="{}">{:02d}</a></td>'.format(link, int(e['Episode']))
prx's avatar
prx committed
157 158
                serie_stuff += '<td><a href="{}">{}</a></td>'.format(link, e['Title'])
                serie_stuff += '<td class="text-right"><a href="{}">{}</a></td>'.format(link, e['Released'])
159

160 161
                with DontPanicDB() as db:
                    if db.is_user_in(user):
162
                        togglelink = "/_toggleep/{}/".format(search)
163
                        if db.is_title_in_wl(user, search):
Tankypon's avatar
Tankypon committed
164 165
                            serie_stuff += '<td style="background:{1};">\
                                    <a href="{0}" style="color:{1};">{2}</a></td>'.format(togglelink, 'green', _('Yes'))
166
                        else:
167
                            serie_stuff += '<td style="background:{1};">\
Tankypon's avatar
Tankypon committed
168
                                    <a href="{0}" style="color:{1};">{2}</a></td>'.format(togglelink, 'red', _('No'))
prx's avatar
prx committed
169
                    else:
170
                        serie_stuff += '<td class="text-right">?</td>'
171 172 173
                serie_stuff += '</tr>'
        serie_stuff += '</table>'

Tankypon's avatar
Tankypon committed
174
    return serie_stuff
prx's avatar
prx committed
175

176

Tankypon's avatar
Tankypon committed
177
def omdb2template(keywords, year, isserie=0):
prx's avatar
prx committed
178 179 180 181 182 183 184 185
    title = ''
    runtime = ''
    director = ''
    writer = ''
    actors = ''
    poster = ''
    plot = ''
    rating = ''
186
    html = ''
prx's avatar
prx committed
187 188 189 190 191
    r = omdb_sch(keywords, year)

    if 'Response' not in r.keys():
        return title, runtime, director, writer, actors, poster, plot, rating, html
    elif r['Response'] == 'True':
Tankypon's avatar
Tankypon committed
192
        title = r['Title']
prx's avatar
prx committed
193 194 195 196 197 198 199 200
        runtime = r['Runtime']
        director = r['Director']
        writer = r['Writer']
        actors = r['Actors']
        poster = r['Poster']
        plot = r['Plot']
        rating = r['imdbRating']
    else:
prx's avatar
prx committed
201
        html = _("Do you mean ? ")
prx's avatar
prx committed
202 203 204 205
        html += "<p>"
        lop = []
        k = keywords
        while len(k.split(' ')) > 1:
Tankypon's avatar
Tankypon committed
206
            k = k.rsplit(' ', 1)[0]
prx's avatar
prx committed
207 208 209
            lop.append(k)
        k = keywords
        while len(k.split(' ')) > 1:
Tankypon's avatar
Tankypon committed
210
            k = k.split(' ', 1)[-1]
prx's avatar
prx committed
211
            lop.append(k)
Tankypon's avatar
Tankypon committed
212 213
        # lop = set(listofpossibles)[0] # remove duplicates
        for i in lop:
prx's avatar
prx committed
214
            html += ' - '
prx's avatar
prx committed
215
            html += '<a href="/whatsthat/{0}/{1}/{2}/">{0}</a>'.format(i, year, isserie)
prx's avatar
prx committed
216 217 218
            html += ' - '
        html += "</p>"

Tankypon's avatar
Tankypon committed
219
    return title, runtime, director, writer, actors, poster, plot, rating, html
220 221 222 223 224


def watchlist(user):
    movies = ''
    series = ''
Tankypon's avatar
Tankypon committed
225
    watchlists = list()
226 227
    with DontPanicDB() as db:
        watchlists = db.get_watchlist(user)
228

Tankypon's avatar
Tankypon committed
229
    for watch in watchlists:
230
        for ele in watch["list"]:
231 232 233
            date = ele["datetime"]
            title = ele["title"]
            magnet = ele["magnet"]
234

235 236
            link_remove = quoteurl('/_removeinwatchlist/{}/'.format(title))

Tankypon's avatar
Tankypon committed
237
            if watch["type"] == "serie":
238
                link_watch = quoteurl('/stream/{}/{}/1'.format(title, magnet))
239 240 241
                series += '<li>\n'
                series += '{}   {}'.format(date, title)
                if magnet != '':
242 243 244
                    series += '<span class="float-right"><a href="{}">▷ {}</a></span>'.format(link_watch,
                                                                                              _('Watch again'))
                series += '<span class="float-right"><a href="{}">✕ {}</a></span>'.format(link_remove, _('Remove'))
245 246
                series += '</li>\n'
            else:
247
                link_watch = quoteurl('/stream/{}/{}/0'.format(title, magnet))
248 249 250
                movies += '<li>\n'
                movies += '{}   {}'.format(date, title)
                if magnet != '':
251 252 253
                    movies += '<span class="float-right"><a href="{}">▷ {}</a></span>'.format(link_watch,
                                                                                              _('Watch again'))
                movies += '<span class="float-right"><a href="{}">✕ {}</a></span>'.format(link_remove, _('Remove'))
254
                movies += '</li>\n'
255 256 257 258 259 260

    if len(movies) > 0:
        movies = '<ul style="list-style-type:square;">{}</ul>'.format(movies)
    if len(series) > 0:
        series = '<ul style="list-style-type:square;">{}</ul>'.format(series)

Tankypon's avatar
Tankypon committed
261 262
    return movies, series

prx's avatar
prx committed
263 264

def pick_welcomemsg():
Tankypon's avatar
Tankypon committed
265 266
    welcomemsg = WELCOMEMSG[random.randrange(0, len(WELCOMEMSG))]
    return welcomemsg
prx's avatar
prx committed
267 268


Tankypon's avatar
Tankypon committed
269
class DontPanic:
Tankypon's avatar
Tankypon committed
270
    def __init__(self, host, port, admin, adminpw="password", local_=True):
Tankypon's avatar
Tankypon committed
271
        self.startperc = None
prx's avatar
prx committed
272
        self.tmp = tempdir
Tankypon's avatar
Tankypon committed
273 274 275
        self.uplimit = None
        self.downlimit = None

prx's avatar
prx committed
276 277 278
        self.app = Bottle()
        self.host = host
        self.port = port
Tankypon's avatar
Tankypon committed
279
        self.local = local_
280
        self.dllist = []
prx's avatar
prx committed
281
        self.secret = randomtext(22)
Tankypon's avatar
Tankypon committed
282
        self.read_config()
283

284
        self.admin = admin
285
        self.adminpw = adminpw
286

287 288
        with DontPanicDB() as db:
            db.add_user(admin, adminpw)
prx's avatar
prx committed
289 290 291

        @self.app.route('/')
        def index():
292
            user = ''
293
            if self.local:
Tankypon's avatar
Tankypon committed
294
                response.set_cookie("username", self.admin, secret=self.secret, httponly=True)
295
                user = self.admin
prx's avatar
prx committed
296 297 298

            # show recent search
            html = ''
299 300
            if user == '':
                user = request.get_cookie("username", secret=self.secret)
prx's avatar
prx committed
301 302
            if user:
                recent_search = list()
Tankypon's avatar
Tankypon committed
303 304
                with DontPanicDB() as database:
                    recent_search = database.get_recent_search(user)
prx's avatar
prx committed
305 306 307 308 309 310

                if len(recent_search) > 0:
                    for ele in recent_search:
                        title = ele["title"]
                        year = ele["year"]
                        isserie = ele["isserie"]
311 312 313 314
                        poster = ele["poster"]

                        posterhtml = '<div class="show-image">\n'
                        posterhtml += '<img src="{}" />\n'.format(poster)
315
                        posterhtml += '<a class="delete" href="/_removeinrecent/{}/">✗</a>\n'.format(title)
316 317
                        posterhtml += '</div>\n'
                        
Tankypon's avatar
Tankypon committed
318
                        html += '<li><a href="/whatsthat/{1}/{2}/{3}/">{0}<p>{1} <br /> {2}</p></a></li>'.format(
319
                                posterhtml, title, year, isserie)
prx's avatar
prx committed
320 321

                    html = '<ul class="ca-menu">{}</ul>'.format(html)
322 323 324
                
                tplvars = {'welcomemsg' : pick_welcomemsg() , 'recents': html } 
                tplvars.update(tpl_msg)
prx's avatar
prx committed
325

326
            return template('towel', tplvars) 
prx's avatar
prx committed
327

prx's avatar
prx committed
328 329 330 331
        @self.app.route('/static/<filename:path>')
        def static(filename):
            return static_file(filename, root='./static/')

332 333
        @self.app.route('/me')
        def login():
Tankypon's avatar
Tankypon committed
334
            whoami = request.get_cookie("username", secret=self.secret)
335
            if not whoami:
prx's avatar
prx committed
336 337 338 339
                usermsg = _("Username")
                pwmsg = _("Password")
                loginmsg = _("Login")
                registermsg = _("Register")
prx's avatar
prx committed
340
                return '''
341
                <form action="/me" method="post">
prx's avatar
prx committed
342 343 344
                    {}: <input name="username" type="text" />
                    {}: <input name="password" type="password" />
                    <input value="{}" type="submit" />
345
                </form>
prx's avatar
prx committed
346 347
                <p><a href="/sign">{}</a></p>
                    '''.format(usermsg, pwmsg, loginmsg, registermsg)
348 349
            else:
                movies, series = watchlist(whoami)
350 351 352
                tplvars = {'username': whoami, 'movies': movies, 'series':series}
                tplvars.update(tpl_msg)
                return template('perso',tplvars)
353 354 355 356 357

        @self.app.route('/me', method='POST')
        def do_login():
            username = request.forms.get('username')
            password = request.forms.get('password')
358

Tankypon's avatar
Tankypon committed
359 360
            with DontPanicDB() as database:
                if database.check_login(username, password):
361 362 363
                    response.set_cookie("username", username, secret=self.secret)
                    redirect('/me')
                else:
Tankypon's avatar
Tankypon committed
364
                    return '<p>{}.</p>'.format(_('Your login information was incorrect'))
365 366 367 368 369 370 371 372

        @self.app.route('/logout')
        def logout():
            response.delete_cookie("username")
            redirect('/')

        @self.app.route('/sign')
        def sign():
prx's avatar
prx committed
373 374 375 376
            usermsg = _("Username")
            pwmsg = _("Password")
            loginmsg = _("Login")
            registermsg = _("Register")
prx's avatar
prx committed
377
            return ('''
prx's avatar
prx committed
378
            <h1>{}</h1>
379
                <form action="/sign" method="post">
prx's avatar
prx committed
380 381 382
                    {}: <input name="username" type="text" />
                    {}: <input name="password" type="password" />
                    <input value="{}" type="submit" />
383
                </form>
prx's avatar
prx committed
384
                    '''.format(registermsg, usermsg, pwmsg, loginmsg))
385 386 387 388 389

        @self.app.route('/sign', method='POST')
        def do_sign():
            username = request.forms.get('username')
            password = request.forms.get('password')
390

Tankypon's avatar
Tankypon committed
391 392
            with DontPanicDB() as database:
                if database.add_user(username, password):
393
                    bravo = _("Congratulations, your subscription is a success")
prx's avatar
prx committed
394
                    loginmsg = _("Now, log in")
prx's avatar
prx committed
395
                    return '''<h1>{}</h1>
prx's avatar
prx committed
396 397
                    <p><a href="/me">{}</a> ;)</p>
                    '''.format(bravo, loginmsg)
398
                else:
399 400 401
                    sorry = _("Oh no!")
                    reason = _("User {} already exists".format(username))
                    return '<h1>{} {}</h1>'.format(sorry, reason)
402 403 404

        @self.app.route('/unsubscribe')
        def unsubscribe():
Tankypon's avatar
Tankypon committed
405
            whoami = request.get_cookie("username", secret=self.secret)
406
            if not whoami:
prx's avatar
prx committed
407
                return _("You're not logged in")
Tankypon's avatar
Tankypon committed
408 409
            with DontPanicDB() as database:
                if database.delete_user(whoami):
prx's avatar
prx committed
410
                    return _("Your accoun't is deleted")
411
                else:
prx's avatar
prx committed
412
                    return _("An error occured while deleting your account. Please contact the administrator")
413

prx's avatar
prx committed
414 415 416
        @self.app.route('/clean')
        def cleanup():
            cleancache()
417
            redirect('/_admin')
prx's avatar
prx committed
418

419 420
        @self.app.route('/cleantorrent')
        def cleantorrent():
prx's avatar
prx committed
421
            self._clean_torlist()
422
            redirect('/_admin')
prx's avatar
prx committed
423 424 425 426 427 428

        @self.app.route('/search', method='get')
        def search():
            keywords = request.query.get('keywords')
            searchengine = request.query.get('searchengine')
            nocatalog = request.query.get('nocatalog')
429 430
            extrawords = request.query.get('extrawords')
            isserie = request.query.get('isserie')
prx's avatar
prx committed
431

Tankypon's avatar
Tankypon committed
432
            if not searchengine:
433
                searchengine = ''
Tankypon's avatar
Tankypon committed
434
            if not nocatalog:
prx's avatar
prx committed
435
                nocatalog = 0
436 437 438 439
            if not extrawords:
                extrawords = ' '
            if not isserie:
                isserie = '0'
prx's avatar
prx committed
440 441

            seriessch, moviessch = '', ''
442
            logger.info('Searching on {}...   '.format(searchengine))
Tankypon's avatar
Tankypon committed
443
            # FIXME: Better handle when a search engine has modified its html code
444 445 446 447
            try:
                htmlresults = search2html(keywords, extrawords=extrawords, searchengine=searchengine, isserie=isserie)
            except Exception as e:
                logger.info('Error while searching :  {}...   '.format(e))
448 449
                htmlresults = '<div class="text-center"><br>{}</div>'.format(
                        _("Error while searching. Try another engine"))
450

prx's avatar
prx committed
451
            if nocatalog != "1":
452
                logger.info('Searching {}...   '.format("in series"))
prx's avatar
prx committed
453
                seriessch = allocinesch2html(keywords, serie=True)
454
                logger.info('Searching {}...   '.format("in movies"))
prx's avatar
prx committed
455 456
                moviessch = allocinesch2html(keywords, serie=False)

457 458 459 460 461 462 463 464
            tplvars = { 'sch_res' : htmlresults,
                'the_search': '{} {}'.format(keywords, extrawords).strip(),
                'moviessch' : moviessch,
                'seriessch' : seriessch,
                'isserie' : isserie,
                'nocatalog' : nocatalog}
            tplvars.update(tpl_msg)
            return template('results', tplvars)
prx's avatar
prx committed
465

466 467
        @self.app.route('/moreresults/<keywords>/<page>/<searchengine>/<extrawords>/<isserie>')
        def moreresults(keywords, page, searchengine, extrawords, isserie):
Tankypon's avatar
Tankypon committed
468
            # FIXME: Better handle when a search engine has modified its html code
469 470 471 472 473 474 475 476 477
            try:
                htmlresults = search2html(keywords.strip(),
                                        extrawords=extrawords,
                                        searchengine=searchengine,
                                        page=page,
                                        isserie=isserie)
            except Exception as e:
                logger.info('Error while searching :  {}...   '.format(e))
                htmlresults = _("Error while searching. Try another engine")
478 479 480 481 482 483 484 485

            tplvars = {
                'sch_res' : htmlresults,
                'the_search' : '{} {}'.format(keywords, extrawords).strip(),
                'moviessch' : '',
                'seriessch' : ''}
            tplvars.update(tpl_msg)
            return template('results', tplvars)
prx's avatar
prx committed
486

prx's avatar
prx committed
487
        @self.app.route('/whatsthat/<title>/<year>/<isserie>/')
488
        @self.app.route('/whatsthat/<title>/<year>/<isserie>/<oldposter:path>')
Tankypon's avatar
Tankypon committed
489
        def whatsthat(title, year, isserie,  oldposter=""):
Tankypon's avatar
Tankypon committed
490 491 492
            title_simplify = simplifyname(title)
            title, runtime, director, writer, actors, poster, plot, rating, html = omdb2template(
                title_simplify, year, isserie)
prx's avatar
prx committed
493

494 495 496
            if poster == "N/A" and oldposter != "":
                poster = oldposter

Tankypon's avatar
Tankypon committed
497
            if len(poster) > 0:
prx's avatar
prx committed
498
                whoami = request.get_cookie("username", secret=self.secret)
Tankypon's avatar
Tankypon committed
499
                self._add_to_recent_search(whoami, title, year, poster, isserie)
prx's avatar
prx committed
500

501 502 503 504 505 506 507 508 509 510 511 512 513 514 515
            tplvars = {
                    'title': title, \
                    'title_simplify' : title_simplify,\
                    'year' : year,\
                    'runtime' : runtime,\
                    'director' : director,\
                    'writer' : writer,\
                    'actors' : actors,\
                    'poster': poster,\
                    'plot' : plot,\
                    'rating' : rating,\
                    'html' : html,\
                    'isserie' : isserie }
            tplvars.update(tpl_msg)
            return template('what',tplvars)
516 517 518

        @self.app.route('/serie/<title>/<year>')
        def serie(title, year):
Tankypon's avatar
Tankypon committed
519
            keywords = simplifyname(title.strip())
520 521 522 523
            year = year.strip()
            extrawords = request.query.get('extrawords')
            searchengine = request.query.get('searchengine')

Tankypon's avatar
Tankypon committed
524
            whoami = request.get_cookie("username", secret=self.secret)
prx's avatar
prx committed
525

Tankypon's avatar
Tankypon committed
526
            htmlstuff = getseriestuff(keywords, year, extrawords=extrawords, user=whoami, searchengine=searchengine)
prx's avatar
prx committed
527

528 529 530 531
            tplvars = {'htmlstuff' : htmlstuff , 'title': title } 
            tplvars.update(tpl_msg)

            return template('classic', tplvars)
prx's avatar
prx committed
532

533
        @self.app.route("/stream/<name>/<magnet:path>/<isserie>")
Tankypon's avatar
Tankypon committed
534
        def stream(name, magnet, isserie):
Tankypon's avatar
Tankypon committed
535 536 537 538
            # Start download
            newses = TorrentSession()
            newses.download(magnet, self.tmp, self.uplimit, self.downlimit)

539
            self.dllist.append(newses)
Tankypon's avatar
Tankypon committed
540
            index = self.dllist.index(newses)
541

Tankypon's avatar
Tankypon committed
542
            # Record to watchlist
Tankypon's avatar
Tankypon committed
543
            whoami = request.get_cookie("username", secret=self.secret)
544
            if whoami:
Tankypon's avatar
Tankypon committed
545
                t = Thread(target=self._add_when_ready, args=(newses, whoami, name, magnet, isserie))
546
                t.start()
prx's avatar
prx committed
547

548 549 550 551 552 553 554 555 556 557
            tplvars = { 'movie_name' : name,\
                'isserie' : isserie,
                'status' : '',
                'playlink' : '',
                'subtitles' : '',
                'stop' : '',
                'index' : index}
            tplvars.update(tpl_msg)

            return template('towel-dl',tplvars)
prx's avatar
prx committed
558

Tankypon's avatar
Tankypon committed
559 560
        @self.app.route('/_getinfos/<name>/<isserie>/<index:int>')
        def getinfos(name, isserie, index):
561 562 563 564
            """
            Give infos html formatted

            Args:
Tankypon's avatar
Tankypon committed
565 566
                name: Title of the video
                isserie (boolean): If True the video is a serie else it's a movie
Tankypon's avatar
Tankypon committed
567
                index:
568
            """
Tankypon's avatar
Tankypon committed
569
            session = self.dllist[index]
Tankypon's avatar
Tankypon committed
570
            perc = session.get_progress()
571 572 573
            state = session.get_state()
            peers = session.get_peers()
            downrate = session.get_downrate()
prx's avatar
prx committed
574

Tankypon's avatar
Tankypon committed
575
            # Hack for progress bar
prx's avatar
prx committed
576 577 578
            #htmlperc = '<div class="progressbar" style="background-size: {}% 100%; ">'.format(perc)
            htmlperc = '<div class="progressbar" ><progress value="{0}" max="100" ></progress>'.format(perc)
            htmlperc += '<div>{}% <br />{} - {}/s - {} peers</div>'.format(
Tankypon's avatar
Tankypon committed
579
                perc, state, downrate, peers)
prx's avatar
prx committed
580
            htmlperc += '</div>'
Tankypon's avatar
Tankypon committed
581

Tankypon's avatar
Tankypon committed
582
            # To stop the download
prx's avatar
prx committed
583 584 585
            stop = ''
            if state != "downloading metadata":
                stop = '<a href="/stopdl/{}" >{}</a>'.format(index, _('Stop this download'))
prx's avatar
prx committed
586

Tankypon's avatar
Tankypon committed
587
            # Show the play link
588
            moviepath = session.get_file_name()
prx's avatar
prx committed
589 590
            playlink = ''
            subtitles = ''
Tankypon's avatar
Tankypon committed
591
            if perc >= int(self.startperc) and session.ready2watch():
Tankypon's avatar
Tankypon committed
592
                # Show the link to play the file
Tankypon's avatar
Tankypon committed
593
                playlink = '<span class="big"><a href="/html5/{}">{} ▷</a></span>'.format(moviepath, _('Play'))
Tankypon's avatar
Tankypon committed
594 595
                subtitles = '<a href="/getsubtitles/{}/{}" target="_blank">{}</a> - '.format(
                    name, moviepath, _('Find subtitles'))
596

Tankypon's avatar
Tankypon committed
597 598
            return ({'status': htmlperc,
                     'playlink': playlink,
prx's avatar
prx committed
599 600 601 602 603
                     'subtitles': subtitles,
                     'stop': stop})

        @self.app.route('/stopdl/<i:int>')
        def stopdl(i):
604
            t = self.dllist[i]
prx's avatar
prx committed
605 606
            whoami = request.get_cookie("username", secret=self.secret)
            if whoami != self.admin:
prx's avatar
prx committed
607
                return _("Only the administrator can delete a download")
prx's avatar
prx committed
608
            else:
prx's avatar
prx committed
609
                h = t.handle
610
                session = t.session
prx's avatar
prx committed
611
                session.remove_torrent(h)
612
                self.dllist.remove(t)
613
                del session
614
                html = _("Torrent deleted")
615 616 617 618

                tplvars = { 'htmlstuff' : html, 'title' : "Deleted" }
                tplvars.update(tpl_msg)
                return template('classic', tplvars)
prx's avatar
prx committed
619

prx's avatar
prx committed
620 621
        @self.app.route('/html5/<filename:path>')
        def html5(filename):
622 623 624 625
            mtype = 'type="{}"'.format(mimetypes.guess_type(filename)[0])
            html = '<video class="video-js" controls preload="auto" autoplay="true" >\n'
            html += '<source src="/video/{}" {} >\n'.format(filename, mtype)
            html += '</video>\n'
626 627 628 629 630
            html += '<div>\n'
            if self.local :
                html += '- <a href="/localvideo/{}">{}</a>\n'.format(filename, _('Play with external video player'))
            html += '<a href="/video/{}">{}</a>\n'.format(filename, _('Download the video'))
            html += '</div>\n'
Tankypon's avatar
Tankypon committed
631
            # html += '<script src="http://vjs.zencdn.net/5.4.6/video.js"></script>\n'
632 633 634
            tplvars = { 'title' : os.path.basename(filename), 'htmlstuff' : html }
            tplvars.update(tpl_msg)
            return template('classic', tplvars)
prx's avatar
prx committed
635

prx's avatar
prx committed
636
        @self.app.route('/video/<filename:path>')
prx's avatar
prx committed
637 638
        def send_static_video(filename):
            return static_file(filename, root=self.tmp)
Tankypon's avatar
Tankypon committed
639
            # return "<script>javascript:history.back();</script>"
640

641 642 643 644 645 646 647 648 649 650
        @self.app.route('/localvideo/<filename:path>')
        def open_in_external_player(filename):
            file_path = os.path.join(self.tmp, filename)
            if sys.platform.startswith('linux'):
                import subprocess
                subprocess.call(["xdg-open", file_path])
            else:
                os.startfile(file_path)
            return "<script>javascript:history.back();</script>"

651
        @self.app.route('/getsubtitles/<title>/<filename:path>')
Tankypon's avatar
Tankypon committed
652
        def showsubtitles(title, filename):
Tankypon's avatar
Tankypon committed
653 654
            opensubtitles = '<p>{} <a href="http://www.opensubtitles.org/search2/moviename-{}">\
                Opensubtitles.org</a></p>'.format(title, _('Look for subtitles on'))
655

Tankypon's avatar
Tankypon committed
656
            fullpath = os.path.join(self.tmp, filename)
Tankypon's avatar
Tankypon committed
657 658
            hash_ = get_hash(fullpath)
            avail = sch_subtitle(hash_)
659
            if avail:
Tankypon's avatar
Tankypon committed
660
                subdb = "<p>{} :</p>\n".format(_('Choose a language to download subtitle'))
661 662 663
                subdb += '<ul">\n'
                o = os.path.splitext(filename)[0] + '.srt'
                for l in avail:
Tankypon's avatar
Tankypon committed
664
                    subdb += '<li style="display:inline-block;">- <a href="/dlsub/{0}/{1}/{2}">{0}</a> -</li>\n'.format(
Tankypon's avatar
Tankypon committed
665
                        l, hash_, o)
666 667 668

                subdb += '</ul>\n'
            else:
Tankypon's avatar
Tankypon committed
669
                subdb = '<p>{}</p>'.format(_('No subtitle available on subdb'))
670 671 672

            html = subdb + opensubtitles

673 674 675
            tplvars = { 'title' : title, 'htmlstuff' : html }
            tplvars.update(tpl_msg)
            return template('classic', tplvars)
676 677

        @self.app.route('/dlsub/<lang>/<hash>/<filename:path>')
Tankypon's avatar
Tankypon committed
678 679
        def dlsub(lang, hash_, filename):
            if dl_subtitle(hash_, lang, os.path.join(self.tmp, filename)):
680
                msg1 = _("Success: your subtitles are in the torrent folder")
prx's avatar
prx committed
681 682
                msg2 = _("Click here if you want to download them on you computer")
                msg3 = _("This page will close in 5.42 seconds")
prx's avatar
prx committed
683
                return '''<p>{}<br />
prx's avatar
prx committed
684
                        <a href="/video/{}">{}</a>
685
                        </p>
prx's avatar
prx committed
686
                        <p>{}</p>
687 688 689 690

                        <script>
                            setTimeout("window.close()", 5420);
                        </script>
prx's avatar
prx committed
691
                        '''.format(msg1,filename, msg2, msg3)
692
            else:
prx's avatar
prx committed
693
                return _("Failed to download subtitles")
prx's avatar
prx committed
694

695
        @self.app.route('/_admin')
prx's avatar
prx committed
696
        def configure():
Tankypon's avatar
Tankypon committed
697
            whoami = request.get_cookie("username", secret=self.secret)
698
            if whoami != self.admin:
prx's avatar
prx committed
699 700 701 702
                warnmsg = _("Only the administrator can see this page")
                usermsg = _("Username")
                pwmsg = _("Password")
                loginmsg = _("Login")
prx's avatar
prx committed
703
                return '''
prx's avatar
prx committed
704
                <p>{}</p>
705
                <form action="/_admin" method="post">
prx's avatar
prx committed
706 707 708
                    {}: <input name="username" type="text" />
                    {}: <input name="password" type="password" />
                    <input value="{}" type="submit" />
709
                </form>
prx's avatar
prx committed
710
                    '''.format(warnmsg, usermsg, pwmsg, loginmsg)
711 712
            else:
                conf = load_config()
713 714 715 716 717 718 719 720 721

                tplvars = {
                    'cache' : get_cache_size(),
                    'ntor' : len(self.dllist),
                    'startperc' : conf['STARTPERC'],
                    'uplimit' : conf['UPLIMIT'],
                    'downlimit' : conf['DOWNLIMIT']}
                tplvars.update(tpl_msg)
                return template('config', tplvars)
722 723 724 725 726 727

        @self.app.route('/_admin', method='POST')
        def do_login():
            username = request.forms.get('username')
            password = request.forms.get('password')
            if username == self.admin and password == self.adminpw:
Tankypon's avatar
Tankypon committed
728
                response.set_cookie("username", username, secret=self.secret)
729 730
                redirect('/_admin')
            else:
Tankypon's avatar
Tankypon committed
731
                return '<p>{}</p>'.format(_('Go to hell, take the highway. You are not the administrator.'))
prx's avatar
prx committed
732 733 734 735 736 737

        @self.app.route('/_applyconfig', method='post')
        def applyconfig():
            startperc = request.forms.get('startperc')
            uplimit = request.forms.get('uplimit')
            downlimit = request.forms.get('downlimit')
Tankypon's avatar
Tankypon committed
738

prx's avatar
prx committed
739 740 741 742 743 744 745 746 747 748 749 750
            conf = load_config()

            if conf['STARTPERC'] != startperc:
                conf['STARTPERC'] = startperc
                save_config(conf)
            if conf['UPLIMIT'] != uplimit:
                conf['UPLIMIT'] = uplimit
                save_config(conf)
            if conf['DOWNLIMIT'] != downlimit:
                conf['DOWNLIMIT'] = downlimit
                save_config(conf)

Tankypon's avatar
Tankypon committed
751
            self.read_config()
prx's avatar
prx committed
752

753
            redirect('/_admin')
prx's avatar
prx committed
754

755
        @self.app.route('/_goodbye')
prx's avatar
prx committed
756
        def stop_server():
Tankypon's avatar
Tankypon committed
757
            whoami = request.get_cookie("username", secret=self.secret)
758
            if whoami == self.admin:
Tankypon's avatar
Tankypon committed
759
                htmlstuff = "<p>{}.</p><p>{}! ☺</p>".format(_("Don't Panic is now off"), _('See you soon'))
Tankypon's avatar
Tankypon committed
760
                title = "ARGH! X.X"
761 762 763 764

                tplvars = {'htmlstuff' : htmlstuff, 'title' : title }
                tplvars.update(tpl_msg)
                yield template('goodbye', tplvars)
765
                self.stop()
766 767
            else:
                redirect('/')
prx's avatar
prx committed
768

769 770
        @self.app.route('/_toggleep/<title>/')
        def toggle_episode(title):
Tankypon's avatar
Tankypon committed
771
            whoami = request.get_cookie("username", secret=self.secret)
Tankypon's avatar
Tankypon committed
772 773 774 775
            link = request.headers.get('Referer')
            with DontPanicDB() as database:
                database.toggle_in_wl(whoami, title)
            redirect(link)
prx's avatar
prx committed
776

777
        @self.app.route('/_removeinwatchlist/<title>/')
Tankypon's avatar
Tankypon committed
778
        def remove_in_watchlist(title):
779 780 781 782 783 784
            whoami = request.get_cookie("username", secret=self.secret)
            link = request.headers.get('Referer')
            with DontPanicDB() as database:
                database.delete_title_in_wl(whoami, title)
            redirect(link)

785 786 787 788 789 790 791 792
        @self.app.route('/_removeinrecent/<title>/')
        def remove_in_recent(title):
            whoami = request.get_cookie("username", secret=self.secret)
            link = request.headers.get('Referer')
            with DontPanicDB() as database:
                database.remove_in_recent(whoami, title)
            redirect(link)

Tankypon's avatar
Tankypon committed
793 794 795 796 797 798 799 800 801
    def read_config(self):
        conf = load_config()
        self.startperc = conf['STARTPERC']  # percent to start a movie
        self.uplimit = conf['UPLIMIT']
        self.downlimit = conf['DOWNLIMIT']
        return conf

    def open_in_browser(self):
        import webbrowser
802
        time.sleep(1)
803
        webbrowser.open("http://localhost:{}".format(self.port))
Tankypon's avatar
Tankypon committed
804

prx's avatar
prx committed
805
    def stop(self):
806 807 808 809 810 811 812 813 814
        if sys.platform == 'win32':
            """Kill for Win32 (https://docs.python.org/2/faq/windows.html#how-do-i-emulate-os-kill-in-windows)"""
            import ctypes
            kernel32 = ctypes.windll.kernel32
            handle = kernel32.OpenProcess(1, 0, os.getpid())
            kernel32.TerminateProcess(handle, 0)
        else:
            import signal
            os.kill(os.getpid(), signal.SIGKILL)
prx's avatar
prx committed
815 816

    def run(self):
817 818 819
        openurl = Thread(target=self.open_in_browser)
        openurl.daemon = True
        openurl.start()
820
        self.app.run(host=self.host, port=self.port, debug=True)
prx's avatar
prx committed
821

prx's avatar
prx committed
822
    def _add_when_ready(self, torsession, user, title, magnet, isserie):
Tankypon's avatar
Tankypon committed
823
        while not torsession.handle:
824
            time.sleep(1)
Tankypon's avatar
Tankypon committed
825

826 827
        while torsession.get_state() != "downloading metadata":
            time.sleep(1)
Tankypon's avatar
Tankypon committed
828

829 830
        with DontPanicDB() as db:
            if db.is_user_in(user):
831
                logger.info("add {} to watchlist".format(title))
832
                db.add2watchlist(user, title, magnet, isserie)
833

prx's avatar
prx committed
834 835 836 837 838 839
    def _add_to_recent_search(self, user, title, year, poster, isserie):
        with DontPanicDB() as db:
            if db.is_user_in(user):
                logger.info("add {} to recent research".format(title))
                db.add2recent(user, title, year, poster, isserie)

prx's avatar
prx committed
840
    def _clean_torlist(self):
841
        """Remove every finished torrent in self.dllist"""
842 843
        for t in self.dllist:
            if t.get_state() in ['finished', 'seeding']:
844 845
                h = t.handle
                session = t.session
846 847
                session.remove_torrent(h)
                self.dllist.remove(t)
848
                del session
prx's avatar
prx committed
849

Tankypon's avatar
Tankypon committed
850

851
if __name__ == '__main__':
852 853 854
    parser = ArgumentParser(prog=progname, description=_("Dontpanic is a python app to look for any movie or"
                                                         " serie and watch them in streaming. It looks like popcorntime"
                                                         ", but doesn't depend of a centralized api."))
855

Tankypon's avatar
Tankypon committed
856 857
    parser.add_argument("-v", "--verbose", action="count", default=0, help=_("Increase output verbosity"))
    parser.add_argument("--version", action="version", version=progversion, help=_("Print version"))
858 859

    args = parser.parse_args()
860
    setup_logging(args.verbose)
861

862
    os.chdir(os.path.dirname(os.path.abspath(__file__)))
prx's avatar
prx committed
863
    prepconfig()
864

Tankypon's avatar
Tankypon committed
865
    port = get_free_port()  # random port
866 867
    lock("Don't panic", port)

868
    me = getpass.getuser()
869

870
    myapp = DontPanic('localhost', port, me)
Tankypon's avatar
Tankypon committed
871
    myapp.run()
prx's avatar
prx committed
872 873

# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4