dontpanic.py 39.2 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
licence :     MIT
prx's avatar
prx committed
8 9 10 11 12

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

13 14
import getpass
import sys
15

16
from argparse import ArgumentParser
prx's avatar
prx committed
17
from bottle import *
18
from threading import Thread
19
import webbrowser
Tankypon's avatar
Tankypon committed
20

21
from database import DontPanicDB
22
from i18n import _
prx's avatar
prx committed
23
import locale
Tankypon's avatar
Tankypon committed
24
from subtitle import Subtitle
25
from torrentsession import TorrentSession
26 27
from utils import *
from webutils import *
prx's avatar
prx committed
28

29 30 31 32
if sys.version_info >= (3, 0):
    from urllib.parse import quote as quoteurl
else:
    from urllib import quote as quoteurl
33

34 35
logger = logging.getLogger(__name__)

Tankypon's avatar
Tankypon committed
36

37
def search2html(keywords, extrawords=' ', searchengine='btdigg', page=0, isserie=0):
38 39 40 41 42 43 44 45 46 47 48 49 50
    """
    Show search result

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

    Return:

    """
51 52
    if searchengine == tpl_msg['tpl_all']:
        searchengine = "*"
Tankypon's avatar
Tankypon committed
53

54
    sch = '{} {}'.format(keywords, extrawords).strip()
55 56
    with TorrentSearch(searchengine) as ts:
        s = ts.search(sch, page)
prx's avatar
prx committed
57

58 59
    # If there is results
    if s:
60
        html = '<table class="sortable">\n'
61 62 63
        html += '<thead>\n'
        html += '<tr>\n'
        html += '<th class="text-left">{}</th>\n'.format(_('Name'))
Tankypon's avatar
Tankypon committed
64 65
        html += '<th>{}</th>\n'.format(_('Size'))
        html += '<th>{}</th>\n'.format(_('Leechs'))
66
        html += '<th id="seeds">{}</th>\n'.format(_('Seeds'))
67 68 69 70
        html += '</tr>\n'
        html += '</thead>\n'

        html += '<tbody>'
71 72
        for result in s:
            link = '/stream/{}/{}/{}'.format(keywords, quoteurl(result['magnet']), isserie)
73 74 75
            html += '<tr>\n'
            html += '<td><a href="{}">{}</a></td>\n'.format(link, result['name'])
            html += '<td class="text-center"><a href="{}">{}</a></td>\n'.format(link, result['size'])
76

77
            html += '<td class="text-center"><a href="{}">{}</a></td>\n'.format(link, result['leechs'])
78 79 80 81 82 83 84 85 86 87 88 89 90 91

            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'
Tankypon's avatar
Tankypon committed
92

93
            html += '<td class="text-center" style="background:{};"><a href="{}">{}</a></td>'.format(
94
                    color, link, result['seeds'])
95
            html += '</tr>'
96
        html += '</tbody>'
97 98
        html += '</table>'
        if searchengine == 'btdigg':
Tankypon's avatar
Tankypon committed
99
            html += '<a href="/moreresults/{}/{}/{}/{}/{}">{}</a>'.format(
100
                    keywords, int(page) + 1, searchengine, extrawords, isserie, _('More results'))
101
    else:  # If there is no result
Tankypon's avatar
Tankypon committed
102
        html = '<p><br>{}</p>'.format(
prx's avatar
prx committed
103
            _('There is no result for your search, try something else'))
104

Tankypon's avatar
Tankypon committed
105 106
    return html

prx's avatar
prx committed
107

prx's avatar
prx committed
108
def sch2html(keywords, serie=False):
109 110 111 112 113 114 115 116
    """


    Args:
        keywords:
        serie:

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

Tankypon's avatar
Tankypon committed
130 131
        return html

prx's avatar
prx committed
132

133
def getseriestuff(keywords, year, user=''):
prx's avatar
prx committed
134
    serie_stuff = ''
135
    r = OMDBbAPI.search_by_title(keywords, year)
prx's avatar
prx committed
136
    if r['Response'] == 'True':
137
        eplist = get_episode_list(keywords, year)
138 139 140 141 142 143 144 145
        serie_stuff = '<div id="scroll">\n'
        serie_stuff += '<table class="sortable">\n'
        serie_stuff += '<thead>\n<tr>\n<th>{}</th>\n'.format(_('Season'))
        serie_stuff += '<th>{}</th>\n'.format(_('Episode'))
        serie_stuff += '<th>{}</th>\n'.format(_('Title'))
        serie_stuff += '<th class="text-right">{}</th>\n'.format(_('Release date'))
        serie_stuff += '<th>{}</th>\n</tr>\n</thead>\n'.format(_('Seen'))
        serie_stuff += '<tbody>\n'
146 147 148 149
        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)
150
                serie_stuff += '<tr>\n'
Tankypon's avatar
Tankypon committed
151 152 153 154 155 156 157 158 159 160 161 162
                serie_stuff += '<td class="text-center">' \
                               '\t<a href="#" onClick="updateInput(\'{}\')">{:02d}</a>' \
                               '</td>\n'.format(search, int(season['Season']))
                serie_stuff += '<td class="text-center">' \
                               '\t<a href="#" onClick="updateInput(\'{}\')">{:02d}</a>' \
                               '</td>\n'.format(search, int(e['Episode']))
                serie_stuff += '<td>' \
                               '\t<a id="submit" href="#" onClick="updateInput(\'{}\')">{}</a>' \
                               '</td>\n'.format(search, e['Title'])
                serie_stuff += '<td class="text-right">' \
                               '\t<a href="#" onClick="updateInput(\'{}\')">{}</a>' \
                               '</td>\n'.format(search, e['Released'])
163

164 165
                with DontPanicDB() as db:
                    if db.is_user_in(user):
166
                        togglelink = "/_toggleep/{}/".format(search)
167 168 169 170
                        color, seen = ("green", _("Yes")) if db.is_title_in_wl(user, search) else ("red", _("No"))

                        serie_stuff += '<td class="text-center" style="background:{1};">' \
                                       '<a href="{0}">{2}</a>' \
171
                                       '</td>\n'.format(togglelink, color, seen)
prx's avatar
prx committed
172
                    else:
173 174 175 176 177 178 179
                        serie_stuff += '<td class="text-right">?</td>\n'
                serie_stuff += '</tr>\n'
        serie_stuff += '</tbody>\n'
        serie_stuff += '</table>\n'
        serie_stuff += '</div>\n'

        serie_stuff += """
