2
from flexget.utils.cached_input import cached
3
from flexget.utils.search import StringComparator, MovieComparator, AnyComparator, clean_title
4
from flexget.plugin import register_plugin, get_plugin_by_name, PluginError, \
5
get_plugins_by_group, get_plugins_by_phase, PluginWarning
7
log = logging.getLogger('discover')
10
class Discover(object):
11
""" Discover content based on other inputs material.
23
from flexget import validator
24
discover = validator.factory('dict')
26
inputs = discover.accept('list', key='what', required=True).accept('dict')
27
for plugin in get_plugins_by_phase('input'):
28
if hasattr(plugin.instance, 'validator'):
29
inputs.accept(plugin.instance.validator, key=plugin.name)
31
searches = discover.accept('list', key='from', required=True)
32
no_config = searches.accept('choice')
33
for plugin in get_plugins_by_group('search'):
34
if hasattr(plugin.instance, 'validator'):
35
searches.accept('dict').accept(plugin.instance.validator, key=plugin.name)
37
no_config.accept(plugin.name)
39
discover.accept('integer', key='limit')
40
discover.accept('choice', key='type').accept_choices(['any', 'normal', 'exact', 'movies'])
43
def execute_inputs(self, config, feed):
45
:param config: Discover config
46
:param feed: Current feed
47
:return: List of pseudo entries created by inputs under `what` configuration
54
for item in config['what']:
55
for input_name, input_config in item.iteritems():
56
input = get_plugin_by_name(input_name)
57
if input.api_ver == 1:
58
raise PluginError('Plugin %s does not support API v2' % input_name)
59
method = input.phase_handlers['input']
61
result = method(feed, input_config)
62
except PluginError, e:
63
log.warning('Error during input plugin %s: %s' % (input_name, e))
66
log.warning('Input %s did not return anything' % input_name)
70
urls = ([entry['url']] if entry.get('url') else []) + entry.get('urls', [])
71
if any(url in entry_urls for url in urls):
72
log.debug('URL for `%s` already in entry list, skipping.' % entry['title'])
75
if entry['title'] in entry_titles:
76
log.verbose('Ignored duplicate title `%s`' % entry['title']) # TODO: should combine?
79
entry_titles.add(entry['title'])
80
entry_urls.update(urls)
83
def execute_searches(self, config, entries):
85
:param config: Discover plugin config
86
:param entries: List of pseudo entries to search
87
:return: List of entries found from search engines listed under `from` configuration
91
if config.get('type', 'normal') == 'normal':
92
comparator = StringComparator(cutoff=0.7, cleaner=clean_title)
93
elif config['type'] == 'exact':
94
comparator = StringComparator(cutoff=0.9)
95
elif config['type'] == 'any':
96
comparator = AnyComparator()
98
comparator = MovieComparator()
99
for item in config['from']:
100
if isinstance(item, dict):
101
plugin_name, plugin_config = item.items()[0]
103
plugin_name, plugin_config = item, None
104
search = get_plugin_by_name(plugin_name).instance
105
if not callable(getattr(search, 'search')):
106
log.critical('Search plugin %s does not implement search method' % plugin_name)
107
for entry in entries:
109
search_results = search.search(entry['title'], comparator, plugin_config)
110
log.debug('Discovered %s entries from %s' % (len(search_results), plugin_name))
111
result.extend(search_results[:config.get('limit')])
112
except (PluginError, PluginWarning):
113
log.debug('No results from %s' % plugin_name)
114
return sorted(result, reverse=True, key=lambda x: x.get('search_sort'))
117
def on_feed_input(self, feed, config):
118
entries = self.execute_inputs(config, feed)
119
log.verbose('Discovering %i titles ...' % len(entries))
120
if len(entries) > 500:
121
log.critical('Looks like your inputs in discover configuration produced over 500 entries, please reduce the amount!')
122
return self.execute_searches(config, entries)
125
register_plugin(Discover, 'discover', api_ver=2)