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

"""

prx's avatar
prx committed
6 7
Auteur :      thuban <thuban@yeuxdelibad.net>  
licence :     MIT
prx's avatar
prx committed
8 9
"""

10

prx's avatar
prx committed
11
from bottle import *
12
from threading import Thread
13
import webbrowser
Tankypon's avatar
Tankypon committed
14

prx's avatar
prx committed
15 16 17 18 19 20
from .database import DontPanicDB
from .i18n import _
from .subtitle import Subtitle
from .torrentsession import TorrentSession
from .utils import *
from .webutils import *
prx's avatar
prx committed
21

22 23 24 25
if sys.version_info >= (3, 0):
    from urllib.parse import quote as quoteurl
else:
    from urllib import quote as quoteurl
26

prx's avatar
prx committed
27
import locale
Tankypon's avatar
Tankypon committed
28

29
def search2html(keywords, extrawords=' ', searchengine='kickasstorrents', page=0, isserie=0):
30 31 32 33 34 35 36 37 38 39 40 41 42
    """
    Show search result

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

    Return:

    """
43 44
    if searchengine == tpl_msg['tpl_all']:
        searchengine = "*"
Tankypon's avatar
Tankypon committed
45

46
    sch = '{} {}'.format(keywords, extrawords).strip()
47 48
    with TorrentSearch(searchengine) as ts:
        s = ts.search(sch, page)
prx's avatar
prx committed
49

50 51
    # If there is results
    if s:
52
        html = '<table class="sortable">\n'
53 54 55
        html += '<thead>\n'
        html += '<tr>\n'
        html += '<th class="text-left">{}</th>\n'.format(_('Name'))
Tankypon's avatar
Tankypon committed
56 57
        html += '<th>{}</th>\n'.format(_('Size'))
        html += '<th>{}</th>\n'.format(_('Leechs'))
58
        html += '<th id="seeds">{}</th>\n'.format(_('Seeds'))
59 60 61 62
        html += '</tr>\n'
        html += '</thead>\n'

        html += '<tbody>'
63 64
        for result in s:
            link = '/stream/{}/{}/{}'.format(keywords, quoteurl(result['magnet']), isserie)
65 66 67
            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'])
68

69
            html += '<td class="text-center"><a href="{}">{}</a></td>\n'.format(link, result['leechs'])
70 71 72 73 74 75 76 77 78 79 80 81 82 83

            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
84

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

Tankypon's avatar
Tankypon committed
97 98
    return html

prx's avatar
prx committed
99

prx's avatar
prx committed
100
def sch2html(keywords, serie=False):
101 102 103 104 105 106 107 108
    """


    Args:
        keywords:
        serie:

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

Tankypon's avatar
Tankypon committed
122 123
        return html

prx's avatar
prx committed
124

125
def getseriestuff(keywords, year, user=''):
prx's avatar
prx committed
126
    serie_stuff = ''
127
    r = OMDBbAPI.search_by_title(keywords, year)
prx's avatar
prx committed
128
    if r['Response'] == 'True':
129
        eplist = get_episode_list(keywords, year)
130 131 132 133 134 135 136 137
        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'
138 139 140 141
        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)
142
                serie_stuff += '<tr>\n'
Tankypon's avatar
Tankypon committed
143 144 145 146 147 148 149 150 151 152 153 154
                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'])
155

156 157
                with DontPanicDB() as db:
                    if db.is_user_in(user):
158
                        togglelink = "/_toggleep/{}/".format(search)
159 160 161 162
                        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>' \
163
                                       '</td>\n'.format(togglelink, color, seen)
prx's avatar
prx committed
164
                    else:
165 166 167 168 169 170 171
                        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
172
            <script src="/static/perfect-scrollbar.min.js"></script>
173
            <script>
prx's avatar
prx committed
174 175 176 177 178
                window.onload = function() {
                var container = document.getElementById("scroll");
                Ps.initialize(container);
                }

179 180 181 182
            function updateInput(ish){
                document.getElementById('keywords').value = ish;
                document.getElementById('series_form').submit();
            }
183
            </script>"""