prx's avatar
prx committed
180
            <script src="/static/perfect-scrollbar.min.js"></script>
181
            <script>
prx's avatar
prx committed
182 183 184 185 186
                window.onload = function() {
                var container = document.getElementById("scroll");
                Ps.initialize(container);
                }

187 188 189 190
            function updateInput(ish){
                document.getElementById('keywords').value = ish;
                document.getElementById('series_form').submit();
            }
191
            </script>"""
192

Tankypon's avatar
Tankypon committed
193
    return serie_stuff
prx's avatar
prx committed
194

195

Tankypon's avatar
Tankypon committed
196
def omdb2template(keywords, year, isserie=0):
prx's avatar
prx committed
197 198 199 200 201 202 203 204
    title = ''
    runtime = ''
    director = ''
    writer = ''
    actors = ''
    poster = ''
    plot = ''
    rating = ''
205
    html = ''
206
    r = OMDBbAPI.search_by_title(keywords, year)
prx's avatar
prx committed
207 208 209 210

    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
211
        title = r['Title']
prx's avatar
prx committed
212 213 214 215 216
        runtime = r['Runtime']
        director = r['Director']
        writer = r['Writer']
        actors = r['Actors']
        poster = r['Poster']
prx's avatar
prx committed
217 218
        if poster == 'N/A':
            poster = "/static/unknown.png"
prx's avatar
prx committed
219 220 221
        plot = r['Plot']
        rating = r['imdbRating']
    else:
prx's avatar
prx committed
222
        html = _("Do you mean ? ")
prx's avatar
prx committed
223 224 225 226
        html += "<p>"
        lop = []
        k = keywords
        while len(k.split(' ')) > 1:
Tankypon's avatar
Tankypon committed
227
            k = k.rsplit(' ', 1)[0]
prx's avatar
prx committed
228
            lop.append(k)
Tankypon's avatar
Tankypon committed
229

prx's avatar
prx committed
230 231
        k = keywords
        while len(k.split(' ')) > 1:
Tankypon's avatar
Tankypon committed
232
            k = k.split(' ', 1)[-1]
prx's avatar
prx committed
233
            lop.append(k)
Tankypon's avatar
Tankypon committed
234

Tankypon's avatar
Tankypon committed
235
        for i in lop:
prx's avatar
prx committed
236
            html += ' - '
Tankypon's avatar
Tankypon committed
237
            html += '<a href="/whatsthat/{0}/{1}/{2}">{0}</a>'.format(i, year, isserie)
prx's avatar
prx committed
238 239 240
            html += ' - '
        html += "</p>"

Tankypon's avatar
Tankypon committed
241
    return title, runtime, director, writer, actors, poster, plot, rating, html
242 243 244 245 246


def watchlist(user):
    movies = ''
    series = ''
247 248
    with DontPanicDB() as db:
        watchlists = db.get_watchlist(user)
249

Tankypon's avatar
Tankypon committed
250
    for watch in watchlists:
251
        for ele in watch["list"]:
252 253 254
            date = ele["datetime"]
            title = ele["title"]
            magnet = ele["magnet"]
255
            video = str()
256

257
            link_remove = quoteurl('/_removeinwatchlist/{}/'.format(title))
258 259 260 261 262 263 264 265 266 267 268 269
            link_watch = quoteurl('/stream/{}/{}/{}'.format(title, magnet, 1 if watch["type"] == "serie" else 0))

            video += '<td>{}</td>\n'.format(date)
            video += '<td>{}</td>\n'.format(title)

            actions = str()
            if magnet:
                actions += '<span class="float-right"><a href="{}">▷ {}</a></span>'.format(link_watch, _('Watch again'))
            actions += '<span class="float-right"><a href="{}">✕ {}</a></span>'.format(link_remove, _('Remove'))
            video += '<td>{}</td>\n'.format(actions)

            video = '<tr>{}</tr>\n'.format(video)
270

Tankypon's avatar
Tankypon committed
271
            if watch["type"] == "serie":
272
                series += video
273
            else:
274
                movies += video
275 276

    if len(movies) > 0:
277 278 279 280 281 282 283 284
        movies = """
        <table style="padding-left: 30px;">
            <thead>
                <tr><th>{}</th><th>{}</th></tr>
            </thead>
            <tbody>{}</tbody>
        </table>""".format(_("Date"), _("Title"), movies)

285
    if len(series) > 0:
286 287 288 289 290 291 292
        series = """
        <table style="padding-left: 30px;">
            <thead>
                <tr><th>{}</th><th>{}</th></tr>
            </thead>
            <tbody>{}</tbody>
        </table>""".format(_("Date"), _("Title"), series)
293

Tankypon's avatar
Tankypon committed
294 295
    return movies, series

prx's avatar
prx committed
296

297
def create_select_search_engine(selected):
prx's avatar
prx committed
298
    list_search_engines = ["btdigg", "kickasstorrents", "piratebay", "torrentproject", tpl_msg['tpl_all']]
299 300 301 302 303 304 305 306 307

    list_html = str()
    for search_engine in list_search_engines:
        list_html += "\t<option{}>{}</option>\n".format(" selected" if search_engine == selected else "",
                                                        search_engine)

    return '<select name="searchengine" size="1">\n{}</select>'.format(list_html)


prx's avatar
prx committed
308
def pick_welcomemsg():
309 310 311 312 313 314 315 316
    return WELCOMEMSG[random.randrange(0, len(WELCOMEMSG))]


def pick_logo():
    if time.strftime("%m") == "12":
        return "dontpanic-noel.png"
    else:
        return "favicon.png"
prx's avatar
prx committed
317 318


Tankypon's avatar
Tankypon committed
319
class DontPanic:
prx's avatar
prx committed
320
    def __init__(self, host, port, admin, adminpw="password", local_=True, magnet=""):
Tankypon's avatar
Tankypon committed
321
        self.startperc = None
prx's avatar
prx committed
322
        self.tmp = tempdir
Tankypon's avatar
Tankypon committed
323 324
        self.uplimit = None
        self.downlimit = None
325
        self.proxy = ""
prx's avatar
prx committed
326 327 328 329
        self.proxy_type = ""
        self.proxy_hostname = ""
        self.proxy_username = ""
        self.proxy_password = ""
330
        self.fav_sch_engine = ""
prx's avatar
prx committed
331
        self.lastversion = islastversion()
Tankypon's avatar
Tankypon committed
332

prx's avatar
prx committed
333 334 335
        self.app = Bottle()
        self.host = host
        self.port = port
Tankypon's avatar
Tankypon committed
336
        self.local = local_
337
        self.dldic = {}
prx's avatar
prx committed
338
        self.secret = randomtext(22)
Tankypon's avatar
Tankypon committed
339
        self.read_config()
340

341
        self.admin = admin
342
        self.adminpw = adminpw
343

344 345
        self.magnet_as_arg = magnet

346 347
        with DontPanicDB() as db:
            db.add_user(admin, adminpw)
prx's avatar
prx committed
348 349 350

        @self.app.route('/')
        def index():
351
            user = ''
352
            if self.local:
Tankypon's avatar
Tankypon committed
353
                response.set_cookie("username", self.admin, secret=self.secret, httponly=True)
354
                user = self.admin
prx's avatar
prx committed
355 356 357

            # show recent search
            html = ''
358 359
            if user == '':
                user = request.get_cookie("username", secret=self.secret)
Tankypon's avatar
Tankypon committed
360

prx's avatar
prx committed
361
            if user:
Tankypon's avatar
Tankypon committed
362 363
                with DontPanicDB() as database:
                    recent_search = database.get_recent_search(user)
prx's avatar
prx committed
364 365 366 367 368 369

                if len(recent_search) > 0:
                    for ele in recent_search:
                        title = ele["title"]
                        year = ele["year"]
                        isserie = ele["isserie"]
370 371
                        poster = ele["poster"]

Tankypon's avatar
Tankypon committed
372 373 374 375 376
                        posterhtml = '<img src="{}" title="{} {}" data-toggle="tooltip" ' \
                                     'data-placement="bottom">'.format(poster, title, year)
                        posterhtml = '<a href="/whatsthat/{}/{}/{}">{}</a>'.format(title, year, isserie, posterhtml)
                        posterhtml += '<a class="delete" href="/_removeinrecent/{}/">✗</a>'.format(title)
                        posterhtml = '<div class="show-image">{}</div>'.format(posterhtml)
377

Tankypon's avatar
Tankypon committed
378
                        html += '<li>{}</li>'.format(posterhtml)
prx's avatar
prx committed
379 380

                    html = '<ul class="ca-menu">{}</ul>'.format(html)
381

382 383 384
            tplvars = {'welcomemsg': pick_welcomemsg(),
                       'recents': html,
                       'logo': pick_logo(),
prx's avatar
prx committed
385
                       'islastv' : self.lastversion,
386
                       'search_engines': create_select_search_engine(self.fav_sch_engine)}
prx's avatar
prx committed
387
            tplvars.update(tpl_msg)
388
            return template('towel', tplvars)
prx's avatar
prx committed
389

prx's avatar
prx committed
390 391 392 393
        @self.app.route('/static/<filename:path>')
        def static(filename):
            return static_file(filename, root='./static/')

394 395
        @self.app.route('/me')
        def login():
Tankypon's avatar
Tankypon committed
396
            whoami = request.get_cookie("username", secret=self.secret)
397
            if not whoami:
prx's avatar
prx committed
398
                return '''
399 400 401 402 403 404 405
                        <form action="/me" method="post">
                            {}: <input name="username" type="text" />
                            {}: <input name="password" type="password" />
                            <input value="{}" type="submit" />
                        </form>
                        <p><a href="/sign">{}</a></p>
                        '''.format(_("Username"), _("Password"), _("Login"), _("Register"))
406 407
            else:
                movies, series = watchlist(whoami)
408
                tplvars = {'username': whoami, 'movies': movies, 'series': series}
409
                tplvars.update(tpl_msg)
410

411
                return template('perso', tplvars)
412 413 414

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

Tankypon's avatar
Tankypon committed
417 418
            with DontPanicDB() as database:
                if database.check_login(username, password):
419 420 421
                    response.set_cookie("username", username, secret=self.secret)
                    redirect('/me')
                else:
prx's avatar
prx committed
422
                    abort(401, _('Your login information was incorrect'))
423 424 425 426 427 428 429 430

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

        @self.app.route('/sign')
        def sign():
prx's avatar
prx committed
431
            return ('''
432 433 434 435 436 437 438
                    <h1>{}</h1>
                    <form action="/sign" method="post">
                        {}: <input name="username" type="text" />
                        {}: <input name="password" type="password" />
                        <input value="{}" type="submit" />
                    </form>
                    '''.format(_("Register"), _("Username"), _("Password"), _("Login")))
439 440 441

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

Tankypon's avatar
Tankypon committed
444 445
            with DontPanicDB() as database:
                if database.add_user(username, password):
446 447 448 449
                    return '''
                        <h1>{}</h1>
                        <p><a href="/me">{}</a> ;)</p>
                        '''.format(_("Congratulations, your subscription is a success"), _("Now, log in"))
450
                else:
451
                    return '<h1>{} {}</h1>'.format(_("Oh no!"), _("User {} already exists".format(username)))
452 453 454

        @self.app.route('/unsubscribe')
        def unsubscribe():
Tankypon's avatar
Tankypon committed
455
            whoami = request.get_cookie("username", secret=self.secret)
456
            if not whoami:
457 458 459 460 461 462 463 464
                html = _("You're not logged in")
            else:
                with DontPanicDB() as database:
                    if database.delete_user(whoami):
                        html = _("Your account is deleted")
                    else:
                        html = _("An error occurred while deleting your account. Please contact the administrator")

Tankypon's avatar
Tankypon committed
465
            html = "<div>{}</div>".format(html)
466 467 468 469 470

            tplvars = {'htmlstuff': html, 'title': _("Unsubscribe")}
            tplvars.update(tpl_msg)

            return template('classic', tplvars)
471

prx's avatar
prx committed
472 473 474
        @self.app.route('/clean')
        def cleanup():
            cleancache()
475
            redirect('/_admin')
prx's avatar
prx committed
476

477 478
        @self.app.route('/cleantorrent')
        def cleantorrent():
prx's avatar
prx committed
479
            self._clean_torlist()
480
            redirect('/_admin')
prx's avatar
prx committed
481 482 483 484 485 486

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

Tankypon's avatar
Tankypon committed
490
            if not searchengine:
491
                searchengine = ''
Tankypon's avatar
Tankypon committed
492
            if not nocatalog:
prx's avatar
prx committed
493
                nocatalog = 0
494 495 496 497
            if not extrawords:
                extrawords = ' '
            if not isserie:
                isserie = '0'
prx's avatar
prx committed
498 499

            seriessch, moviessch = '', ''
500
            logger.info('Searching on {}...   '.format(searchengine))
Tankypon's avatar
Tankypon committed
501
            # FIXME: Better handle when a search engine has modified its html code
502 503 504 505
            try:
                htmlresults = search2html(keywords, extrawords=extrawords, searchengine=searchengine, isserie=isserie)
            except Exception as e:
                logger.info('Error while searching :  {}...   '.format(e))
506
                htmlresults = '<div><br>{}</div>'.format(_("Error while searching. Try another engine"))
507

prx's avatar
prx committed
508
            if nocatalog != "1":
509
                logger.info('Searching {}...   '.format("in series"))
prx's avatar
prx committed
510
                seriessch = sch2html(keywords, serie=True)
511
                logger.info('Searching {}...   '.format("in movies"))
prx's avatar
prx committed
512
                moviessch = sch2html(keywords, serie=False)
prx's avatar
prx committed
513

514 515 516 517 518
            tplvars = {'sch_res': htmlresults,
                       'the_search': '{} {}'.format(keywords, extrawords).strip(),
                       'moviessch': moviessch,
                       'seriessch': seriessch,
                       'isserie': isserie,
519
                       'search_engines': create_select_search_engine(searchengine),
520
                       'nocatalog': nocatalog}
521 522
            tplvars.update(tpl_msg)
            return template('results', tplvars)
prx's avatar
prx committed
523

524 525
        @self.app.route('/moreresults/<keywords>/<page>/<searchengine>/<extrawords>/<isserie>')
        def moreresults(keywords, page, searchengine, extrawords, isserie):
Tankypon's avatar
Tankypon committed
526
            # FIXME: Better handle when a search engine has modified its html code
527 528
            try:
                htmlresults = search2html(keywords.strip(),
529 530 531 532
                                          extrawords=extrawords,
                                          searchengine=searchengine,
                                          page=page,
                                          isserie=isserie)
533 534 535
            except Exception as e:
                logger.info('Error while searching :  {}...   '.format(e))
                htmlresults = _("Error while searching. Try another engine")
536 537

            tplvars = {
538 539 540
                'sch_res': htmlresults,
                'the_search': '{} {}'.format(keywords, extrawords).strip(),
                'moviessch': '',
prx's avatar
prx committed
541
                'seriessch': '',
542
                'search_engines': create_select_search_engine(searchengine),
prx's avatar
prx committed
543
                'isserie': isserie}
544 545
            tplvars.update(tpl_msg)
            return template('results', tplvars)
prx's avatar
prx committed
546

prx's avatar
prx committed
547 548
        @self.app.route('/whatsthat/<title>/<year>/<isserie>')
        def whatsthat(title, year, isserie):
Tankypon's avatar
Tankypon committed
549 550
            title_simplify = simplifyname(title)
            title, runtime, director, writer, actors, poster, plot, rating, html = omdb2template(
prx's avatar
prx committed
551
                title_simplify, year, isserie)
prx's avatar
prx committed
552

prx's avatar
prx committed
553 554
            whoami = request.get_cookie("username", secret=self.secret)
            self._add_to_recent_search(whoami, title, year, poster, isserie)
prx's avatar
prx committed
555

556 557 558 559
            if isserie:
                whoami = request.get_cookie("username", secret=self.secret)
                list_ep_serie = getseriestuff(title, year, user=whoami)

prx's avatar
prx committed
560 561 562 563
            try:
                lang = locale.setlocale(locale.LC_ALL, "")[0:2]  # 'fr' or 'en'
            except:
                lang = 'en'
564
            tplvars = {
565 566 567 568 569 570 571 572 573
                'title': title,
                'title_simplify': title_simplify,
                'year': year,
                'runtime': runtime,
                'director': director,
                'writer': writer,
                'actors': actors,
                'poster': poster,
                'plot': plot,
prx's avatar
prx committed
574
                'lang': lang,
575 576
                'rating': rating,
                'html': html,
577
                'isserie': isserie,
578
                'list_ep_serie': list_ep_serie,
579
                'search_engines': create_select_search_engine(self.fav_sch_engine)}
580
            tplvars.update(tpl_msg)
581
            return template('what', tplvars)
582 583

        @self.app.route("/stream/<name>/<magnet:path>/<isserie>")
Tankypon's avatar
Tankypon committed
584
        def stream(name, magnet, isserie):
Tankypon's avatar
Tankypon committed
585
            # Start download
prx's avatar
prx committed
586 587 588 589
            if self.proxy != "none":
                newses = TorrentSession(self.proxy_type, self.proxy_hostname, self.proxy_username, self.proxy_password)
            else:
                newses = TorrentSession()
Tankypon's avatar
Tankypon committed
590 591
            newses.download(magnet, self.tmp, self.uplimit, self.downlimit)

592
            index = 1
593
            if len(self.dldic) > 0:
594 595
                while index in self.dldic.keys():
                    index += 1
596
            self.dldic[index] = newses
597

Tankypon's avatar
Tankypon committed
598
            # Record to watchlist
Tankypon's avatar
Tankypon committed
599
            whoami = request.get_cookie("username", secret=self.secret)
600
            if whoami:
Tankypon's avatar
Tankypon committed
601
                t = Thread(target=self._add_when_ready, args=(newses, whoami, name, magnet, isserie))
602
                t.start()
prx's avatar
prx committed
603

604 605 606 607 608 609
            tplvars = {'movie_name': name,
                       'status': '',
                       'playlink': '',
                       'subtitles': '',
                       'stop': '',
                       'index': index}
610 611
            tplvars.update(tpl_msg)

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

614 615
        @self.app.route('/_getinfos/<name>/<index:int>')
        def getinfos(name, index):
616 617 618 619
            """
            Give infos html formatted

            Args:
