Browse Source

Merge branch 'develop' into unicode-decode-error-fix

pull/7166/head
Ruud Burger 8 years ago
committed by GitHub
parent
commit
3a956b338e
  1. 2
      README.md
  2. 2
      couchpotato/core/downloaders/deluge.py
  3. 16
      couchpotato/core/media/_base/providers/torrent/iptorrents.py
  4. 5
      couchpotato/core/media/_base/providers/torrent/kickasstorrents.py
  5. 136
      couchpotato/core/media/_base/providers/torrent/yts.py
  6. 2
      couchpotato/core/media/movie/providers/nzb/nzbclub.py
  7. 8
      couchpotato/core/media/movie/providers/torrent/iptorrents.py
  8. 2
      couchpotato/core/media/movie/providers/torrent/passthepopcorn.py
  9. 10
      couchpotato/core/media/movie/providers/torrent/yts.py
  10. 93
      couchpotato/core/notifications/discord.py
  11. 3
      couchpotato/core/notifications/plex/server.py
  12. 58
      couchpotato/core/notifications/script.py
  13. 7
      couchpotato/core/plugins/quality/main.py
  14. 4
      couchpotato/core/plugins/quality/static/quality.js
  15. 53
      couchpotato/core/plugins/renamer.py
  16. 11
      couchpotato/core/plugins/scanner.py
  17. 2
      couchpotato/templates/login.html

2
README.md