184

Tankypon's avatar
Tankypon committed
185
    return serie_stuff
prx's avatar
prx committed
186

187

Tankypon's avatar
Tankypon committed
188
def omdb2template(keywords, year, isserie=0):
prx's avatar
prx committed
189 190 191 192 193 194 195 196
    title = ''
    runtime = ''
    director = ''
    writer = ''
    actors = ''
    poster = ''
    plot = ''
    rating = ''
197
    html = ''
198
    r = OMDBbAPI.search_by_title(keywords, year)
prx's avatar
prx committed
199 200 201 202

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

prx's avatar
prx committed
222 223
        k = keywords
        while len(k.split(' ')) > 1:
Tankypon's avatar
Tankypon committed
224
            k = k.split(' ', 1)[-1]
prx's avatar
prx committed
225
            lop.append(k)
Tankypon's avatar
Tankypon committed
226

Tankypon's avatar
Tankypon committed
227
        for i in lop:
prx's avatar
prx committed
228
            html += ' - '
Tankypon's avatar
Tankypon committed
229
            html += '<a href="/whatsthat/{0}/{1}/{2}">{0}</a>'.format(i, year, isserie)
prx's avatar
prx committed
230 231 232
            html += ' - '
        html += "</p>"

Tankypon's avatar
Tankypon committed
233
    return title, runtime, director, writer, actors, poster, plot, rating, html
234 235 236 237 238


def watchlist(user):
    movies = ''
    series = ''
239 240
    with DontPanicDB() as db:
        watchlists = db.get_watchlist(user)
241

Tankypon's avatar
Tankypon committed
242
    for watch in watchlists:
243
        for ele in watch["list"]:
244 245 246
            date = ele["datetime"]
            title = ele["title"]
            magnet = ele["magnet"]
247
            video = str()
248

249
            link_remove = quoteurl('/_removeinwatchlist/{}/'.format(title))
250 251 252 253 254 255 256 257 258 259 260 261
            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)
262

Tankypon's avatar
Tankypon committed
263
            if watch["type"] == "serie":
264
                series += video
265
            else:
266
                movies += video
267 268

    if len(movies) > 0:
269 270 271 272 273 274 275 276
        movies = """
        <table style="padding-left: 30px;">
            <thead>
                <tr><th>{}</th><th>{}</th></tr>
            </thead>
            <tbody>{}</tbody>
        </table>""".format(_("Date"), _("Title"), movies)

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

Tankypon's avatar
Tankypon committed
286 287
    return movies, series

prx's avatar
prx committed
288

289
def create_select_search_engine(selected):
290
    #list_search_engines = ["btdigg", "kickasstorrents", "piratebay", "torrentproject", tpl_msg['tpl_all']]
291
    list_search_engines = ["kickasstorrents", "piratebay", "isohunt", tpl_msg['tpl_all']]
292 293 294 295 296 297 298 299 300

    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
301
def pick_welcomemsg():
302 303 304 305 306 307 308 309
    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
310 311


prx's avatar
prx committed
312 313


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

prx's avatar
prx committed
328 329 330
        self.app = Bottle()
        self.host = host
        self.port = port
Tankypon's avatar
Tankypon committed
331
        self.local = local_
332
        self.dldic = {}
prx's avatar
prx committed
333
        self.secret = randomtext(22)
Tankypon's avatar
Tankypon committed
334
        self.read_config()
335

336
        self.admin = admin
337
        self.adminpw = adminpw
338

339 340
        self.magnet_as_arg = magnet

341 342
        with DontPanicDB() as db:
            db.add_user(admin, adminpw)
prx's avatar
prx committed
343 344 345

        @self.app.route('/')
        def index():
346
            user = ''
347
            if self.local:
Tankypon's avatar
Tankypon committed
348
                response.set_cookie("username", self.admin, secret=self.secret, httponly=True)