Tankypon's avatar
Tankypon committed
620
                name: Title of the video
Tankypon's avatar
Tankypon committed
621
                index:
622
            """
623
            session = self.dldic[index]
Tankypon's avatar
Tankypon committed
624
            perc = session.get_progress()
625 626 627
            state = session.get_state()
            peers = session.get_peers()
            downrate = session.get_downrate()
prx's avatar
prx committed
628

Tankypon's avatar
Tankypon committed
629
            # Hack for progress bar
630
            # htmlperc = '<div class="progressbar" style="background-size: {}% 100%; ">'.format(perc)
prx's avatar
prx committed
631 632
            htmlperc = '<div>{}% <br>{} - {}/s - {} peers</div>'.format(perc, state, downrate, peers)
            htmlperc += '<div class="progressbar"><progress value="{0}" max="100" ></progress>'.format(perc)
prx's avatar
prx committed
633
            htmlperc += '</div>'
Tankypon's avatar
Tankypon committed
634

Tankypon's avatar
Tankypon committed
635
            # To stop the download
prx's avatar
prx committed
636 637 638
            stop = ''
            if state != "downloading metadata":
                stop = '<a href="/stopdl/{}" >{}</a>'.format(index, _('Stop this download'))
prx's avatar
prx committed
639

Tankypon's avatar
Tankypon committed
640
            # Show the play link
641
            moviepath = session.get_file_name()
prx's avatar
prx committed
642 643
            playlink = ''
            subtitles = ''
Tankypon's avatar
Tankypon committed
644
            if perc >= int(self.startperc) and session.ready2watch():
Tankypon's avatar
Tankypon committed
645
                # Show the link to play the file
prx's avatar
prx committed
646
                playlink = '<p><span class="big">' \
Tankypon's avatar
Tankypon committed
647
                           '\t<a href="/html5/{}" target="_blank">{} ▷</a>' \
prx's avatar
prx committed
648
                           '</span></p>'.format(moviepath, _('Play'))
Tankypon's avatar
Tankypon committed
649
                subtitles = '<a href="/getsubtitles/{}/{}" target="_blank">{}</a> - '.format(
prx's avatar
prx committed
650
                    name, moviepath, _('Find subtitles'))
651

Tankypon's avatar
Tankypon committed
652 653 654 655
            return {'status': htmlperc,
                    'playlink': playlink,
                    'subtitles': subtitles,
                    'stop': stop}
prx's avatar
prx committed
656 657 658

        @self.app.route('/stopdl/<i:int>')
        def stopdl(i):
659
            t = self.dldic[i]
prx's avatar
prx committed
660 661
            whoami = request.get_cookie("username", secret=self.secret)
            if whoami != self.admin:
prx's avatar
prx committed
662
                return _("Only the administrator can delete a download")
prx's avatar
prx committed
663
            else:
prx's avatar
prx committed
664
                h = t.handle
665
                session = t.session
prx's avatar
prx committed
666
                session.remove_torrent(h)
667
                self.dldic.pop(i)
668
                del session
669

Tankypon's avatar
Tankypon committed
670
                html = "<div>{}</div>".format(_("Torrent deleted"))
671

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

prx's avatar
prx committed
676 677
        @self.app.route('/html5/<filename:path>')
        def html5(filename):
prx's avatar
prx committed
678 679 680 681 682 683 684 685 686 687 688 689
            mt = mimetypes.guess_type(filename)[0]

            
            if mt == 'video/x-msvideo-nimportequoi' :
                #video = '<object data="/video/{}" width="360" height="250">\n'.format(filename)
                #video += '<param name="src" value="/video/{}" /> </object>\n'.format(filename)
                video = '<embed src="/video/{}">\n'.format(filename)
            else:
                mtype = 'type="{}"'.format(mt)
                video = '<video width="640" controls preload="auto" autoplay="true" >\n'
                video += '<source src="/video/{}" {} >\n'.format(filename, mtype)
                video += '</video>\n'
690 691

            links = '<div>\n'
692 693 694
            links += '<br/>\n'
            links += _("If your browser doesn't support the player above, you may want to : ")
            links += '<br/>\n'
695
            if self.local:
prx's avatar
prx committed
696 697
                links += '<a href="/localvideo/{}">▷ {}</a> - \n'.format(filename, _('Play with external video player'))
            links += '<a href="/video/{}">↓ {}</a>\n'.format(filename, _('Download the video'))
698
            links += '</div>\n'
699

Tankypon's avatar
Tankypon committed
700
            html = "<div>{} {}</div>".format(video, links)
701

702
            tplvars = {'title': os.path.basename(filename), 'htmlstuff': html}
703 704
            tplvars.update(tpl_msg)
            return template('classic', tplvars)
prx's avatar
prx committed
705

prx's avatar
prx committed
706
        @self.app.route('/video/<filename:path>')
prx's avatar
prx committed
707 708
        def send_static_video(filename):
            return static_file(filename, root=self.tmp)
Tankypon's avatar
Tankypon committed
709
            # return "<script>javascript:history.back();</script>"
710

711 712 713
        @self.app.route('/localvideo/<filename:path>')
        def open_in_external_player(filename):
            file_path = os.path.join(self.tmp, filename)
prx's avatar
prx committed
714
            if which("xdg-open"):
715 716
                import subprocess
                subprocess.Popen(["xdg-open", file_path])
717 718
            else:
                os.startfile(file_path)
719

720 721
            return "<script>javascript:history.back();</script>"

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

Tankypon's avatar
Tankypon committed
727
            fullpath = os.path.join(self.tmp, filename)
728
            hash = get_hash(fullpath)
Tankypon's avatar
Tankypon committed
729
            avail = Subtitle.search(hash)
730
            if avail:
731
                subdb = "<p>{}:</p>\n".format(_('Choose a language to download subtitle'))
732 733 734
                subdb += '<ul">\n'
                o = os.path.splitext(filename)[0] + '.srt'
                for l in avail:
Tankypon's avatar
Tankypon committed
735
                    subdb += '<li style="display:inline-block;">- <a href="/dlsub/{0}/{1}/{2}">{0}</a> -</li>\n'.format(
prx's avatar
prx committed
736
                        l, hash, o)
737 738 739

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

Tankypon's avatar
Tankypon committed
742
            html = "<div>{} {}</div>".format(subdb, opensubtitles)
743

744
            tplvars = {'title': title, 'htmlstuff': html}
745 746
            tplvars.update(tpl_msg)
            return template('classic', tplvars)
747 748

        @self.app.route('/dlsub/<lang>/<hash>/<filename:path>')
749
        def dlsub(lang, hash, filename):
Tankypon's avatar
Tankypon committed
750
            if Subtitle.download(hash, lang, os.path.join(self.tmp, filename)):
Tankypon's avatar
Tankypon committed
751
                html = """<div>