@ -13,7 +13,7 @@ Once a movie is found, it will send it to SABnzbd or download the torrent to a s
CouchPotatoServer can be run from source. This will use *git* as updater, so make sure that is installed.
Windows, see [the CP forum](http://couchpota.to/forum/showthread.php?tid=14) for more details:
Windows, see [the CP forum](http://couchpota.to/forum/viewtopic.php?t=14) for more details:
* Install [Python 2.7](http://www.python.org/download/releases/2.7.3/)
* Then install [PyWin32 2.7](http://sourceforge.net/projects/pywin32/files/pywin32/Build%20217/) and [GIT](http://git-scm.com/)

2
couchpotato/core/downloaders/deluge.py

@ -159,7 +159,7 @@ class Deluge(DownloaderBase):
# If an user opts to seed a torrent forever (usually associated to private trackers usage), stop_ratio will be 0 or -1 (depending on Deluge version).
# In this scenario the status of the torrent would never change from BUSY to SEEDING.
# The last check takes care of this case.
if torrent['is_seed'] and ((tryFloat(torrent['ratio']) < tryFloat(torrent['stop_ratio'])) or (tryFloat(torrent['stop_ratio']) <= 0)):
if torrent['is_seed'] and ((tryFloat(torrent['ratio']) < tryFloat(torrent['stop_ratio'])) or (tryFloat(torrent['stop_ratio']) < 0)):
# We have torrent['seeding_time'] to work out what the seeding time is, but we do not
# have access to the downloader seed_time, as with deluge we have no way to pass it
# when the torrent is added. So Deluge will only look at the ratio.

16
couchpotato/core/media/_base/providers/torrent/iptorrents.py

@ -14,11 +14,11 @@ log = CPLog(__name__)
class Base(TorrentProvider):
urls = {
'test': 'https://iptorrents.eu/',
'base_url': 'https://iptorrents.eu',
'login': 'https://iptorrents.eu/take_login.php',
'login_check': 'https://iptorrents.eu/oldinbox.php',
'search': 'https://iptorrents.eu/t?%s%%s&q=%s&qf=ti#torrents&p=%%d',
'test': 'https://iptorrents.com/',
'base_url': 'https://iptorrents.com',
'login': 'https://iptorrents.com/take_login.php',
'login_check': 'https://iptorrents.com/oldinbox.php',
'search': 'https://iptorrents.com/t?%s%%s&q=%s&qf=ti#torrents&p=%%d',
}
http_time_between_calls = 1 # Seconds
@ -36,6 +36,8 @@ class Base(TorrentProvider):
log.warning('Unable to find category ids for identifier "%s"', quality.get('identifier'))
return None
query = query.replace('"', '')
return self.urls['search'] % ("&".join(("%d=" % x) for x in cat_ids), tryUrlencode(query).replace('%', '%%'))
def _searchOnTitle(self, title, media, quality, results):
@ -61,7 +63,7 @@ class Base(TorrentProvider):
final_page_link = next_link.previous_sibling.previous_sibling
pages = int(final_page_link.string)
result_table = html.find('table', attrs = {'id': 'torrents'})
result_table = html.find('table', attrs={'id': 'torrents'})
if not result_table or 'nothing found!' in data.lower():
return
@ -121,7 +123,7 @@ config = [{
'tab': 'searcher',
'list': 'torrent_providers',
'name': 'IPTorrents',
'description': '<a href="https://iptorrents.eu" target="_blank">IPTorrents</a>',
'description': '<a href="https://iptorrents.com" target="_blank">IPTorrents</a>',
'wizard': True,
'icon': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABRklEQVR42qWQO0vDUBiG8zeKY3EqQUtNO7g0J6ZJ1+ifKIIFQXAqDYKCyaaYxM3udrZLHdRFhXrZ6liCW6mubfk874EESgqaeOCF7/Y8hEh41aq6yZi2nyZgBGya9XKtZs4No05pAkZV2YbEmyMMsoSxLQeC46wCTdPPY4HruPQyGIhF97qLWsS78Miydn4XdK46NJ9OsQAYBzMIMf8MQ9wtCnTdWCaIDx/u7uljOIQEe0hiIWPamSTLay3+RxOCSPI9+RJAo7Er9r2bnqjBFAqyK+VyK4f5/Cr5ni8OFKVCz49PFI5GdNvvU7ttE1M1zMU+8AMqFksEhrMnQsBDzqmDAwzx2ehRLwT7yyCI+vSC99c3mozH1NxrJgWWtR1BOECfEJSVCm6WCzJGCA7+IWhBsM4zywDPwEp4vCjx2DzBH2ODAfsDb33Ps6dQwJgAAAAASUVORK5CYII=',
'options': [

5
couchpotato/core/media/_base/providers/torrent/kickasstorrents.py

@ -30,6 +30,11 @@ class Base(TorrentMagnetProvider):
cat_backup_id = None
proxy_list = [
'http://flowtorrent.com',
'http://katcr.to/span',
'http://dx-torrente.com',
'https://kickass.unblocked.vip',
'https://katcr.co',
'https://kat.how',
'https://kickass.cd',
'https://kickass.unlockproject.online',

136
couchpotato/core/media/_base/providers/torrent/yts.py

@ -0,0 +1,136 @@
from datetime import datetime
from couchpotato.core.helpers.variable import tryInt
from couchpotato.core.logger import CPLog
from couchpotato.core.helpers.variable import getTitle
from couchpotato.core.media._base.providers.torrent.base import TorrentMagnetProvider
import random
log = CPLog(__name__)
class Base(TorrentMagnetProvider):
# Only qualities allowed: 720p/1080p/3D - the rest will fail.
# All YTS.ag torrents are verified
urls = {
'detail': 'https://yts.ag/api#list_movies',
'search': 'https://yts.ag/api/v2/list_movies.json?query_term=%s&limit=%s&page=%s'
}
def _search(self, movie, quality, results):
limit = 10
page = 1
data = self.getJsonData(self.urls['search'] % (getTitle(movie), limit, page))
if data:
movie_count = tryInt(data['data']['movie_count'])
if movie_count == 0:
log.debug('%s - found no results', (self.getName()))
else:
movie_results = data['data']['movies']
for i in range(0,len(movie_results)):
result = data['data']['movies'][i]
name = result['title']
t = movie['info']['original_title'].split(' ')
if all(word in name for word in t) and movie['info']['year'] == result['year']:
year = result['year']
detail_url = result['url']
for torrent in result['torrents']:
t_quality = torrent['quality']
if t_quality in quality['label']:
hash = torrent['hash']
size = tryInt(torrent['size_bytes'] / 1048576)
seeders = tryInt(torrent['seeds'])
leechers = tryInt(torrent['peers'])
pubdate = torrent['date_uploaded'] # format: 2017-02-17 18:40:03
pubdate = datetime.strptime(pubdate, '%Y-%m-%d %H:%M:%S')
age = (datetime.now() - pubdate).days
results.append({
'id': random.randint(100, 9999),
'name': '%s (%s) %s %s %s' % (name, year, 'YTS', t_quality, 'BR-Rip'),
'url': self.make_magnet(hash, name),
'size': size,
'seeders': seeders,
'leechers': leechers,
'age': age,
'detail_url': detail_url,
'score': 1
})
return
def make_magnet(self, hash, name):
url_encoded_trackers = 'udp%3A%2F%2Fopen.demonii.com%3A1337%2Fannounce&tr=%0Audp%3A%2F%2Ftracker.openbittorr' \
'ent.com%3A80&tr=%0Audp%3A%2F%2Ftracker.coppersurfer.tk%3A6969&tr=%0Audp%3A%2F%2Fglot' \
'orrents.pw%3A6969%2Fannounce&tr=%0Audp%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannou' \
'nce&tr=%0Audp%3A%2F%2Ftorrent.gresille.org%3A80%2Fannounce&tr=%0Audp%3A%2F%2Fp4p.are' \
'nabg.com%3A1337&tr=%0Audp%3A%2F%2Ftracker.leechers-paradise.org%3A6969]'
return 'magnet:?xt=urn:btih:%s&dn=%s&tr=%s' % (hash, name.replace(' ', '+'), url_encoded_trackers)
config = [{
'name': 'yts',
'groups': [
{
'tab': 'searcher',
'list': 'torrent_providers',
'name': 'YTS',
'description': '<a href="https://yts.ag/" target="_blank">YTS</a>',
'wizard': True,
'icon': 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAACL0lEQVR4AS1SPW/UQBAd23fxne/Ld2dvzvHuzPocEBAKokCBqG'
'iQ6IgACYmvUKRBFEQgKKGg4BAlUoggggYUEQpSHOI7CIEoQs/fYcbLaU/efTvvvZlnA1qydoxU5kcxX0CkgmQZtPy0hCUjvK+W'
'gEByOZ5dns1O5bzna8fRVkgsxH8B0YouIvBhdD5T11NiVOoKrsttyUcpRW0InUrFnwe9HzuP2uaQZYhF2LQ76TTXw2RVMTK8mY'
'Ybjfh+zNquMVCrqn93aArLSixPxnafdGDLaz1tjY5rmNa8z5BczEQOxQfCl1GyoqoWxYRN1bkh7ELw3q/vhP6HIL4TG9Kumpjg'
'vwuyM7OsjSj98E/vszMfZ7xvPtMaWxGO5crwIumKCR5HxDtJ0AWKGG204RfUd/3smJYqwem/Q7BTS1ZGfM4LNpVwuKAz6cMeRO'
'st0S2EwNE7GjTehO2H3dxqIpdkydat15G3F8SXBi4GlpBNlSz012L/k2+W0CLLk/jbcf13rf41yJeMQ8QWUZiHCfCA9ad+81nE'
'KPtoS9mJOf9v0NmMJHgUT6xayheK9EIK7JJeU/AF4scDF7Y5SPlJrRcxJ+um4ibNEdObxLiIwJim+eT2AL5D9CIcnZ5zvSJi9e'
'IlNHVVtZ831dk5svPgvjPWTq+ktWkd/kD0qtm71x+sDQe3kt6DXnM7Ct+GajmTxKlkAokWljyAKSm5oWa2w+BH4P2UuVub7eTy'
'iGOQYapY/wEztHduSDYz5gAAAABJRU5ErkJggg==',
'options': [
{
'name': 'enabled',
'type': 'enabler',
'default': False
},
{
'name': 'seed_ratio',
'label': 'Seed ratio',
'type': 'float',
'default': 1,
'description': 'Will not be (re)moved until this seed ratio is met.',
},
{
'name': 'seed_time',
'label': 'Seed time',
'type': 'int',
'default': 40,
'description': 'Will not be (re)moved until this seed time (in hours) is met.',
},
{
'name': 'info',
'label': 'Info',
'type':'bool',
'default':'False',
'description': 'YTS will only work if you set the minimum size for 720p to 500 and 1080p to 800',
},
{
'name': 'extra_score',
'advanced': True,
'label': 'Extra Score',
'type': 'int',
'default': 0,
'description': 'Starting score for each release found via this provider.',
}
],
}
]
}]

2
couchpotato/core/media/movie/providers/nzb/nzbclub.py

@ -14,7 +14,7 @@ class NZBClub(MovieProvider, Base):
def buildUrl(self, media):
q = tryUrlencode({
'q': '"%s"' % fireEvent('library.query', media, single = True),
'q': '%s' % fireEvent('library.query', media, single = True),
})
query = tryUrlencode({

8
couchpotato/core/media/movie/providers/torrent/iptorrents.py

@ -11,12 +11,12 @@ class IPTorrents(MovieProvider, Base):
cat_ids = [
([87], ['3d']),
([48], ['720p', '1080p']),
([89], ['bd50']),
([96], ['cam', 'ts', 'tc', 'r5', 'scr']),
([48, 20, 90], ['brrip']),
([48], ['720p', '1080p']),
([48, 20], ['brrip']),
([7, 77], ['dvdrip']),
([6], ['dvdr'])
([6], ['dvdr']),
([96], ['cam', 'ts', 'tc', 'r5', 'scr']),
]
def buildUrl(self, title, media, quality):

2
couchpotato/core/media/movie/providers/torrent/passthepopcorn.py

@ -10,6 +10,7 @@ autoload = 'PassThePopcorn'
class PassThePopcorn(MovieProvider, Base):
quality_search_params = {
'2160p': {'resolution': '4K'},
'bd50': {'media': 'Blu-ray', 'format': 'BD50'},
'1080p': {'resolution': '1080p'},
'720p': {'resolution': '720p'},
@ -24,6 +25,7 @@ class PassThePopcorn(MovieProvider, Base):
}
post_search_filters = {
'2160p': {'Resolution': ['4K']},
'bd50': {'Codec': ['BD50']},
'1080p': {'Resolution': ['1080p']},
'720p': {'Resolution': ['720p']},

10
couchpotato/core/media/movie/providers/torrent/yts.py

@ -0,0 +1,10 @@
from couchpotato.core.logger import CPLog
from couchpotato.core.media._base.providers.torrent.yts import Base
from couchpotato.core.media.movie.providers.base import MovieProvider
log = CPLog(__name__)
autoload = 'Yts'
class Yts(MovieProvider, Base):
pass

93
couchpotato/core/notifications/discord.py

@ -0,0 +1,93 @@
from couchpotato.core.logger import CPLog
from couchpotato.core.notifications.base import Notification
import json
import requests
log = CPLog(__name__)
autoload = 'Discord'
class Discord(Notification):
required_confs = ('webhook_url',)
def notify(self, message='', data=None, listener=None):
for key in self.required_confs:
if not self.conf(key):
log.warning('Discord notifications are enabled, but '
'"{0}" is not specified.'.format(key))
return False
data = data or {}
message = message.strip()
if self.conf('include_imdb') and 'identifier' in data:
template = ' http://www.imdb.com/title/{0[identifier]}/'
message += template.format(data)
headers = {b"Content-Type": b"application/json"}
try:
r = requests.post(self.conf('webhook_url'), data=json.dumps(dict(content=message, username=self.conf('bot_name'), avatar_url=self.conf('avatar_url'), tts=self.conf('discord_tts'))), headers=headers)
r.status_code
except Exception as e:
log.warning('Error Sending Discord response error code: {0}'.format(r.status_code))
return False
return True
config = [{
'name': 'discord',
'groups': [
{
'tab': 'notifications',
'list': 'notification_providers',
'name': 'discord',
'options': [
{
'name': 'enabled',
'default': 0,
'type': 'enabler',
},
{
'name': 'webhook_url',
'description': (
'Your Discord authentication webhook URL.',
'Created under channel settings.'
)
},
{
'name': 'include_imdb',
'default': True,
'type': 'bool',
'descrpition': 'Include a link to the movie page on IMDB.'
},
{
'name': 'bot_name',
'description': 'Name of bot.',
'default': 'CouchPotato',
'advanced': True,
},
{
'name': 'avatar_url',
'description': 'URL to an image to use as the avatar for '
'notifications.',
'default': 'https://couchpota.to/media/images/couch.png',
'advanced': True,
},
{
'name': 'discord_tts',
'default': 0,
'type': 'bool',
'advanced': True,
'description': 'Send notification using text-to-speech.',
},
{
'name': 'on_snatch',
'default': 0,
'type': 'bool',
'advanced': True,
'description': 'Also send message when movie is snatched.',
},
],
}
],
}]

3
couchpotato/core/notifications/plex/server.py

@ -51,7 +51,8 @@ class PlexServer(object):
req = urllib2.Request("https://plex.tv/users/sign_in.xml", data="")
authheader = "Basic %s" % base64.encodestring('%s:%s' % (username, password))[:-1]
req.add_header("Authorization", authheader)
req.add_header("X-Plex-Product", "Couchpotato Notifier")
req.add_header("X-Plex-Device-Name", "CouchPotato")
req.add_header("X-Plex-Product", "CouchPotato Notifier")
req.add_header("X-Plex-Client-Identifier", "b3a6b24dcab2224bdb101fc6aa08ea5e2f3147d6")
req.add_header("X-Plex-Version", "1.0")

58
couchpotato/core/notifications/script.py

@ -0,0 +1,58 @@
import traceback
import subprocess
from couchpotato.core.helpers.encoding import toUnicode
from couchpotato.core.helpers.variable import getIdentifier
from couchpotato.core.logger import CPLog
from couchpotato.core.notifications.base import Notification
log = CPLog(__name__)
autoload = 'Script'
class Script(Notification):
def notify(self, message = '', data = None, listener = None):
if not data: data = {}
script_data = {
'message': toUnicode(message)
}
if getIdentifier(data):
script_data.update({
'imdb_id': getIdentifier(data)
})
try:
subprocess.call([self.conf('path'), message])
return True
except:
log.error('Script notification failed: %s', traceback.format_exc())
return False
config = [{
'name': 'script',
'groups': [
{
'tab': 'notifications',
'list': 'notification_providers',
'name': 'script',
'label': 'Script',
'options': [
{
'name': 'enabled',
'default': 0,
'type': 'enabler',
},
{
'name': 'path',
'description': 'The path to the script to execute.'
}
]
}
]
}]

7
couchpotato/core/plugins/quality/main.py

@ -114,7 +114,12 @@ class QualityPlugin(Plugin):
db = get_db()
quality_dict = {}
quality = db.get('quality', identifier, with_doc = True)['doc']
try:
quality = db.get('quality', identifier, with_doc = True)['doc']
except RecordNotFound:
log.error("Unable to find '%s' in the quality DB", indentifier)
quality = None
if quality:
quality_dict = mergeDicts(self.getQuality(quality['identifier']), quality)

4
couchpotato/core/plugins/quality/static/quality.js

@ -31,9 +31,9 @@ var QualityBase = new Class({
getQuality: function(identifier){
try {
return this.qualities.filter(function(q){
return (this.qualities.filter(function(q){
return q.identifier == identifier;
}).pick();
}).pick() || {});
}
catch(e){}

53
couchpotato/core/plugins/renamer.py

@ -216,6 +216,9 @@ class Renamer(Plugin):
except:
log.error('Failed getting files from %s: %s', (media_folder, traceback.format_exc()))
# post_filter files from configuration; this is a ":"-separated list of globs
files = self.filesAfterIgnoring(files)
db = get_db()
# Extend the download info with info stored in the downloaded release
@ -347,11 +350,22 @@ class Renamer(Plugin):
'category': category_label,
'3d': '3D' if group['meta_data']['quality'].get('is_3d', 0) else '',
'3d_type': group['meta_data'].get('3d_type'),
'3d_type_short': group['meta_data'].get('3d_type'),
}
if replacements['mpaa_only'] not in ('G', 'PG', 'PG-13', 'R', 'NC-17'):
replacements['mpaa_only'] = 'Not Rated'
if replacements['3d_type_short']:
replacements['3d_type_short'] = replacements['3d_type_short'].replace('Half ', 'H').replace('Full ', '')
if self.conf('use_tab_threed') and replacements['3d_type']:
if 'OU' in replacements['3d_type']:
replacements['3d_type'] = replacements['3d_type'].replace('OU','TAB')
if self.conf('use_tab_threed') and replacements['3d_type_short']:
if 'OU' in replacements['3d_type_short']:
replacements['3d_type_short'] = replacements['3d_type_short'].replace('OU','TAB')
for file_type in group['files']:
# Move nfo depending on settings
@ -1165,6 +1179,30 @@ Remove it if you want it to be renamed (again, or at least let it try again)
def movieInFromFolder(self, media_folder):
return media_folder and isSubFolder(media_folder, sp(self.conf('from'))) or not media_folder
@property
def ignored_in_path(self):
return self.conf('ignored_in_path').split(":") if self.conf('ignored_in_path') else []
def filesAfterIgnoring(self, original_file_list):
kept_files = []
for path in original_file_list:
if self.keepFile(path):
kept_files.append(path)
else:
log.debug('Ignored "%s" during renaming', path)
return kept_files
def keepFile(self, filename):
# ignoredpaths
for i in self.ignored_in_path:
if i in filename.lower():
log.debug('Ignored "%s" contains "%s".', (filename, i))
return False
# All is OK
return True
def extractFiles(self, folder = None, media_folder = None, files = None, cleanup = False):
if not files: files = []
@ -1298,6 +1336,7 @@ rename_options = {
'quality_type': '(HD) or (SD)',
'3d': '3D',
'3d_type': '3D Type (Full SBS)',
'3d_type_short' : 'Short 3D Type (FSBS)',
'video': 'Video (x264)',
'audio': 'Audio (DTS)',
'group': 'Releasegroup name',
@ -1360,6 +1399,14 @@ config = [{
},
{
'advanced': True,
'name': 'use_tab_threed',
'type': 'bool',
'label': 'Use TAB 3D',
'description': ('Use TAB (Top And Bottom) instead of OU (Over Under).','This will allow Kodi to recognize vertical formatted 3D movies properly.'),
'default': True
},
{
'advanced': True,
'name': 'replace_doubles',
'type': 'bool',
'label': 'Clean Name',
@ -1367,6 +1414,12 @@ config = [{
'default': True
},
{
'name': 'ignored_in_path',
'label': 'Ignored file patterns',
'description': ('A list of globs to path match when scanning, separated by ":"', 'anything on this list will be skipped during rename operations'),
'default': '*/.sync/*',
},
{
'name': 'unrar',
'type': 'bool',
'description': 'Extract rar files if found.',

11
couchpotato/core/plugins/scanner.py

@ -28,6 +28,7 @@ class Scanner(Plugin):
'_failed_rename_', '.appledouble', '.appledb', '.appledesktop', os.path.sep + '._', '.ds_store', 'cp.cpnfo',
'thumbs.db', 'ehthumbs.db', 'desktop.ini'] # unpacking, smb-crap, hidden files
ignore_names = ['extract', 'extracting', 'extracted', 'movie', 'movies', 'film', 'films', 'download', 'downloads', 'video_ts', 'audio_ts', 'bdmv', 'certificate']
ignored_extensions = ['ignore', 'lftp-pget-status']
extensions = {
'movie': ['mkv', 'wmv', 'avi', 'mpg', 'mpeg', 'mp4', 'm2ts', 'iso', 'img', 'mdf', 'ts', 'm4v', 'flv'],
'movie_extra': ['mds'],
@ -42,9 +43,9 @@ class Scanner(Plugin):
'Half SBS': [('half', 'sbs'), ('h', 'sbs'), 'hsbs'],
'Full SBS': [('full', 'sbs'), ('f', 'sbs'), 'fsbs'],
'SBS': ['sbs'],
'Half OU': [('half', 'ou'), ('h', 'ou'), 'hou'],
'Full OU': [('full', 'ou'), ('h', 'ou'), 'fou'],
'OU': ['ou'],
'Half OU': [('half', 'ou'), ('h', 'ou'), ('half', 'tab'), ('h', 'tab'), 'htab', 'hou'],
'Full OU': [('full', 'ou'), ('f', 'ou'), ('full', 'tab'), ('f', 'tab'), 'ftab', 'fou'],
'OU': ['ou', 'tab'],
'Frame Packed': ['mvc', ('complete', 'bluray')],
'3D': ['3d']
}
@ -225,12 +226,12 @@ class Scanner(Plugin):
group['unsorted_files'].extend(found_files)
leftovers = leftovers - found_files
has_ignored += 1 if ext == 'ignore' else 0
has_ignored += 1 if ext in self.ignored_extensions else 0
if has_ignored == 0:
for file_path in list(group['unsorted_files']):
ext = getExt(file_path)
has_ignored += 1 if ext == 'ignore' else 0
has_ignored += 1 if ext in self.ignored_extensions else 0
if has_ignored > 0:
ignored_identifiers.append(identifier)

2
couchpotato/templates/login.html

@ -25,7 +25,7 @@
<body class="page login">
<form action="" method="post">
<h1>CouchPotato</h1>
<div class="ctrlHolder"><input class="username" name="username" type="text" placeholder="Username" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" /></div>
<div class="ctrlHolder"><input class="username" name="username" type="text" placeholder="Username" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" autofocus="autofocus" /></div>
<div class="ctrlHolder"><input class="password" name="password" type="password" placeholder="Password" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" /></div>
<div class="ctrlHolder">
<label class="remember_me" title="for 30 days"><input id="remember_me" name="remember_me" type="checkbox" value="1" checked="checked" /> Remember me</label>

Loading…
Cancel
Save