349
                user = self.admin
prx's avatar
prx committed
350 351 352

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

prx's avatar
prx committed
356
            if user:
Tankypon's avatar
Tankypon committed
357 358
                with DontPanicDB() as database:
                    recent_search = database.get_recent_search(user)
prx's avatar
prx committed
359 360 361 362 363 364

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

Tankypon's avatar
Tankypon committed
367 368 369 370 371
                        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)
372

Tankypon's avatar
Tankypon committed
373
                        html += '<li>{}</li>'.format(posterhtml)
prx's avatar
prx committed
374 375

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

377 378 379
            tplvars = {'welcomemsg': pick_welcomemsg(),
                       'recents': html,
                       'logo': pick_logo(),
prx's avatar
prx committed
380
                       'islastv' : self.lastversion,
381
                       'search_engines': create_select_search_engine(self.fav_sch_engine)}
prx's avatar
prx committed
382
            tplvars.update(tpl_msg)
383
            return template('towel', tplvars)
prx's avatar
prx committed
384

prx's avatar
prx committed
385 386 387 388
        @self.app.route('/static/<filename:path>')
        def static(filename):
            return static_file(filename, root='./static/')

389 390
        @self.app.route('/me')
        def login():
Tankypon's avatar
Tankypon committed
391
            whoami = request.get_cookie("username", secret=self.secret)
392
            if not whoami:
prx's avatar
prx committed
393
                return '''
394 395 396 397 398 399 400
                        <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"))
401 402
            else:
                movies, series = watchlist(whoami)
403
                tplvars = {'username': whoami, 'movies': movies, 'series': series}
404
                tplvars.update(tpl_msg)
405

406
                return template('perso', tplvars)
407 408 409

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

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

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

        @self.app.route('/sign')
        def sign():
prx's avatar
prx committed
426
            return ('''
427 428 429 430 431 432 433
                    <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")))
434 435 436

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

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

        @self.app.route('/unsubscribe')
        def unsubscribe():
Tankypon's avatar
Tankypon committed
450
            whoami = request.get_cookie("username", secret=self.secret)
451
            if not whoami:
452 453 454 455 456 457 458 459
                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
460
            html = "<div>{}</div>".format(html)
461 462 463 464 465

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

            return template('classic', tplvars)
466

prx's avatar
prx committed
467 468 469
        @self.app.route('/clean')
        def cleanup():
            cleancache()
470
            redirect('/_admin')
prx's avatar
prx committed
471

472 473
        @self.app.route('/cleantorrent')
        def cleantorrent():
prx's avatar
prx committed
474
            self._clean_torlist()
475
            redirect('/_admin')
prx's avatar
prx committed
476 477 478 479 480 481

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

Tankypon's avatar
Tankypon committed
485
            if not searchengine:
486
                searchengine = ''
Tankypon's avatar
Tankypon committed
487
            if not nocatalog:
prx's avatar
prx committed
488
                nocatalog = 0
489 490 491 492
            if not extrawords:
                extrawords = ' '
            if not isserie:
                isserie = '0'
prx's avatar
prx committed
493 494

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

prx's avatar
prx committed
503
            if nocatalog != "1":
504
                logger.info('Searching {}...   '.format("in series"))
prx's avatar
prx committed
505
                seriessch = sch2html(keywords, serie=True)
506
                logger.info('Searching {}...   '.format("in movies"))
prx's avatar
prx committed
507
                moviessch = sch2html(keywords, serie=False)
prx's avatar
prx committed
508

509 510 511 512 513
            tplvars = {'sch_res': htmlresults,
                       'the_search': '{} {}'.format(keywords, extrawords).strip(),
                       'moviessch': moviessch,
                       'seriessch': seriessch,
                       'isserie': isserie,
514
                       'search_engines': create_select_search_engine(searchengine),
515
                       'nocatalog': nocatalog}
516 517
            tplvars.update(tpl_msg)
            return template('results', tplvars)