752
                        <p>{}<br><a href="/video/{}">{}</a></p>
prx's avatar
prx committed
753
                        <p>{}</p>
754
                        </div>
755 756 757 758
                        """.format(_("Success: your subtitles are in the torrent folder"),
                                   filename,
                                   _("Click here if you want to download them on you computer"),
                                   _("You may now close this page"))
759
            else:
Tankypon's avatar
Tankypon committed
760
                html = "<div>{}</div>".format(_("Failed to download subtitles"))
761 762 763 764

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

766
        @self.app.route('/_admin')
prx's avatar
prx committed
767
        def configure():
Tankypon's avatar
Tankypon committed
768
            whoami = request.get_cookie("username", secret=self.secret)
769
            if whoami != self.admin:
prx's avatar
prx committed
770
                return '''
771 772 773 774 775 776 777 778 779 780
                    <p>{}</p>
                    <form action="/_admin" method="post">
                        {}: <input name="username" type="text" />
                        {}: <input name="password" type="password" />
                        <input value="{}" type="submit" />
                    </form>
                    '''.format(_("Only the administrator can see this page"),
                               _("Username"),
                               _("Password"),
                               _("Login"))
781 782
            else:
                conf = load_config()
783 784

                tplvars = {
785
                    'cache': get_cache_size(),
prx's avatar
prx committed
786
                    'ntor': len(self.dldic),
787 788 789 790 791 792 793 794
                    'startperc': conf['startperc'],
                    'uplimit': conf['uplimit'],
                    'downlimit': conf['downlimit'],
                    'search_engines': create_select_search_engine(conf['searchengine']),
                    'proxy_type': conf['proxy_type'],
                    'proxy_hostname': conf['proxy_hostname'],
                    'proxy_username': conf['proxy_username'],
                    'proxy_password': conf['proxy_password']
prx's avatar
prx committed
795
                    }
796 797
                tplvars.update(tpl_msg)
                return template('config', tplvars)
798 799 800

        @self.app.route('/_admin', method='POST')
        def do_login():
801
            username, password = request.forms.get('username'), request.forms.get('password')
802
            if username == self.admin and password == self.adminpw:
Tankypon's avatar
Tankypon committed
803
                response.set_cookie("username", username, secret=self.secret)
804 805
                redirect('/_admin')
            else:
Tankypon's avatar
Tankypon committed
806
                return '<p>{}</p>'.format(_('Go to hell, take the highway. You are not the administrator.'))
prx's avatar
prx committed
807 808 809

        @self.app.route('/_applyconfig', method='post')
        def applyconfig():
810 811
            config_list = ["searchengine", "startperc", "uplimit", "downlimit",
                           "proxy_type", "proxy_hostname", "proxy_password"]
Tankypon's avatar
Tankypon committed
812 813 814 815 816 817 818 819 820 821 822 823 824 825
            user_config = load_config()
            update_config = False

            for config_to_check in config_list:
                new_config = request.forms.get(config_to_check)

                if user_config[config_to_check] != new_config:
                    user_config[config_to_check] = new_config

                    if not update_config:
                        update_config = True

            if update_config:
                save_config(user_config)
prx's avatar
prx committed
826

Tankypon's avatar
Tankypon committed
827
            self.read_config()
prx's avatar
prx committed
828

829
            redirect('/_admin')
prx's avatar
prx committed
830

831
        @self.app.route('/_goodbye')
prx's avatar
prx committed
832
        def stop_server():
Tankypon's avatar
Tankypon committed
833
            whoami = request.get_cookie("username", secret=self.secret)
834
            if whoami == self.admin:
Tankypon's avatar
Tankypon committed
835
                htmlstuff = "<p>{}.</p><p>{}! ☺</p>".format(_("Don't Panic is now off"), _('See you soon'))
prx's avatar
prx committed
836
                title = "ARGH! 😵"
837

838
                tplvars = {'htmlstuff': htmlstuff, 'title': title}
839 840
                tplvars.update(tpl_msg)
                yield template('goodbye', tplvars)
841
                self.stop()
842 843
            else:
                redirect('/')
prx's avatar
prx committed
844

Tankypon's avatar
Tankypon committed
845 846 847
        @self.app.route('/_about')
        def show_about():
            htmlstuff = "<p>{}. ☺</p><br/><p>{}! ♥</p>".format(_("Dontpanic is a free (as in 'freedom') python "
Tankypon's avatar
Tankypon committed
848 849 850 851 852
                                                                 "application to look for any movie or serie and watch "
                                                                 "them in 'streaming'. It looks like Popcorn Time or "
                                                                 "Torrents Time, but doesn't depend of a centralized "
                                                                 "api"),
                                                               _("This has been made by two independent developers "
prx's avatar
prx committed
853
                                                                 "who love free software"))
Tankypon's avatar
Tankypon committed
854 855 856
            title = "Don't Panic"
            version = "{}: {}".format(_("Version"), progversion)

857
            tplvars = {'title': title, 'version': version, 'logo': pick_logo(), 'htmlstuff': htmlstuff}
Tankypon's avatar
Tankypon committed
858 859 860
            tplvars.update(tpl_msg)
            yield template('about', tplvars)

861 862
        @self.app.route('/_toggleep/<title>/')
        def toggle_episode(title):
Tankypon's avatar
Tankypon committed
863
            whoami = request.get_cookie("username", secret=self.secret)
Tankypon's avatar
Tankypon committed
864 865 866 867
            link = request.headers.get('Referer')
            with DontPanicDB() as database:
                database.toggle_in_wl(whoami, title)
            redirect(link)
prx's avatar
prx committed
868

869
        @self.app.route('/_removeinwatchlist/<title>/')
Tankypon's avatar
Tankypon committed
870
        def remove_in_watchlist(title):
871 872 873 874 875 876
            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)

877 878 879 880 881 882 883 884
        @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)

prx's avatar
prx committed
885 886
        @self.app.route('/_bestvideos/<i:int>')
        def best_videos(i):
Tankypon's avatar
Tankypon committed
887
            # Set source from IMDB
prx's avatar
prx committed
888
            best = BestIMDB()
Tankypon's avatar
Tankypon committed
889 890

            # See the best series
891
            if i == 1:
Tankypon's avatar
Tankypon committed
892
                url = best.url_best_tvshows()
893 894
                title = "Popular series"

Tankypon's avatar
Tankypon committed
895
                results = best.get_best_tvshows()
Tankypon's avatar
Tankypon committed
896
            # See the best dvd's
897
            else:
Tankypon's avatar
Tankypon committed
898
                url = best.url_best_dvd()
899 900
                title = "Best DVD's"

Tankypon's avatar
Tankypon committed
901
                results = best.get_best_dvd()
902 903 904

            html = str()
            for result in results:
Tankypon's avatar
Tankypon committed
905 906 907
                html += '<li><a href="/whatsthat/{1}/{2}/{3}"><img src="{0}" data-toggle="tooltip" ' \
                        'data-placement="bottom" title="{1} {2}"></a></li>'.format(result["img"], result["title"],
                                                                                   result["year"], i)
908 909 910

            html = '<ul class="ca-menu">{}</ul>'.format(html)

Tankypon's avatar
Tankypon committed
911
            tplvars = {'url': url, 'title': title, 'htmlstuff': html}
912
            tplvars.update(tpl_msg)
Tankypon's avatar
Tankypon committed
913
            return template('best', tplvars)
914

Tankypon's avatar
Tankypon committed
915 916
    def read_config(self):
        conf = load_config()
917 918 919 920 921 922 923 924
        self.startperc = conf['startperc']  # percent to start a movie
        self.uplimit = conf['uplimit']
        self.downlimit = conf['downlimit']
        self.fav_sch_engine = conf['searchengine']
        self.proxy = conf['proxy_type']
        self.proxy_hostname = conf['proxy_hostname']
        self.proxy_username = conf['proxy_username']
        self.proxy_password = conf['proxy_password']
925

Tankypon's avatar
Tankypon committed
926 927 928
        return conf

    def open_in_browser(self):
929
        while not is_running():
prx's avatar
prx committed
930
            time.sleep(0.1)
931

prx's avatar
prx committed
932
        time.sleep(0.5)
933
        if self.magnet_as_arg:
934 935 936
            webbrowser.open("http://localhost:{}/stream/dontpanic/{}/0".format(port, self.magnet_as_arg))
        else:
            webbrowser.open("http://localhost:{}".format(self.port))
Tankypon's avatar
Tankypon committed
937

prx's avatar
prx committed
938
    def stop(self):
Tankypon's avatar
Tankypon committed
939
        if sys.platform == 'win32' and sys.version_info < (3, 2):
940 941 942 943 944 945 946 947
            """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
