Hello community,

here is the log from the commit of package python-shodan for openSUSE:Factory 
checked in at 2019-04-05 11:56:58
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-shodan (Old)
 and      /work/SRC/openSUSE:Factory/.python-shodan.new.3908 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-shodan"

Fri Apr  5 11:56:58 2019 rev:9 rq:687605 version:1.11.1

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-shodan/python-shodan.changes      
2018-12-24 11:43:56.605316407 +0100
+++ /work/SRC/openSUSE:Factory/.python-shodan.new.3908/python-shodan.changes    
2019-04-05 11:56:58.794350141 +0200
@@ -1,0 +2,15 @@
+Sat Mar 16 14:57:46 UTC 2019 - Sebastian Wagner <[email protected]>
+
+- update to version 1.11.1:
+ * Allow a single network alert to monitor multiple IP ranges (#93)
+- update to version 1.11.0:
+ * New command **shodan scan list** to list recently launched scans
+ * New command **shodan alert triggers** to list the available notification 
triggers
+ * New command **shodan alert enable** to enable a notification trigger
+ * New command **shodan alert disable** to disable a notification trigger
+ * New command **shodan alert info** to show details of a specific alert
+ * Include timestamp, vulns and tags in CSV converter (#85)
+ * Fixed bug that caused an exception when parsing uncompressed data files in 
Python3
+ * Code quality improvements
+
+-------------------------------------------------------------------

Old:
----
  shodan-1.10.4.tar.gz

New:
----
  shodan-1.11.1.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-shodan.spec ++++++
--- /var/tmp/diff_new_pack.W2sBZb/_old  2019-04-05 11:57:00.586351416 +0200
+++ /var/tmp/diff_new_pack.W2sBZb/_new  2019-04-05 11:57:00.590351419 +0200
@@ -1,7 +1,7 @@
 #
 # spec file for package python-shodan
 #
-# Copyright (c) 2018 SUSE LINUX GmbH, Nuernberg, Germany.
+# Copyright (c) 2019 SUSE LINUX GmbH, Nuernberg, Germany.
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -19,7 +19,7 @@
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 %{!?license: %global license %doc}
 Name:           python-shodan
-Version:        1.10.4
+Version:        1.11.1
 Release:        0
 Summary:        Python library and command-line utility for Shodan
 License:        MIT

++++++ shodan-1.10.4.tar.gz -> shodan-1.11.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/shodan-1.10.4/.gitignore new/shodan-1.11.1/.gitignore
--- old/shodan-1.10.4/.gitignore        2017-07-05 23:17:17.000000000 +0200
+++ new/shodan-1.11.1/.gitignore        2019-01-13 06:20:00.000000000 +0100
@@ -8,4 +8,6 @@
 tmp/*
 MANIFEST
 .vscode/
-PKG-INFO
\ No newline at end of file
+PKG-INFO
+venv/*
+.idea/*
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/shodan-1.10.4/CHANGELOG.md 
new/shodan-1.11.1/CHANGELOG.md
--- old/shodan-1.10.4/CHANGELOG.md      2018-09-22 04:14:40.000000000 +0200
+++ new/shodan-1.11.1/CHANGELOG.md      2019-02-24 10:29:28.000000000 +0100
@@ -1,6 +1,27 @@
 CHANGELOG
 =========
 
+1.11.1
+------
+* Allow a single network alert to monitor multiple IP ranges (#93)
+
+1.11.0
+------
+* New command **shodan scan list** to list recently launched scans
+* New command **shodan alert triggers** to list the available notification 
triggers
+* New command **shodan alert enable** to enable a notification trigger
+* New command **shodan alert disable** to disable a notification trigger
+* New command **shodan alert info** to show details of a specific alert
+* Include timestamp, vulns and tags in CSV converter (#85)
+* Fixed bug that caused an exception when parsing uncompressed data files in 
Python3
+* Code quality improvements
+* Thank you for contributions from @wagner-certat, @cclauss, @opt9, @voldmar 
and Antoine Neuenschwander
+
+1.10.4
+------
+* Fix a bug when showing old banner records that don't have the "transport" 
property
+* Code quality improvements (bare excepts)
+
 1.10.3
 ------
 * Change bare 'except:' statements to 'except Exception:' or more specific ones
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/shodan-1.10.4/PKG-INFO new/shodan-1.11.1/PKG-INFO
--- old/shodan-1.10.4/PKG-INFO  2018-10-05 03:00:20.000000000 +0200
+++ new/shodan-1.11.1/PKG-INFO  2019-02-24 10:58:50.000000000 +0100
@@ -1,6 +1,6 @@
-Metadata-Version: 1.1
+Metadata-Version: 2.1
 Name: shodan
-Version: 1.10.4
+Version: 1.11.1
 Summary: Python library and command-line utility for Shodan 
(https://developer.shodan.io)
 Home-page: http://github.com/achillean/shodan-python/tree/master
 Author: John Matherly
@@ -26,6 +26,7 @@
         - `Fast/ bulk IP lookups 
<https://help.shodan.io/developer-fundamentals/looking-up-ip-info>`_
         - Streaming API support for real-time consumption of Shodan firehose
         - `Network alerts (aka private firehose) 
<https://help.shodan.io/guides/how-to-monitor-network>`_
+        - `Manage Email Notifications 
<https://asciinema.org/a/7WvyDtNxn0YeNU70ozsxvXDmL>`_
         - Exploit search API fully implemented
         - Bulk data downloads
         - `Command-line interface <https://cli.shodan.io>`_
@@ -96,3 +97,4 @@
 Classifier: Programming Language :: Python :: 3.5
 Classifier: Programming Language :: Python :: 3.6
 Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Description-Content-Type: text/x-rst
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/shodan-1.10.4/README.rst new/shodan-1.11.1/README.rst
--- old/shodan-1.10.4/README.rst        2018-08-17 23:19:34.000000000 +0200
+++ new/shodan-1.11.1/README.rst        2019-02-24 10:33:19.000000000 +0100
@@ -18,6 +18,7 @@
 - `Fast/ bulk IP lookups 
<https://help.shodan.io/developer-fundamentals/looking-up-ip-info>`_
 - Streaming API support for real-time consumption of Shodan firehose
 - `Network alerts (aka private firehose) 
<https://help.shodan.io/guides/how-to-monitor-network>`_
+- `Manage Email Notifications 
<https://asciinema.org/a/7WvyDtNxn0YeNU70ozsxvXDmL>`_
 - Exploit search API fully implemented
 - Bulk data downloads
 - `Command-line interface <https://cli.shodan.io>`_
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/shodan-1.10.4/setup.py new/shodan-1.11.1/setup.py
--- old/shodan-1.10.4/setup.py  2018-10-05 02:49:51.000000000 +0200
+++ new/shodan-1.11.1/setup.py  2019-02-24 10:57:38.000000000 +0100
@@ -6,19 +6,19 @@
 README = open('README.rst', 'r').read()
 
 setup(
-    name = 'shodan',
-    version = '1.10.4',
-    description = 'Python library and command-line utility for Shodan 
(https://developer.shodan.io)',
-    long_description = README,
-    long_description_content_type = 'text/x-rst',
-    author = 'John Matherly',
-    author_email = '[email protected]',
-    url = 'http://github.com/achillean/shodan-python/tree/master',
-    packages = ['shodan', 'shodan.cli', 'shodan.cli.converter'],
-    entry_points = {'console_scripts': ['shodan = shodan.__main__:main']},
-    install_requires = DEPENDENCIES,
-    keywords = ['security', 'network'],
-    classifiers = [
+    name='shodan',
+    version='1.11.1',
+    description='Python library and command-line utility for Shodan 
(https://developer.shodan.io)',
+    long_description=README,
+    long_description_content_type='text/x-rst',
+    author='John Matherly',
+    author_email='[email protected]',
+    url='http://github.com/achillean/shodan-python/tree/master',
+    packages=['shodan', 'shodan.cli', 'shodan.cli.converter'],
+    entry_points={'console_scripts': ['shodan=shodan.__main__:main']},
+    install_requires=DEPENDENCIES,
+    keywords=['security', 'network'],
+    classifiers=[
         'Development Status :: 5 - Production/Stable',
         'Intended Audience :: Developers',
         'License :: OSI Approved :: MIT License',
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/shodan-1.10.4/shodan/__main__.py 
new/shodan-1.11.1/shodan/__main__.py
--- old/shodan-1.10.4/shodan/__main__.py        2018-10-05 02:49:11.000000000 
+0200
+++ new/shodan-1.11.1/shodan/__main__.py        2019-02-11 01:22:17.000000000 
+0100
@@ -49,6 +49,13 @@
 from click_plugins import with_plugins
 from pkg_resources import iter_entry_points
 
+# Large subcommands are stored in separate modules
+from shodan.cli.alert import alert
+from shodan.cli.data import data
+from shodan.cli.organization import org
+from shodan.cli.scan import scan
+
+
 # Make "-h" work like "--help"
 CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'])
 
@@ -58,7 +65,6 @@
 except NameError:
     basestring = str
 
-
 # Define the main entry point for all of our commands
 # and expose a way for 3rd-party plugins to tie into the Shodan CLI.
 @with_plugins(iter_entry_points('shodan.cli.plugins'))
@@ -67,11 +73,7 @@
     pass
 
 
-# Large subcommands are stored in separate modules
-from shodan.cli.alert import alert
-from shodan.cli.data import data
-from shodan.cli.organization import org
-from shodan.cli.scan import scan
+# Setup the large subcommands
 main.add_command(alert)
 main.add_command(data)
 main.add_command(org)
@@ -151,6 +153,7 @@
 
     os.chmod(keyfile, 0o600)
 
+
 @main.command()
 @click.argument('query', metavar='<search query>', nargs=-1)
 def count(query):
@@ -203,7 +206,7 @@
     try:
         total = api.count(query)['total']
         info = api.info()
-    except:
+    except Exception:
         raise click.ClickException('The Shodan API is unresponsive at the 
moment, please try again later.')
 
     # Print some summary information about the download request
@@ -275,7 +278,6 @@
         raise click.ClickException(e.value)
 
 
-
 @main.command()
 def info():
     """Shows general information about your account"""
@@ -308,7 +310,6 @@
 
     has_filters = len(filters) > 0
 
-
     # Setup the output file handle
     fout = None
     if filename:
@@ -333,7 +334,7 @@
             helpers.write_banner(fout, banner)
 
         # Loop over all the fields and print the banner as a row
-        for field in fields:
+        for i, field in enumerate(fields):
             tmp = u''
             value = get_banner_field(banner, field)
             if value:
@@ -351,9 +352,10 @@
                 if color:
                     tmp = click.style(tmp, fg=COLORIZE_FIELDS.get(field, 
'white'))
 
-                # Add the field information to the row
-                row += tmp
-            row += separator
+            # Add the field information to the row
+            if i > 0:
+                row += separator
+            row += tmp
 
         click.echo(row)
 
@@ -519,7 +521,7 @@
                 if len(values) > counter:
                     has_items = True
                     row[pos] = values[counter]['value']
-                    row[pos+1] = values[counter]['count']
+                    row[pos + 1] = values[counter]['count']
 
                 pos += 2
 
@@ -545,7 +547,7 @@
 @click.option('--asn', help='A comma-separated list of ASNs to grab data on.', 
default=None, type=str)
 @click.option('--alert', help='The network alert ID or "all" to subscribe to 
all network alerts on your account.', default=None, type=str)
 @click.option('--compresslevel', help='The gzip compression level (0-9; 0 = no 
compression, 9 = most compression', default=9, type=int)
-def stream(color, fields, separator, limit, datadir, ports, quiet, timeout, 
streamer, countries,  asn, alert, compresslevel):
+def stream(color, fields, separator, limit, datadir, ports, quiet, timeout, 
streamer, countries, asn, alert, compresslevel):
     """Stream data in real-time."""
     # Setup the Shodan API
     key = get_api_key()
@@ -641,9 +643,9 @@
                 if datadir:
                     cur_time = timestr()
                     if cur_time != last_time:
-                            last_time = cur_time
-                            fout.close()
-                            fout = open_streaming_file(datadir, last_time)
+                        last_time = cur_time
+                        fout.close()
+                        fout = open_streaming_file(datadir, last_time)
                     helpers.write_banner(fout, banner)
 
                 # Print the banner information to stdout
@@ -706,7 +708,7 @@
             click.echo(click.style('Not a honeypot', fg='green'))
 
         click.echo('Score: {}'.format(score))
-    except:
+    except Exception:
         raise click.ClickException('Unable to calculate honeyscore')
 
 
@@ -725,5 +727,6 @@
     except Exception as e:
         raise click.ClickException(u'{}'.format(e))
 
+
 if __name__ == '__main__':
     main()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/shodan-1.10.4/shodan/alert.py 
new/shodan-1.11.1/shodan/alert.py
--- old/shodan-1.10.4/shodan/alert.py   2015-11-20 01:41:53.000000000 +0100
+++ new/shodan-1.11.1/shodan/alert.py   1970-01-01 01:00:00.000000000 +0100
@@ -1,9 +0,0 @@
-class Alert:
-    def __init__(self):
-        self.id = None
-        self.name = None
-        self.api_key = None
-        self.filters = None
-        self.credits = None
-        self.created = None
-        self.expires = None
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/shodan-1.10.4/shodan/cli/alert.py 
new/shodan-1.11.1/shodan/cli/alert.py
--- old/shodan-1.10.4/shodan/cli/alert.py       2018-09-02 00:56:08.000000000 
+0200
+++ new/shodan-1.11.1/shodan/cli/alert.py       2019-02-24 10:31:35.000000000 
+0100
@@ -1,8 +1,10 @@
 import click
 import shodan
 
+from operator import itemgetter
 from shodan.cli.helpers import get_api_key
 
+
 @click.group()
 def alert():
     """Manage the network alerts for your account"""
@@ -25,23 +27,61 @@
         raise click.ClickException(e.value)
     click.echo("Alerts deleted")
 
+
 @alert.command(name='create')
 @click.argument('name', metavar='<name>')
[email protected]('netblock', metavar='<netblock>')
-def alert_create(name, netblock):
[email protected]('netblocks', metavar='<netblocks>', nargs=-1)
+def alert_create(name, netblocks):
     """Create a network alert to monitor an external network"""
     key = get_api_key()
 
     # Get the list
     api = shodan.Shodan(key)
     try:
-        alert = api.create_alert(name, netblock)
+        alert = api.create_alert(name, netblocks)
     except shodan.APIError as e:
         raise click.ClickException(e.value)
 
     click.secho('Successfully created network alert!', fg='green')
     click.secho('Alert ID: {}'.format(alert['id']), fg='cyan')
 
+
[email protected](name='info')
[email protected]('alert', metavar='<alert id>')
+def alert_info(alert):
+    """Show information about a specific alert"""
+    key = get_api_key()
+    api = shodan.Shodan(key)
+
+    try:
+        info = api.alerts(aid=alert)
+    except shodan.APIError as e:
+        raise click.ClickException(e.value)
+
+    click.secho(info['name'], fg='cyan')
+    click.secho('Created: ', nl=False, dim=True)
+    click.secho(info['created'], fg='magenta')
+
+    click.secho('Notifications: ', nl=False, dim=True)
+    if 'triggers' in info and info['triggers']:
+        click.secho('enabled', fg='green')
+    else:
+        click.echo('disabled')
+
+    click.echo('')
+    click.secho('Network Range(s):', dim=True)
+
+    for network in info['filters']['ip']:
+        click.echo(u' > {}'.format(click.style(network, fg='yellow')))
+
+    click.echo('')
+    if 'triggers' in info and info['triggers']:
+        click.secho('Triggers:', dim=True)
+        for trigger in info['triggers']:
+            click.echo(u' > {}'.format(click.style(trigger, fg='yellow')))
+        click.echo('')
+
+
 @alert.command(name='list')
 @click.option('--expired', help='Whether or not to show expired alerts.', 
default=True, type=bool)
 def alert_list(expired):
@@ -57,17 +97,21 @@
 
     if len(results) > 0:
         click.echo(u'# {:14} {:<21} {:<15s}'.format('Alert ID', 'Name', 'IP/ 
Network'))
-        # click.echo('#' * 65)
+
         for alert in results:
             click.echo(
                 u'{:16} {:<30} {:<35} '.format(
-                    click.style(alert['id'],  fg='yellow'),
+                    click.style(alert['id'], fg='yellow'),
                     click.style(alert['name'], fg='cyan'),
                     click.style(', '.join(alert['filters']['ip']), fg='white')
                 ),
                 nl=False
             )
 
+            if 'triggers' in alert and alert['triggers']:
+                click.secho('Triggers: ', fg='magenta', nl=False)
+                click.echo(', '.join(alert['triggers'].keys()), nl=False)
+
             if 'expired' in alert and alert['expired']:
                 click.secho('expired', fg='red')
             else:
@@ -89,3 +133,68 @@
     except shodan.APIError as e:
         raise click.ClickException(e.value)
     click.echo("Alert deleted")
+
+
[email protected](name='triggers')
+def alert_list_triggers():
+    """List the available notification triggers"""
+    key = get_api_key()
+
+    # Get the list
+    api = shodan.Shodan(key)
+    try:
+        results = api.alert_triggers()
+    except shodan.APIError as e:
+        raise click.ClickException(e.value)
+
+    if len(results) > 0:
+        click.secho('The following triggers can be enabled on alerts:', 
dim=True)
+        click.echo('')
+
+        for trigger in sorted(results, key=itemgetter('name')):
+            click.secho('{:<12} '.format('Name'), dim=True, nl=False)
+            click.secho(trigger['name'], fg='yellow')
+
+            click.secho('{:<12} '.format('Description'), dim=True, nl=False)
+            click.secho(trigger['description'], fg='cyan')
+
+            click.secho('{:<12} '.format('Rule'), dim=True, nl=False)
+            click.echo(trigger['rule'])
+
+            click.echo('')
+    else:
+        click.echo("No triggers currently available.")
+
+
[email protected](name='enable')
[email protected]('alert_id', metavar='<alert ID>')
[email protected]('trigger', metavar='<trigger name>')
+def alert_enable_trigger(alert_id, trigger):
+    """Enable a trigger for the alert"""
+    key = get_api_key()
+
+    # Get the list
+    api = shodan.Shodan(key)
+    try:
+        api.enable_alert_trigger(alert_id, trigger)
+    except shodan.APIError as e:
+        raise click.ClickException(e.value)
+
+    click.secho('Successfully enabled the trigger: {}'.format(trigger), 
fg='green')
+
+
[email protected](name='disable')
[email protected]('alert_id', metavar='<alert ID>')
[email protected]('trigger', metavar='<trigger name>')
+def alert_disable_trigger(alert_id, trigger):
+    """Disable a trigger for the alert"""
+    key = get_api_key()
+
+    # Get the list
+    api = shodan.Shodan(key)
+    try:
+        api.disable_alert_trigger(alert_id, trigger)
+    except shodan.APIError as e:
+        raise click.ClickException(e.value)
+
+    click.secho('Successfully disabled the trigger: {}'.format(trigger), 
fg='green')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/shodan-1.10.4/shodan/cli/converter/__init__.py 
new/shodan-1.11.1/shodan/cli/converter/__init__.py
--- old/shodan-1.10.4/shodan/cli/converter/__init__.py  2017-06-21 
00:17:07.000000000 +0200
+++ new/shodan-1.11.1/shodan/cli/converter/__init__.py  2019-02-11 
01:01:14.000000000 +0100
@@ -2,4 +2,4 @@
 from .excel import ExcelConverter
 from .geojson import GeoJsonConverter
 from .images import ImagesConverter
-from .kml import KmlConverter
\ No newline at end of file
+from .kml import KmlConverter
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/shodan-1.10.4/shodan/cli/converter/base.py 
new/shodan-1.11.1/shodan/cli/converter/base.py
--- old/shodan-1.10.4/shodan/cli/converter/base.py      2016-02-18 
06:00:23.000000000 +0100
+++ new/shodan-1.11.1/shodan/cli/converter/base.py      2019-02-11 
03:14:17.000000000 +0100
@@ -3,6 +3,6 @@
 
     def __init__(self, fout):
         self.fout = fout
-    
+
     def process(self, fout):
         pass
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/shodan-1.10.4/shodan/cli/converter/csvc.py 
new/shodan-1.11.1/shodan/cli/converter/csvc.py
--- old/shodan-1.10.4/shodan/cli/converter/csvc.py      2018-09-22 
04:14:03.000000000 +0200
+++ new/shodan-1.11.1/shodan/cli/converter/csvc.py      2019-02-11 
03:12:57.000000000 +0100
@@ -24,10 +24,13 @@
         'os',
         'asn',
         'port',
+        'tags',
+        'timestamp',
         'transport',
         'product',
         'version',
-        
+        'vulns',
+
         'ssl.cipher.version',
         'ssl.cipher.bits',
         'ssl.cipher.name',
@@ -36,18 +39,23 @@
         'ssl.cert.serial',
         'ssl.cert.fingerprint.sha1',
         'ssl.cert.fingerprint.sha256',
-        
+
         'html',
         'title',
     ]
-    
+
     def process(self, files):
         writer = csv_writer(self.fout, dialect=excel)
-        
+
         # Write the header
         writer.writerow(self.fields)
-        
+
         for banner in iterate_files(files):
+            # The "vulns" property can't be nicely flattened as-is so we turn
+            # it into a list before processing the banner.
+            if 'vulns' in banner:
+                banner['vulns'] = banner['vulns'].keys()
+
             try:
                 row = []
                 for field in self.fields:
@@ -56,33 +64,32 @@
                 writer.writerow(row)
             except Exception:
                 pass
-    
+
     def banner_field(self, banner, flat_field):
         # The provided field is a collapsed form of the actual field
         fields = flat_field.split('.')
-    
+
         try:
             current_obj = banner
             for field in fields:
                 current_obj = current_obj[field]
-            
+
             # Convert a list into a concatenated string
             if isinstance(current_obj, list):
                 current_obj = ','.join([str(i) for i in current_obj])
-            
+
             return current_obj
         except Exception:
             pass
-    
+
         return ''
-    
+
     def flatten(self, d, parent_key='', sep='.'):
         items = []
         for k, v in d.items():
             new_key = parent_key + sep + k if parent_key else k
             if isinstance(v, MutableMapping):
-                # pylint: disable=E0602
-                items.extend(flatten(v, new_key, sep=sep).items())
+                items.extend(self.flatten(v, new_key, sep=sep).items())
             else:
                 items.append((new_key, v))
         return dict(items)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/shodan-1.10.4/shodan/cli/converter/excel.py 
new/shodan-1.11.1/shodan/cli/converter/excel.py
--- old/shodan-1.10.4/shodan/cli/converter/excel.py     2018-09-22 
04:14:03.000000000 +0200
+++ new/shodan-1.11.1/shodan/cli/converter/excel.py     2019-02-11 
03:14:03.000000000 +0100
@@ -23,7 +23,7 @@
         'transport',
         'product',
         'version',
-        
+
         'http.server',
         'http.title',
     ]
@@ -40,7 +40,7 @@
         'http.server': 'Web Server',
         'http.title': 'Website Title',
     }
-    
+
     def process(self, files):
         # Get the filename from the already-open file handle
         filename = self.fout.name
@@ -55,14 +55,14 @@
         bold = workbook.add_format({
             'bold': 1,
         })
-        
+
         # Create the main worksheet where all the raw data is shown
         main_sheet = workbook.add_worksheet('Raw Data')
 
         # Write the header
-        main_sheet.write(0, 0, 'IP', bold) # The IP field can be either ip_str 
or ipv6 so we treat it differently
+        main_sheet.write(0, 0, 'IP', bold)  # The IP field can be either 
ip_str or ipv6 so we treat it differently
         main_sheet.set_column(0, 0, 20)
-        
+
         row = 0
         col = 1
         for field in self.fields:
@@ -80,7 +80,7 @@
                 for field in self.fields:
                     value = self.banner_field(banner, field)
                     data.append(value)
-                
+
                 # Write those values to the main workbook
                 # Starting off w/ the special "IP" property
                 main_sheet.write_string(row, 0, get_ip(banner))
@@ -92,11 +92,11 @@
                 row += 1
             except Exception:
                 pass
-            
+
             # Aggregate summary information
             total += 1
             ports[banner['port']] += 1
-        
+
         summary_sheet = workbook.add_worksheet('Summary')
         summary_sheet.write(0, 0, 'Total', bold)
         summary_sheet.write(0, 1, total)
@@ -109,22 +109,22 @@
             summary_sheet.write(row, col, key)
             summary_sheet.write(row, col + 1, value)
             row += 1
-    
+
     def banner_field(self, banner, flat_field):
         # The provided field is a collapsed form of the actual field
         fields = flat_field.split('.')
-    
+
         try:
             current_obj = banner
             for field in fields:
                 current_obj = current_obj[field]
-            
+
             # Convert a list into a concatenated string
             if isinstance(current_obj, list):
                 current_obj = ','.join([str(i) for i in current_obj])
-            
+
             return current_obj
         except Exception:
             pass
-    
+
         return ''
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/shodan-1.10.4/shodan/cli/converter/geojson.py 
new/shodan-1.11.1/shodan/cli/converter/geojson.py
--- old/shodan-1.10.4/shodan/cli/converter/geojson.py   2018-09-22 
04:14:03.000000000 +0200
+++ new/shodan-1.11.1/shodan/cli/converter/geojson.py   2019-02-11 
03:19:42.000000000 +0100
@@ -2,39 +2,39 @@
 from .base import Converter
 from ...helpers import get_ip, iterate_files
 
+
 class GeoJsonConverter(Converter):
-    
+
     def header(self):
         self.fout.write("""{
             "type": "FeatureCollection",
             "features": [
         """)
-    
+
     def footer(self):
         self.fout.write("""{ }]}""")
-    
+
     def process(self, files):
         # Write the header
         self.header()
-        
+
         hosts = {}
         for banner in iterate_files(files):
             ip = get_ip(banner)
             if not ip:
                 continue
-    
+
             if ip not in hosts:
                 hosts[ip] = banner
                 hosts[ip]['ports'] = []
-    
+
             hosts[ip]['ports'].append(banner['port'])
-    
+
         for ip, host in iter(hosts.items()):
             self.write(host)
-        
+
         self.footer()
-            
-    
+
     def write(self, host):
         try:
             ip = get_ip(host)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/shodan-1.10.4/shodan/cli/converter/images.py 
new/shodan-1.11.1/shodan/cli/converter/images.py
--- old/shodan-1.10.4/shodan/cli/converter/images.py    2017-06-21 
01:01:02.000000000 +0200
+++ new/shodan-1.11.1/shodan/cli/converter/images.py    2019-02-11 
03:14:34.000000000 +0100
@@ -14,7 +14,7 @@
     # special code in the Shodan CLI that relies on the "dirname" property to 
let
     # the user know where the images have been stored.
     dirname = None
-    
+
     def process(self, files):
         # Get the filename from the already-open file handle and use it as
         # the directory name to store the images.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/shodan-1.10.4/shodan/cli/converter/kml.py 
new/shodan-1.11.1/shodan/cli/converter/kml.py
--- old/shodan-1.10.4/shodan/cli/converter/kml.py       2018-09-22 
04:14:03.000000000 +0200
+++ new/shodan-1.11.1/shodan/cli/converter/kml.py       2019-02-11 
03:19:06.000000000 +0100
@@ -2,38 +2,38 @@
 from .base import Converter
 from ...helpers import iterate_files
 
+
 class KmlConverter(Converter):
-    
+
     def header(self):
         self.fout.write("""<?xml version="1.0" encoding="UTF-8"?>
 <kml xmlns="http://www.opengis.net/kml/2.2";>
   <Document>""")
-    
+
     def footer(self):
         self.fout.write("""</Document></kml>""")
-    
+
     def process(self, files):
         # Write the header
         self.header()
-        
+
         hosts = {}
         for banner in iterate_files(files):
             ip = banner.get('ip_str', banner.get('ipv6', None))
             if not ip:
                 continue
-    
+
             if ip not in hosts:
                 hosts[ip] = banner
                 hosts[ip]['ports'] = []
-    
+
             hosts[ip]['ports'].append(banner['port'])
-    
+
         for ip, host in iter(hosts.items()):
             self.write(host)
-        
+
         self.footer()
-            
-    
+
     def write(self, host):
         try:
             ip = host.get('ip_str', host.get('ipv6', None))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/shodan-1.10.4/shodan/cli/helpers.py 
new/shodan-1.11.1/shodan/cli/helpers.py
--- old/shodan-1.10.4/shodan/cli/helpers.py     2018-09-22 04:14:03.000000000 
+0200
+++ new/shodan-1.11.1/shodan/cli/helpers.py     2019-02-11 01:45:35.000000000 
+0100
@@ -10,6 +10,12 @@
 
 from .settings import SHODAN_CONFIG_DIR
 
+try:
+    basestring            # Python 2
+except NameError:
+    basestring = (str, )  # Python 3
+
+
 def get_api_key():
     '''Returns the API key of the current logged-in user.'''
     shodan_dir = os.path.expanduser(SHODAN_CONFIG_DIR)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/shodan-1.10.4/shodan/cli/host.py 
new/shodan-1.11.1/shodan/cli/host.py
--- old/shodan-1.10.4/shodan/cli/host.py        2018-10-05 01:20:34.000000000 
+0200
+++ new/shodan-1.11.1/shodan/cli/host.py        2019-02-11 01:46:29.000000000 
+0100
@@ -64,9 +64,9 @@
         for port in ports:
             banner = {
                 'port': port,
-                'transport': 'tcp', # All the filtered services use TCP
-                'timestamp': host['data'][-1]['timestamp'], # Use the 
timestamp of the oldest banner
-                'placeholder': True, # Don't store this banner when the file 
is saved
+                'transport': 'tcp',  # All the filtered services use TCP
+                'timestamp': host['data'][-1]['timestamp'],  # Use the 
timestamp of the oldest banner
+                'placeholder': True,  # Don't store this banner when the file 
is saved
             }
             host['data'].append(banner)
 
@@ -94,7 +94,7 @@
         # Show optional ssl info
         if 'ssl' in banner:
             if 'versions' in banner['ssl'] and banner['ssl']['versions']:
-                click.echo('\t|-- SSL Versions: {}'.format(', '.join([version 
for version in sorted(banner['ssl']['versions']) if not 
version.startswith('-')])))
+                click.echo('\t|-- SSL Versions: {}'.format(', '.join([item for 
item in sorted(banner['ssl']['versions']) if not version.startswith('-')])))
             if 'dhparams' in banner['ssl'] and banner['ssl']['dhparams']:
                 click.echo('\t|-- Diffie-Hellman Parameters:')
                 click.echo('\t\t{:15s}{}\n\t\t{:15s}{}'.format('Bits:', 
banner['ssl']['dhparams']['bits'], 'Generator:', 
banner['ssl']['dhparams']['generator']))
@@ -119,4 +119,4 @@
 HOST_PRINT = {
     'pretty': host_print_pretty,
     'tsv': host_print_tsv,
-}
\ No newline at end of file
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/shodan-1.10.4/shodan/cli/organization.py 
new/shodan-1.11.1/shodan/cli/organization.py
--- old/shodan-1.10.4/shodan/cli/organization.py        2018-09-22 
04:14:03.000000000 +0200
+++ new/shodan-1.11.1/shodan/cli/organization.py        2019-02-11 
01:45:19.000000000 +0100
@@ -22,7 +22,7 @@
         api.org.add_member(user, notify=not silent)
     except shodan.APIError as e:
         raise click.ClickException(e.value)
-    
+
     click.secho('Successfully added the new member', fg='green')
 
 
@@ -39,11 +39,11 @@
     click.secho(organization['name'], fg='cyan')
     click.secho('Access Level: ', nl=False, dim=True)
     click.secho(humanize_api_plan(organization['upgrade_type']), fg='magenta')
-    
+
     if organization['domains']:
         click.secho('Authorized Domains: ', nl=False, dim=True)
         click.echo(', '.join(organization['domains']))
-    
+
     click.echo('')
     click.secho('Administrators:', dim=True)
 
@@ -51,8 +51,8 @@
         click.echo(u' > {:30}\t{:30}'.format(
             click.style(admin['username'], fg='yellow'),
             admin['email'])
-            )
-    
+        )
+
     click.echo('')
     if organization['members']:
         click.secho('Members:', dim=True)
@@ -76,5 +76,5 @@
         api.org.remove_member(user)
     except shodan.APIError as e:
         raise click.ClickException(e.value)
-    
+
     click.secho('Successfully removed the member', fg='green')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/shodan-1.10.4/shodan/cli/scan.py 
new/shodan-1.11.1/shodan/cli/scan.py
--- old/shodan-1.10.4/shodan/cli/scan.py        2018-08-31 02:54:21.000000000 
+0200
+++ new/shodan-1.11.1/shodan/cli/scan.py        2019-02-11 03:11:51.000000000 
+0100
@@ -17,6 +17,35 @@
     pass
 
 
[email protected](name='list')
+def scan_list():
+    """Show recently launched scans"""
+    key = get_api_key()
+
+    # Get the list
+    api = shodan.Shodan(key)
+    try:
+        scans = api.scans()
+    except shodan.APIError as e:
+        raise click.ClickException(e.value)
+
+    if len(scans) > 0:
+        click.echo(u'# {} Scans Total - Showing 10 most recent 
scans:'.format(scans['total']))
+        click.echo(u'# {:20} {:<15} {:<10} {:<15s}'.format('Scan ID', 
'Status', 'Size', 'Timestamp'))
+        # click.echo('#' * 65)
+        for scan in scans['matches'][:10]:
+            click.echo(
+                u'{:31} {:<24} {:<10} {:<15s}'.format(
+                    click.style(scan['id'], fg='yellow'),
+                    click.style(scan['status'], fg='cyan'),
+                    scan['size'],
+                    scan['created']
+                )
+            )
+    else:
+        click.echo("You haven't yet launched any scans.")
+
+
 @scan.command(name='internet')
 @click.option('--quiet', help='Disable the printing of information to the 
screen.', default=False, is_flag=True)
 @click.argument('port', type=int)
@@ -58,10 +87,9 @@
 
                             if not quiet:
                                 click.echo('{0:<40} {1:<20} {2}'.format(
-                                        click.style(helpers.get_ip(banner), 
fg=COLORIZE_FIELDS['ip_str']),
-                                        click.style(str(banner['port']), 
fg=COLORIZE_FIELDS['port']),
-                                        ';'.join(banner['hostnames'])
-                                    )
+                                    click.style(helpers.get_ip(banner), 
fg=COLORIZE_FIELDS['ip_str']),
+                                    click.style(str(banner['port']), 
fg=COLORIZE_FIELDS['port']),
+                                    ';'.join(banner['hostnames']))
                                 )
                     except shodan.APIError as e:
                         # We stop waiting for results if the scan has been 
processed by the crawlers and
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/shodan-1.10.4/shodan/cli/worldmap.py 
new/shodan-1.11.1/shodan/cli/worldmap.py
--- old/shodan-1.10.4/shodan/cli/worldmap.py    2018-09-22 04:14:03.000000000 
+0200
+++ new/shodan-1.11.1/shodan/cli/worldmap.py    2019-02-11 01:44:09.000000000 
+0100
@@ -108,14 +108,14 @@
         TODO: filter out stuff that doesn't fit
         TODO: make it possible to use "zoomed" maps
         """
-        width = (self.corners[3]-self.corners[1])
-        height = (self.corners[2]-self.corners[0])
+        width = (self.corners[3] - self.corners[1])
+        height = (self.corners[2] - self.corners[0])
 
         # change to 0-180, 0-360
-        abs_lat = -lat+90
-        abs_lon = lon+180
-        x = (abs_lon/360.0)*width + self.corners[1]
-        y = (abs_lat/180.0)*height + self.corners[0]
+        abs_lat = -lat + 90
+        abs_lon = lon + 180
+        x = (abs_lon / 360.0) * width + self.corners[1]
+        y = (abs_lat / 180.0) * height + self.corners[0]
         return int(x), int(y)
 
     def set_data(self, data):
@@ -155,7 +155,7 @@
         self.window.addstr(0, 0, self.map)
 
         # FIXME: position to be defined in map config?
-        row = self.corners[2]-6
+        row = self.corners[2] - 6
         items_to_show = 5
         for lat, lon, char, desc, attrs, color in self.data:
             # to make this work almost everywhere. see 
http://docs.python.org/2/library/curses.html
@@ -177,7 +177,7 @@
                     self.window.addstr(row, 1, det_show, attrs)
                     row += 1
                     items_to_show -= 1
-                except StandardError:
+                except Exception:
                     # FIXME: check window size before addstr()
                     break
         self.window.overwrite(target)
@@ -257,6 +257,7 @@
     api = Shodan(get_api_key())
     return launch_map(api)
 
+
 if __name__ == '__main__':
     import sys
     sys.exit(main())
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/shodan-1.10.4/shodan/client.py 
new/shodan-1.11.1/shodan/client.py
--- old/shodan-1.10.4/shodan/client.py  2018-09-22 04:14:03.000000000 +0200
+++ new/shodan-1.11.1/shodan/client.py  2019-02-11 01:04:51.000000000 +0100
@@ -176,7 +176,7 @@
         :param key: The Shodan API key.
         :type key: str
         :param proxies: A proxies array for the requests library, e.g. 
{'https': 'your proxy'}
-        :type key: dict
+        :type proxies: dict
         """
         self.api_key = key
         self.base_url = 'https://api.shodan.io'
@@ -347,6 +347,16 @@
 
         return self._request('/shodan/scan', params, method='post')
 
+    def scans(self, page=1):
+        """Get a list of scans submitted
+
+        :param page: Page through the list of scans 100 results at a time
+        :type page: int
+        """
+        return self._request('/shodan/scans', {
+            'page': page,
+        })
+
     def scan_internet(self, port, protocol):
         """Scan a network using Shodan
 
@@ -438,7 +448,7 @@
                     try:
                         yield banner
                     except GeneratorExit:
-                        return # exit out of the function
+                        return  # exit out of the function
                 page += 1
                 tries = 0
             except Exception:
@@ -447,7 +457,7 @@
                     break
 
                 tries += 1
-                time.sleep(1.0) # wait 1 second if the search errored out for 
some reason
+                time.sleep(1.0)  # wait 1 second if the search errored out for 
some reason
 
     def search_tokens(self, query):
         """Returns information about the search query itself (filters used 
etc.)
@@ -507,8 +517,8 @@
     def queries_tags(self, size=10):
         """Search the directory of saved search queries in Shodan.
 
-        :param query: The number of tags to return
-        :type page: int
+        :param size: The number of tags to return
+        :type size: int
 
         :returns: A list of tags.
         """
@@ -518,12 +528,14 @@
         return self._request('/shodan/query/tags', args)
 
     def create_alert(self, name, ip, expires=0):
-        """Search the directory of saved search queries in Shodan.
+        """Create a network alert/ private firehose for the specified IP 
range(s)
 
-        :param query: The number of tags to return
-        :type page: int
+        :param name: Name of the alert
+        :type name: str
+        :param ip: Network range(s) to monitor
+        :type ip: str OR list of str
 
-        :returns: A list of tags.
+        :returns: A dict describing the alert
         """
         data = {
             'name': name,
@@ -547,8 +559,7 @@
 
         response = api_request(self.api_key, func, params={
             'include_expired': include_expired,
-            },
-            proxies=self._session.proxies)
+        }, proxies=self._session.proxies)
 
         return response
 
@@ -561,3 +572,17 @@
 
         return response
 
+    def alert_triggers(self):
+        """Return a list of available triggers that can be enabled for alerts.
+
+        :returns: A list of triggers
+        """
+        return self._request('/shodan/alert/triggers', {})
+
+    def enable_alert_trigger(self, aid, trigger):
+        """Enable the given trigger on the alert."""
+        return self._request('/shodan/alert/{}/trigger/{}'.format(aid, 
trigger), {}, method='put')
+
+    def disable_alert_trigger(self, aid, trigger):
+        """Disable the given trigger on the alert."""
+        return self._request('/shodan/alert/{}/trigger/{}'.format(aid, 
trigger), {}, method='delete')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/shodan-1.10.4/shodan/exception.py 
new/shodan-1.11.1/shodan/exception.py
--- old/shodan-1.10.4/shodan/exception.py       2018-08-18 03:27:55.000000000 
+0200
+++ new/shodan-1.11.1/shodan/exception.py       2019-02-11 01:35:57.000000000 
+0100
@@ -2,11 +2,10 @@
     """This exception gets raised whenever a non-200 status code was returned 
by the Shodan API."""
     def __init__(self, value):
         self.value = value
-    
+
     def __str__(self):
         return self.value
 
 
 class APITimeout(APIError):
-       pass
-
+    pass
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/shodan-1.10.4/shodan/helpers.py 
new/shodan-1.11.1/shodan/helpers.py
--- old/shodan-1.10.4/shodan/helpers.py 2018-09-23 03:58:44.000000000 +0200
+++ new/shodan-1.11.1/shodan/helpers.py 2019-02-16 03:46:04.000000000 +0100
@@ -19,7 +19,7 @@
         if isinstance(facet, basestring):
             facet_str += facet
         else:
-            facet_str += '%s:%s'  % (facet[0], facet[1])
+            facet_str += '{}:{}'.format(facet[0], facet[1])
         facet_str += ','
     return facet_str[:-1]
 
@@ -76,7 +76,7 @@
     # Parse the text into JSON
     try:
         data = data.json()
-    except:
+    except Exception:
         raise APIError('Unable to parse JSON response')
 
     # Raise an exception if an error occurred
@@ -113,12 +113,14 @@
 
         for line in fin:
             # Ensure the line has been decoded into a string to prevent errors 
w/ Python3
-            line = line.decode('utf-8')
+            if not isinstance(line, basestring):
+                line = line.decode('utf-8')
 
             # Convert the JSON into a native Python object
             banner = loads(line)
             yield banner
 
+
 def get_screenshot(banner):
     if 'opts' in banner and 'screenshot' in banner['opts']:
         return banner['opts']['screenshot']
@@ -159,14 +161,13 @@
     >>> humanize_bytes(1024*1234*1111,1)
     '1.3 GB'
     """
-
     if bytes == 1:
         return '1 byte'
     if bytes < 1024:
         return '%.*f %s' % (precision, bytes, "bytes")
 
     suffixes = ['KB', 'MB', 'GB', 'TB', 'PB']
-    multiple = 1024.0    #.0 force float on python 2
+    multiple = 1024.0  # .0 to force float on python 2
     for suffix in suffixes:
         bytes /= multiple
         if bytes < multiple:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/shodan-1.10.4/shodan/stream.py 
new/shodan-1.11.1/shodan/stream.py
--- old/shodan-1.10.4/shodan/stream.py  2018-08-01 08:07:51.000000000 +0200
+++ new/shodan-1.11.1/shodan/stream.py  2019-02-11 01:05:46.000000000 +0100
@@ -21,7 +21,7 @@
 
         # The user doesn't want to use a timeout
         # If the timeout is specified as 0 then we also don't want to have a 
timeout
-        if ( timeout and timeout <= 0 ) or ( timeout == 0 ):
+        if (timeout and timeout <= 0) or (timeout == 0):
             timeout = None
 
         # If the user requested a timeout then we need to disable heartbeat 
messages
@@ -43,16 +43,16 @@
                 # not specific to Cloudflare.
                 if req.status_code != 524 or timeout >= 0:
                     break
-        except Exception as e:
+        except Exception:
             raise APIError('Unable to contact the Shodan Streaming API')
 
         if req.status_code != 200:
             try:
                 data = json.loads(req.text)
                 raise APIError(data['error'])
-            except APIError as e:
+            except APIError:
                 raise
-            except Exception as e:
+            except Exception:
                 pass
             raise APIError('Invalid API key or you do not have access to the 
Streaming API')
         if req.encoding is None:
@@ -78,9 +78,9 @@
         try:
             for line in self._iter_stream(stream, raw):
                 yield line
-        except requests.exceptions.ConnectionError as e:
+        except requests.exceptions.ConnectionError:
             raise APIError('Stream timed out')
-        except ssl.SSLError as e:
+        except ssl.SSLError:
             raise APIError('Stream timed out')
 
     def asn(self, asn, raw=False, timeout=None):
@@ -123,4 +123,3 @@
         stream = self._create_stream('/shodan/ports/%s' % ','.join([str(port) 
for port in ports]), timeout=timeout)
         for line in self._iter_stream(stream, raw):
             yield line
-
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/shodan-1.10.4/shodan/threatnet.py 
new/shodan-1.11.1/shodan/threatnet.py
--- old/shodan-1.10.4/shodan/threatnet.py       2018-08-31 03:40:23.000000000 
+0200
+++ new/shodan-1.11.1/shodan/threatnet.py       2019-02-11 01:18:29.000000000 
+0100
@@ -24,13 +24,13 @@
             try:
                 req = requests.get(self.base_url + name, params={'key': 
self.parent.api_key},
                                    stream=True, proxies=self.proxies)
-            except:
+            except Exception:
                 raise APIError('Unable to contact the Shodan Streaming API')
 
             if req.status_code != 200:
                 try:
                     raise APIError(req.json()['error'])
-                except:
+                except Exception:
                     pass
                 raise APIError('Invalid API key or you do not have access to 
the Streaming API')
             return req
@@ -65,4 +65,3 @@
         self.api_key = key
         self.base_url = 'https://api.shodan.io'
         self.stream = self.Stream(self)
-
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/shodan-1.10.4/shodan.egg-info/PKG-INFO 
new/shodan-1.11.1/shodan.egg-info/PKG-INFO
--- old/shodan-1.10.4/shodan.egg-info/PKG-INFO  2018-10-05 03:00:20.000000000 
+0200
+++ new/shodan-1.11.1/shodan.egg-info/PKG-INFO  2019-02-24 10:58:50.000000000 
+0100
@@ -1,6 +1,6 @@
-Metadata-Version: 1.1
+Metadata-Version: 2.1
 Name: shodan
-Version: 1.10.4
+Version: 1.11.1
 Summary: Python library and command-line utility for Shodan 
(https://developer.shodan.io)
 Home-page: http://github.com/achillean/shodan-python/tree/master
 Author: John Matherly
@@ -26,6 +26,7 @@
         - `Fast/ bulk IP lookups 
<https://help.shodan.io/developer-fundamentals/looking-up-ip-info>`_
         - Streaming API support for real-time consumption of Shodan firehose
         - `Network alerts (aka private firehose) 
<https://help.shodan.io/guides/how-to-monitor-network>`_
+        - `Manage Email Notifications 
<https://asciinema.org/a/7WvyDtNxn0YeNU70ozsxvXDmL>`_
         - Exploit search API fully implemented
         - Bulk data downloads
         - `Command-line interface <https://cli.shodan.io>`_
@@ -96,3 +97,4 @@
 Classifier: Programming Language :: Python :: 3.5
 Classifier: Programming Language :: Python :: 3.6
 Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Description-Content-Type: text/x-rst
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/shodan-1.10.4/shodan.egg-info/SOURCES.txt 
new/shodan-1.11.1/shodan.egg-info/SOURCES.txt
--- old/shodan-1.10.4/shodan.egg-info/SOURCES.txt       2018-10-05 
03:00:20.000000000 +0200
+++ new/shodan-1.11.1/shodan.egg-info/SOURCES.txt       2019-02-24 
10:58:50.000000000 +0100
@@ -18,7 +18,6 @@
 docs/examples/query-summary.rst
 shodan/__init__.py
 shodan/__main__.py
-shodan/alert.py
 shodan/client.py
 shodan/exception.py
 shodan/helpers.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/shodan-1.10.4/shodan.egg-info/requires.txt 
new/shodan-1.11.1/shodan.egg-info/requires.txt
--- old/shodan-1.10.4/shodan.egg-info/requires.txt      2018-10-05 
03:00:20.000000000 +0200
+++ new/shodan-1.11.1/shodan.egg-info/requires.txt      2019-02-24 
10:58:50.000000000 +0100
@@ -1,5 +1,5 @@
-XlsxWriter
 click
 click-plugins
 colorama
 requests>=2.2.1
+XlsxWriter
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/shodan-1.10.4/tests/test_shodan.py 
new/shodan-1.11.1/tests/test_shodan.py
--- old/shodan-1.10.4/tests/test_shodan.py      2018-09-22 04:14:03.000000000 
+0200
+++ new/shodan-1.11.1/tests/test_shodan.py      2019-02-11 00:53:38.000000000 
+0100
@@ -9,148 +9,148 @@
 
 class ShodanTests(unittest.TestCase):
 
-       api = None
-       FACETS = [
-               'port',
-               ('domain', 1)
-       ]
-       QUERIES = {
-               'simple': 'cisco-ios',
-               'minify': 'apache',
-               'advanced': 'apache port:443',
-               'empty': 'asdasdasdasdasdasdasdasdasdhjihjkjk',
-       }
-
-       def setUp(self):
-               self.api = shodan.Shodan(open('SHODAN-API-KEY').read().strip())
-
-       def test_search_simple(self):
-               results = self.api.search(self.QUERIES['simple'])
-
-               # Make sure the properties exist
-               self.assertIn('matches', results)
-               self.assertIn('total', results)
-
-               # Make sure no error occurred
-               self.assertNotIn('error', results)
-
-               # Make sure some values were returned
-               self.assertTrue(results['matches'])
-               self.assertTrue(results['total'])
-
-               # A regular search shouldn't have the optional info
-               self.assertNotIn('opts', results['matches'][0])
-
-       def test_search_empty(self):
-               results = self.api.search(self.QUERIES['empty'])
-               self.assertTrue(len(results['matches']) == 0)
-               self.assertEqual(results['total'], 0)
-
-       def test_search_facets(self):
-               results = self.api.search(self.QUERIES['simple'], 
facets=self.FACETS)
-
-               self.assertTrue(results['facets']['port'])
-               self.assertEqual(len(results['facets']['domain']), 1)
-
-       def test_count_simple(self):
-               results = self.api.count(self.QUERIES['simple'])
-
-               # Make sure the properties exist
-               self.assertIn('matches', results)
-               self.assertIn('total', results)
-
-               # Make sure no error occurred
-               self.assertNotIn('error', results)
-
-               # Make sure no values were returned
-               self.assertFalse(results['matches'])
-               self.assertTrue(results['total'])
-
-       def test_count_facets(self):
-               results = self.api.count(self.QUERIES['simple'], 
facets=self.FACETS)
-
-               self.assertTrue(results['facets']['port'])
-               self.assertEqual(len(results['facets']['domain']), 1)
-
-       def test_host_details(self):
-               host = self.api.host('147.228.101.7')
-
-               self.assertEqual('147.228.101.7', host['ip_str'])
-               self.assertFalse(isinstance(host['ip'], basestring))
-
-       def test_search_minify(self):
-               results = self.api.search(self.QUERIES['minify'], minify=False)
-               self.assertIn('opts', results['matches'][0])
-
-       def test_exploits_search(self):
-               results = self.api.exploits.search('apache')
-               self.assertIn('matches', results)
-               self.assertIn('total', results)
-               self.assertTrue(results['matches'])
-
-       def test_exploits_search_paging(self):
-               results = self.api.exploits.search('apache', page=1)
-               match1 = results['matches'][0]
-               results = self.api.exploits.search('apache', page=2)
-               match2 = results['matches'][0]
-
-               self.assertNotEqual(match1['_id'], match2['_id'])
-
-       def test_exploits_search_facets(self):
-               results = self.api.exploits.search('apache', facets=['source', 
('author', 1)])
-               self.assertIn('facets', results)
-               self.assertTrue(results['facets']['source'])
-               self.assertTrue(len(results['facets']['author']) == 1)
-
-       def test_exploits_count(self):
-               results = self.api.exploits.count('apache')
-               self.assertIn('matches', results)
-               self.assertIn('total', results)
-               self.assertTrue(len(results['matches']) == 0)
-
-       def test_exploits_count_facets(self):
-               results = self.api.exploits.count('apache', facets=['source', 
('author', 1)])
-               self.assertEqual(len(results['matches']), 0)
-               self.assertIn('facets', results)
-               self.assertTrue(results['facets']['source'])
-               self.assertTrue(len(results['facets']['author']) == 1)
-
-       # Test error responses
-       def test_invalid_key(self):
-               api = shodan.Shodan('garbage')
-               raised = False
-               try:
-                       api.search('something')
-               except shodan.APIError as e:
-                       raised = True
-
-               self.assertTrue(raised)
-
-       def test_invalid_host_ip(self):
-               raised = False
-               try:
-                       host = self.api.host('test')
-               except shodan.APIError as e:
-                       raised = True
-
-               self.assertTrue(raised)
-
-       def test_search_empty_query(self):
-               raised = False
-               try:
-                       self.api.search('')
-               except shodan.APIError as e:
-                       raised = True
-               self.assertTrue(raised)
-
-       def test_search_advanced_query(self):
-               # The free API plan can't use filters
-               raised = False
-               try:
-                       self.api.search(self.QUERIES['advanced'])
-               except shodan.APIError as e:
-                       raised = True
-               self.assertTrue(raised)
+    api = None
+    FACETS = [
+        'port',
+        ('domain', 1)
+    ]
+    QUERIES = {
+        'simple': 'cisco-ios',
+        'minify': 'apache',
+        'advanced': 'apache port:443',
+        'empty': 'asdasdasdasdasdasdasdasdasdhjihjkjk',
+    }
+
+    def setUp(self):
+        self.api = shodan.Shodan(open('SHODAN-API-KEY').read().strip())
+
+    def test_search_simple(self):
+        results = self.api.search(self.QUERIES['simple'])
+
+        # Make sure the properties exist
+        self.assertIn('matches', results)
+        self.assertIn('total', results)
+
+        # Make sure no error occurred
+        self.assertNotIn('error', results)
+
+        # Make sure some values were returned
+        self.assertTrue(results['matches'])
+        self.assertTrue(results['total'])
+
+        # A regular search shouldn't have the optional info
+        self.assertNotIn('opts', results['matches'][0])
+
+    def test_search_empty(self):
+        results = self.api.search(self.QUERIES['empty'])
+        self.assertTrue(len(results['matches']) == 0)
+        self.assertEqual(results['total'], 0)
+
+    def test_search_facets(self):
+        results = self.api.search(self.QUERIES['simple'], facets=self.FACETS)
+
+        self.assertTrue(results['facets']['port'])
+        self.assertEqual(len(results['facets']['domain']), 1)
+
+    def test_count_simple(self):
+        results = self.api.count(self.QUERIES['simple'])
+
+        # Make sure the properties exist
+        self.assertIn('matches', results)
+        self.assertIn('total', results)
+
+        # Make sure no error occurred
+        self.assertNotIn('error', results)
+
+        # Make sure no values were returned
+        self.assertFalse(results['matches'])
+        self.assertTrue(results['total'])
+
+    def test_count_facets(self):
+        results = self.api.count(self.QUERIES['simple'], facets=self.FACETS)
+
+        self.assertTrue(results['facets']['port'])
+        self.assertEqual(len(results['facets']['domain']), 1)
+
+    def test_host_details(self):
+        host = self.api.host('147.228.101.7')
+
+        self.assertEqual('147.228.101.7', host['ip_str'])
+        self.assertFalse(isinstance(host['ip'], basestring))
+
+    def test_search_minify(self):
+        results = self.api.search(self.QUERIES['minify'], minify=False)
+        self.assertIn('opts', results['matches'][0])
+
+    def test_exploits_search(self):
+        results = self.api.exploits.search('apache')
+        self.assertIn('matches', results)
+        self.assertIn('total', results)
+        self.assertTrue(results['matches'])
+
+    def test_exploits_search_paging(self):
+        results = self.api.exploits.search('apache', page=1)
+        match1 = results['matches'][0]
+        results = self.api.exploits.search('apache', page=2)
+        match2 = results['matches'][0]
+
+        self.assertNotEqual(match1['_id'], match2['_id'])
+
+    def test_exploits_search_facets(self):
+        results = self.api.exploits.search('apache', facets=['source', 
('author', 1)])
+        self.assertIn('facets', results)
+        self.assertTrue(results['facets']['source'])
+        self.assertTrue(len(results['facets']['author']) == 1)
+
+    def test_exploits_count(self):
+        results = self.api.exploits.count('apache')
+        self.assertIn('matches', results)
+        self.assertIn('total', results)
+        self.assertTrue(len(results['matches']) == 0)
+
+    def test_exploits_count_facets(self):
+        results = self.api.exploits.count('apache', facets=['source', 
('author', 1)])
+        self.assertEqual(len(results['matches']), 0)
+        self.assertIn('facets', results)
+        self.assertTrue(results['facets']['source'])
+        self.assertTrue(len(results['facets']['author']) == 1)
+
+    # Test error responses
+    def test_invalid_key(self):
+        api = shodan.Shodan('garbage')
+        raised = False
+        try:
+            api.search('something')
+        except shodan.APIError:
+            raised = True
+
+        self.assertTrue(raised)
+
+    def test_invalid_host_ip(self):
+        raised = False
+        try:
+            self.api.host('test')
+        except shodan.APIError:
+            raised = True
+
+        self.assertTrue(raised)
+
+    def test_search_empty_query(self):
+        raised = False
+        try:
+            self.api.search('')
+        except shodan.APIError:
+            raised = True
+        self.assertTrue(raised)
+
+    def test_search_advanced_query(self):
+        # The free API plan can't use filters
+        raised = False
+        try:
+            self.api.search(self.QUERIES['advanced'])
+        except shodan.APIError:
+            raised = True
+        self.assertTrue(raised)
 
 
 if __name__ == '__main__':


Reply via email to