prx's avatar
prx committed
518

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

            tplvars = {
533 534 535
                'sch_res': htmlresults,
                'the_search': '{} {}'.format(keywords, extrawords).strip(),
                'moviessch': '',
prx's avatar
prx committed
536
                'seriessch': '',
537
                'search_engines': create_select_search_engine(searchengine),
prx's avatar
prx committed
538
                'isserie': isserie}
539 540
            tplvars.update(tpl_msg)
            return template('results', tplvars)
prx's avatar
prx committed
541

prx's avatar
prx committed
542 543
        @self.app.route('/whatsthat/<title>/<year>/<isserie>')
        def whatsthat(title, year, isserie):
Tankypon's avatar
Tankypon committed
544 545
            title_simplify = simplifyname(title)
            title, runtime, director, writer, actors, poster, plot, rating, html = omdb2template(
prx's avatar
prx committed
546
                title_simplify, year, isserie)
prx's avatar
prx committed
547

prx's avatar
prx committed
548 549
            whoami = request.get_cookie("username", secret=self.secret)
            self._add_to_recent_search(whoami, title, year, poster, isserie)
prx's avatar
prx committed
550

551 552 553 554
            if isserie:
                whoami = request.get_cookie("username", secret=self.secret)
                list_ep_serie = getseriestuff(title, year, user=whoami)

prx's avatar
prx committed
555 556 557 558
            try:
                lang = locale.setlocale(locale.LC_ALL, "")[0:2]  # 'fr' or 'en'
            except:
                lang = 'en'
559
            tplvars = {
560 561 562 563 564 565 566 567 568
                '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
569
                'lang': lang,
570 571
                'rating': rating,
                'html': html,
572
                'isserie': isserie,
573
                'list_ep_serie': list_ep_serie,
574
                'search_engines': create_select_search_engine(self.fav_sch_engine)}
575
            tplvars.update(tpl_msg)
576
            return template('what', tplvars)
577 578

        @self.app.route("/stream/<name>/<magnet:path>/<isserie>")
Tankypon's avatar
Tankypon committed
579
        def stream(name, magnet, isserie):
Tankypon's avatar
Tankypon committed
580
            # Start download
prx's avatar
prx committed
581 582 583 584
            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
585 586
            newses.download(magnet, self.tmp, self.uplimit, self.downlimit)

587
            index = 1
588
            if len(self.dldic) > 0:
589 590
                while index in self.dldic.keys():
                    index += 1
591
            self.dldic[index] = newses
592

Tankypon's avatar
Tankypon committed
593
            # Record to watchlist
Tankypon's avatar
Tankypon committed
594
            whoami = request.get_cookie("username", secret=self.secret)
595
            if whoami:
Tankypon's avatar
Tankypon committed
596
                t = Thread(target=self._add_when_ready, args=(newses, whoami, name, magnet, isserie))
597
                t.start()
prx's avatar
prx committed
598

599 600 601 602 603 604
            tplvars = {'movie_name': name,
                       'status': '',
                       'playlink': '',
                       'subtitles': '',
                       'stop': '',
                       'index': index}
605 606
            tplvars.update(tpl_msg)

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

609 610
        @self.app.route('/_getinfos/<name>/<index:int>')
        def getinfos(name, index):
611 612 613 614
            """
            Give infos html formatted

            Args:
Tankypon's avatar
Tankypon committed
615
                name: Title of the video
Tankypon's avatar
Tankypon committed
616
                index:
617
            """
618
            session = self.dldic[index]
Tankypon's avatar
Tankypon committed
619
            perc = session.get_progress()
620 621 622
            state = session.get_state()
            peers = session.get_peers()
            downrate = session.get_downrate()
prx's avatar
prx committed
623

Tankypon's avatar
Tankypon committed
624
            # Hack for progress bar
625
            # htmlperc = '<div class="progressbar" style="background-size: {}% 100%; ">'.format(perc)
prx's avatar
prx committed
626 627
            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