948 949

    def run(self):
950 951 952
        openurl = Thread(target=self.open_in_browser)
        openurl.daemon = True
        openurl.start()
953 954
        # Warning for the debug: http://bottlepy.org/docs/dev/tutorial.html#tutorial-debugging
        # We can use it on the development branch but for the new release, this is recommend to disable it
prx's avatar
prx committed
955
        self.app.run(host=self.host, port=self.port, debug=True, quiet=True, adapter="auto")
956
        # self.app.run(host=self.host, port=self.port, debug=False, quiet=True, adapter="auto")
prx's avatar
prx committed
957

prx's avatar
prx committed
958
    def _add_when_ready(self, torsession, user, title, magnet, isserie):
Tankypon's avatar
Tankypon committed
959
        while not torsession.handle:
960
            time.sleep(1)
Tankypon's avatar
Tankypon committed
961

962 963
        while torsession.get_state() != "downloading metadata":
            time.sleep(1)
Tankypon's avatar
Tankypon committed
964

965 966
        with DontPanicDB() as db:
            if db.is_user_in(user):
967
                logger.info("add {} to watchlist".format(title))
968
                db.add2watchlist(user, title, magnet, isserie)
969

prx's avatar
prx committed
970 971 972 973 974 975
    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
976
    def _clean_torlist(self):