628
            htmlperc += '</div>'
Tankypon's avatar
Tankypon committed
629

Tankypon's avatar
Tankypon committed
630
            # To stop the download
prx's avatar
prx committed
631 632 633
            stop = ''
            if state != "downloading metadata":
                stop = '<a href="/stopdl/{}" >{}</a>'.format(index, _('Stop this download'))
prx's avatar
prx committed
634

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

Tankypon's avatar
Tankypon committed
647 648 649 650
            return {'status': htmlperc,
                    'playlink': playlink,
                    'subtitles': subtitles,
                    'stop': stop}
prx's avatar
prx committed
651 652 653

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

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

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

prx's avatar
prx committed
671 672
        @self.app.route('/html5/<filename:path>')
        def html5(filename):
prx's avatar
prx committed
673 674 675 676 677 678 679 680 681 682 683 684
            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'
685 686

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

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

697
            tplvars = {'title': os.path.basename(filename), 'htmlstuff': html}
698 699
            tplvars.update(tpl_msg)
            return template('classic', tplvars)
prx's avatar
prx committed
700

prx's avatar
prx committed
701
        @self.app.route('/video/<filename:path>')
prx's avatar
prx committed
702 703
        def send_static_video(filename):
            return static_file(filename, root=self.tmp)
Tankypon's avatar
Tankypon committed
704
            # return "<script>javascript:history.back();</script>"
705

706 707 708
        @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
709
            if which("xdg-open"):
710 711
                import subprocess
                subprocess.Popen(["xdg-open", file_path])
712 713
            else:
                os.startfile(file_path)
714

715 716
            return "<script>javascript:history.back();</script>"

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

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

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

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

739
            tplvars = {'title': title, 'htmlstuff': html}
740 741
            tplvars.update(tpl_msg)
            return template('classic', tplvars)
742 743

        @self.app.route('/dlsub/<lang>/<hash>/<filename:path>')
744
        def dlsub(lang, hash, filename):
Tankypon's avatar
Tankypon committed
745
            if Subtitle.download(hash, lang, os.path.join(self.tmp, filename)):
Tankypon's avatar
Tankypon committed
746
                html = """<div>
747
                        <p>{}<br><a href="/video/{}">{}</a></p>
prx's avatar
prx committed
748
                        <p>{}</p>
749
                        </div>
750 751 752 753
                        """.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"))
754
            else:
Tankypon's avatar
Tankypon committed
755
                html = "<div>{}</div>".format(_("Failed to download subtitles"))
756 757 758 759

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

761
        @self.app.route('/_admin')
prx's avatar
prx committed
762
        def configure():
Tankypon's avatar
Tankypon committed
763
            whoami = request.get_cookie("username", secret=self.secret)
764
            if whoami != self.admin:
prx's avatar
prx committed
765
                return '''
766 767 768 769 770 771 772 773 774 775
                    <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"))
776 777
            else:
                conf = load_config()
778 779

                tplvars = {
780
                    'cache': get_cache_size(),
prx's avatar
prx committed
781
                    'ntor': len(self.dldic),
782 783 784 785 786 787 788 789
                    '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
790
                    }
791 792
                tplvars.update(tpl_msg)
                return template('config', tplvars)
793 794 795

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

        @self.app.route('/_applyconfig', method='post')
        def applyconfig():
805 806
            config_list = ["searchengine", "startperc", "uplimit", "downlimit",
                           "proxy_type", "proxy_hostname", "proxy_password"]
Tankypon's avatar
Tankypon committed
807 808 809 810 811 812 813 814 815 816 817 818 819 820
            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
821

Tankypon's avatar
Tankypon committed
822
            self.read_config()
prx's avatar
prx committed
823

824
            redirect('/_admin')
prx's avatar
prx committed
825

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

833
                tplvars = {'htmlstuff': htmlstuff, 'title': title}
834 835
                tplvars.update(tpl_msg)
                yield template('goodbye', tplvars)
836
                self.stop()
837 838
            else:
                redirect('/')
prx's avatar
prx committed
839

Tankypon's avatar
Tankypon committed
840 841 842
        @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
843 844 845 846 847
                                                                 "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
848
                                                                 "who love free software"))
Tankypon's avatar
Tankypon committed
849 850 851
            title = "Don't Panic"
            version = "{}: {}".format(_("Version"), progversion)

852
            tplvars = {'title': title, 'version': version, 'logo': pick_logo(), 'htmlstuff': htmlstuff}
Tankypon's avatar
Tankypon committed
853 854 855
            tplvars.update(tpl_msg)
            yield template('about', tplvars)

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

864
        @self.app.route('/_removeinwatchlist/<title>/')
Tankypon's avatar
Tankypon committed
865
        def remove_in_watchlist(title):
866 867 868 869 870 871
            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)

872 873 874 875 876 877 878 879
        @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
880 881
        @self.app.route('/_bestvideos/<i:int>')
        def best_videos(i):
Tankypon's avatar
Tankypon committed
882
            # Set source from IMDB
prx's avatar
prx committed
883
            best = BestIMDB()
Tankypon's avatar
Tankypon committed
884 885

            # See the best series
886
            if i == 1:
Tankypon's avatar
Tankypon committed
887
                url = best.url_best_tvshows()
888 889
                title = "Popular series"

Tankypon's avatar
Tankypon committed
890
                results = best.get_best_tvshows()
Tankypon's avatar
Tankypon committed
891
            # See the best dvd's
892
            else:
Tankypon's avatar
Tankypon committed
893
                url = best.url_best_dvd()
894 895
                title = "Best DVD's"

Tankypon's avatar
Tankypon committed
896
                results = best.get_best_dvd()
897 898 899

            html = str()
            for result in results:
Tankypon's avatar
Tankypon committed
900 901 902
                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)
903 904 905

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

Tankypon's avatar
Tankypon committed
906
            tplvars = {'url': url, 'title': title, 'htmlstuff': html}
907
            tplvars.update(tpl_msg)
Tankypon's avatar
Tankypon committed
908
            return template('best', tplvars)
909

Tankypon's avatar
Tankypon committed
910 911
    def read_config(self):
        conf = load_config()
912 913 914 915 916 917 918 919
        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']
920

Tankypon's avatar
Tankypon committed
921 922 923
        return conf

    def open_in_browser(self):
924
        while not is_running():
prx's avatar
prx committed
925
            time.sleep(0.1)
926

prx's avatar
prx committed
927
        time.sleep(0.5)
928
        if self.magnet_as_arg:
929 930 931
            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
932

prx's avatar
prx committed
933
    def stop(self):
Tankypon's avatar
Tankypon committed
934
        if sys.platform == 'win32' and sys.version_info < (3, 2):
935 936 937 938 939 940 941 942
            """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
943 944

    def run(self):
945 946 947
        openurl = Thread(target=self.open_in_browser)
        openurl.daemon = True
        openurl.start()
948 949
        # 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
950
        self.app.run(host=self.host, port=self.port, debug=True, quiet=True, adapter="auto")
951
        # self.app.run(host=self.host, port=self.port, debug=False, quiet=True, adapter="auto")
prx's avatar
prx committed
952

prx's avatar
prx committed
953
    def _add_when_ready(self, torsession, user, title, magnet, isserie):
Tankypon's avatar
Tankypon committed
954
        while not torsession.handle:
955
            time.sleep(1)
Tankypon's avatar
Tankypon committed
956

957 958
        while torsession.get_state() != "downloading metadata":
            time.sleep(1)
Tankypon's avatar
Tankypon committed
959

960 961
        with DontPanicDB() as db:
            if db.is_user_in(user):
962
                logger.info("add {} to watchlist".format(title))
963
                db.add2watchlist(user, title, magnet, isserie)
964

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

Tankypon's avatar
Tankypon committed
981