prx's avatar
prx committed
977
        """Remove every finished torrent in self.dldic"""
prx's avatar
prx committed
978
        for i, t in self.dldic.items():
979
            if t.get_state() in ['finished', 'seeding']:
980 981
                h = t.handle
                session = t.session
982
                session.remove_torrent(h)
prx's avatar
prx committed
983
                self.dldic.pop(i)
984
                del session
prx's avatar
prx committed
985

Tankypon's avatar
Tankypon committed
986

987
if __name__ == '__main__':
988 989 990
    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."))
991

Tankypon's avatar
Tankypon committed
992 993
    parser.add_argument("-v", "--verbose", action="count", default=0, help=_("Increase output verbosity"))
    parser.add_argument("--version", action="version", version=progversion, help=_("Print version"))
Tankypon's avatar
Tankypon committed
994
    parser.add_argument("magnet", nargs='?', action="store", default="", help=_("Stream a magnet link"))
995 996

    args = parser.parse_args()
997
    setup_logging(args.verbose)
998

999
    os.chdir(os.path.dirname(os.path.abspath(__file__)))
prx's avatar
prx committed
1000
    prepconfig()
1001

1002
    port = lock()
1003

1004 1005 1006 1007 1008
    # In case if the login contains weird characters
    if sys.platform == 'win32' and sys.version_info < (3, 0):
        me = getpass.getuser().decode('utf-8', errors='ignore')
    else:
        me = getpass.getuser()
1009

1010 1011 1012 1013 1014
    # a magnet link is passed as argument
    mgt = ""
    if args.magnet.startswith("magnet:"):
        mgt = quoteurl(args.magnet)

prx's avatar
prx committed
1015
    if not port:   # already running
1016 1017 1018 1019 1020 1021 1022
        with open(lock_file, 'r') as l:
            port = l.read().strip()
        if mgt != "":
            webbrowser.open("http://localhost:{}/stream/dontpanic/{}/0".format(port, mgt))
        else:
            webbrowser.open("http://localhost:{}".format(port))
    else:
prx's avatar
prx committed
1023
        myapp = DontPanic('localhost', port, me, magnet=mgt)
1024
        myapp.run()
prx's avatar
prx committed
1025 1026

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