├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── ipip.py ├── ipip ├── __init__.py ├── __main__.py ├── __version__.py ├── addons │ ├── __init__.py │ ├── ip138.py │ ├── ipipnet.py │ ├── ipstack.py │ └── qqwry.py ├── db │ ├── ipipfree.ipdb │ └── qqwry.ipdb └── settings.py ├── requirements.txt └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 HJK 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: test 2 | 3 | dev: 4 | pip install setuptools pytest black twine flake8 5 | 6 | ci: 7 | py.test --junitxml=report.xml 8 | 9 | test: 10 | python3 setup.py test 11 | pytest 12 | 13 | coverage: 14 | py.test --cov-config .coveragerc --verbose --cov-report term --cov-report xml --cov=ipip --junitxml=report.xml tests 15 | 16 | flake8: 17 | black . 18 | flake8 --ignore=E501,F401,W503 ipip 19 | 20 | clean: 21 | rm -fr build dist .egg ipip.egg-info 22 | rm -fr .pytest_cache coverage.xml report.xml htmlcov 23 | find . | grep __pycache__ | xargs rm -fr 24 | find . | grep .pyc | xargs rm -f 25 | pip uninstall ipip 26 | 27 | install: 28 | python3 setup.py install 29 | 30 | publish: 31 | python3 setup.py sdist bdist_wheel 32 | twine upload dist/* 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IPIP 2 | 3 | ## 主要功能 4 | 5 | 1. 查询本机IP 6 | 2. 单个IP多来源查询(联网) 7 | 3. 单个IP高精度查询(联网) 8 | 4. 单个IP本地查询 9 | 5. IP批量本地查询 10 | 7. 支持管道查询 11 | 12 | ## 安装 13 | 14 | ``` 15 | git clone https://github.com/0xHJK/ipip 16 | 17 | cd ipip 18 | 19 | make install 20 | ``` 21 | 22 | 注册 https://ipstack.com 23 | 24 | 修改`settings.py`中的`self.ipstack_key` 25 | 26 | ## 使用方式 27 | 28 | 查看帮助 29 | 30 | ```bash 31 | ipip --help 32 | ``` 33 | 34 | ```bash 35 | Usage: ipip [OPTIONS] [IPADDRS]... 36 | 37 | Options: 38 | -t, --test 查看本机IP 39 | -i, --infile TEXT 指定IP列表文件 40 | -o, --outfile TEXT 指定输出结果文件 41 | -v, --verbose 详细模式 42 | --debug 调试模式 43 | --help Show this message and exit. 44 | ``` 45 | 46 | 查询本机IP(输出mac地址、内网IP、外网IP) 47 | 48 | ```bash 49 | ipip -t 50 | ``` 51 | 52 | 查询指定IP 53 | 54 | ```bash 55 | ipip 1.1.1.1 56 | ``` 57 | 58 | 批量查询IP 59 | 60 | ```bash 61 | ipip -i ip.txt -o out.txt 62 | ``` 63 | 64 | 管道查询IP 65 | 66 | ```bash 67 | cat ip.txt | ipip 68 | ``` 69 | 70 | ```bash 71 | echo "8.8.8.8" | ipip 72 | ``` -------------------------------------------------------------------------------- /ipip.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding=utf-8 -*- 3 | 4 | import os, sys 5 | 6 | _srcdir = "%s/" % os.path.dirname(os.path.realpath(__file__)) 7 | _filepath = os.path.dirname(sys.argv[0]) 8 | sys.path.insert(1, os.path.join(_filepath, _srcdir)) 9 | 10 | if sys.version_info[0] == 3: 11 | import ipip 12 | 13 | if __name__ == "__main__": 14 | ipip.main() 15 | else: # Python 2 16 | print("Python3 Only.") 17 | -------------------------------------------------------------------------------- /ipip/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding=utf-8 -*- 3 | 4 | from .__version__ import __version__ 5 | from .__main__ import main 6 | -------------------------------------------------------------------------------- /ipip/__main__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding=utf-8 -*- 3 | 4 | import os 5 | import sys 6 | import re 7 | import uuid 8 | import socket 9 | import logging 10 | import requests 11 | import fileinput 12 | import ipdb 13 | import click 14 | from .settings import opts 15 | from .addons import ip138, ipipnet, ipstack, qqwry 16 | 17 | 18 | def sprint(*args): 19 | if not args: 20 | return 21 | s = click.style(args[0], fg="yellow") 22 | s += " ".join(args[1:]) 23 | click.echo(s) 24 | 25 | 26 | def sformat(*args): 27 | s = click.style(args[0], fg="yellow") 28 | s += " ".join(args[1:]) 29 | return s 30 | 31 | 32 | def myip() -> list: 33 | """ 输出本机MAC地址、内网地址和外网地址 """ 34 | ret = [] 35 | # MAC地址 36 | mac = ":".join(re.findall("..", "%012x" % uuid.getnode())).upper() 37 | sprint("[MAC]: ", mac) 38 | # 内网地址 39 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 40 | try: 41 | # doesn't even have to be reachable 42 | s.connect(("10.255.255.255", 1)) 43 | internal_ip = s.getsockname()[0] 44 | except Exception: 45 | internal_ip = "127.0.0.1" 46 | finally: 47 | s.close() 48 | ret.append(sformat("[INTERNAL]: ", internal_ip)) 49 | # 外网地址 TODO: 加代理 50 | ret.append(sformat("[IPIP.NET]: ", ipipnet.myip())) 51 | ret.append(sformat("[IP138]: ", ip138.myip())) 52 | 53 | return ret 54 | 55 | 56 | def ipquery(ipaddrs) -> list: 57 | """ 联网查询一个或多个IP """ 58 | ret = [] 59 | for ip in ipaddrs: 60 | ret.append(sformat("[QQWRY]: ", qqwry.ipinfo(ip))) 61 | ret.append(sformat("[IPSTACK]: ", ipstack.ipinfo(ip))) 62 | return ret 63 | 64 | 65 | def ipbatch(ipaddrs) -> list: 66 | """ 本地库查询多个IP """ 67 | ret = [] 68 | for ip in ipaddrs: 69 | ret.append(qqwry.ipinfo(ip)) 70 | return ret 71 | 72 | 73 | def output(result, outfile=None) -> None: 74 | """ 输出结果 """ 75 | if not outfile: 76 | for line in result: 77 | print(line) 78 | else: 79 | with open(outfile, "w") as f: 80 | f.write("\n".join(result)) 81 | 82 | 83 | @click.command() 84 | @click.argument("ipaddrs", nargs=-1) 85 | @click.option("-t", "--test", is_flag=True, help="查看本机IP") 86 | @click.option("-i", "--infile", help="指定IP列表文件") 87 | @click.option("-o", "--outfile", help="指定输出结果文件") 88 | @click.option("-v", "--verbose", is_flag=True, help="详细模式") 89 | @click.option("--debug", is_flag=True, help="调试模式") 90 | def main(ipaddrs, test, infile, outfile, verbose, debug): 91 | if verbose: 92 | opts.verbose = verbose 93 | if debug: 94 | opts.debug = debug 95 | 96 | level = logging.DEBUG if debug else logging.INFO 97 | logging.basicConfig( 98 | level=level, 99 | format="[%(asctime)s] %(levelname)-8s | %(name)s: %(msg)s ", 100 | datefmt="%H:%M:%S", 101 | ) 102 | 103 | 104 | if test: 105 | # 查询本机IP 106 | output(result=myip(), outfile=outfile) 107 | return 108 | 109 | if ipaddrs: 110 | # 查询指定IP 111 | output(result=ipquery(ipaddrs), outfile=outfile) 112 | return 113 | 114 | if infile: 115 | # 批量查询文件IP(本地) 116 | opts.infile = infile 117 | ipaddrs = open(infile, "r").readlines() 118 | output(result=ipbatch(ipaddrs), outfile=outfile) 119 | return 120 | 121 | # 管道查询(本地) 122 | ipaddrs = fileinput.input() 123 | output(result=ipbatch(ipaddrs), outfile=outfile) 124 | 125 | 126 | -------------------------------------------------------------------------------- /ipip/__version__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding=utf-8 -*- 3 | 4 | __title__ = "ipip" 5 | __description__ = "IP to location" 6 | __url__ = "https://github.com/0xHJK/ipip" 7 | __version__ = "0.0.1" 8 | __author__ = "HJK" 9 | __author_email__ = "HJKdev@gmail.com" 10 | __license__ = "MIT License" 11 | __copyright__ = "Copyright 2020 HJK" 12 | -------------------------------------------------------------------------------- /ipip/addons/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xHJK/ipip/0d6ac29a4ee5cdba5631707967e73d4d3bcf779d/ipip/addons/__init__.py -------------------------------------------------------------------------------- /ipip/addons/ip138.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding=utf-8 -*- 3 | 4 | """ 5 | 从IP138获得IP位置信息 6 | """ 7 | 8 | import requests 9 | from pyquery import PyQuery as pq 10 | from ..settings import opts 11 | 12 | API = "https://2021.ip138.com/" 13 | 14 | 15 | def myip() -> str: 16 | """ 查本机IP """ 17 | try: 18 | r = requests.get(API, headers=opts.fake_headers) 19 | d = pq(r.text) 20 | return d(d("p")[0]).text() 21 | except Exception as e: 22 | return "0.0.0.0" 23 | 24 | 25 | def ipinfo(ip) -> str: 26 | try: 27 | pass 28 | except Exception as e: 29 | return "%s query failed." % ip 30 | -------------------------------------------------------------------------------- /ipip/addons/ipipnet.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding=utf-8 -*- 3 | 4 | """ 5 | 从ipip.net获得ip位置信息 6 | """ 7 | 8 | import requests 9 | 10 | API = "http://myip.ipip.net" 11 | 12 | 13 | def myip() -> str: 14 | """ 查本机IP """ 15 | try: 16 | r = requests.get(API) 17 | return r.text.strip() 18 | except Exception as e: 19 | return "0.0.0.0" 20 | 21 | 22 | def ipinfo(ip) -> str: 23 | """ 查指定IP信息 """ 24 | pass 25 | -------------------------------------------------------------------------------- /ipip/addons/ipstack.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding=utf-8 -*- 3 | 4 | """ 5 | 从IPSTACK获得IP位置信息 6 | """ 7 | 8 | import requests 9 | from ..settings import opts 10 | 11 | ACCESS_KEY = opts.ipstack_key 12 | API = "http://api.ipstack.com/" 13 | 14 | 15 | def ipinfo(ip) -> str: 16 | """ 获得单个IP的信息 """ 17 | ip = ip.strip() 18 | try: 19 | r = requests.get("%s/%s?access_key=%s&language=zh" % (API, ip, ACCESS_KEY)) 20 | j = r.json() 21 | ret = "%s, %s, %s, %s, %s" % ( 22 | ip, 23 | j.get("country_name", "-"), 24 | j.get("region_name", "-"), 25 | j.get("city", "-"), 26 | j.get("connection", {}).get("isp", "-"), 27 | ) 28 | return ret 29 | except Exception as e: 30 | return "%s query failed." % ip 31 | -------------------------------------------------------------------------------- /ipip/addons/qqwry.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding=utf-8 -*- 3 | 4 | """ 5 | 从本地qqwry.ipdb分析IP归属地 6 | """ 7 | 8 | import ipdb 9 | from ..settings import opts 10 | 11 | DB = ipdb.City(opts.ipdbfile) 12 | 13 | 14 | def ipinfo(ip) -> str: 15 | """ 查找单个IP """ 16 | ip = ip.strip() 17 | try: 18 | return "%s, %s" % (ip, ", ".join(DB.find(ip.strip(), "CN"))) 19 | except Exception as e: 20 | return "%s query failed." % ip 21 | 22 | 23 | def ipbatch(ipaddrs) -> list: 24 | """ 查找多个IP """ 25 | return [ipinfo(ip) for ip in ipaddrs] 26 | -------------------------------------------------------------------------------- /ipip/db/ipipfree.ipdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xHJK/ipip/0d6ac29a4ee5cdba5631707967e73d4d3bcf779d/ipip/db/ipipfree.ipdb -------------------------------------------------------------------------------- /ipip/db/qqwry.ipdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xHJK/ipip/0d6ac29a4ee5cdba5631707967e73d4d3bcf779d/ipip/db/qqwry.ipdb -------------------------------------------------------------------------------- /ipip/settings.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding=utf-8 -*- 3 | 4 | """ 5 | 配置和全局变量 6 | """ 7 | 8 | import os 9 | 10 | 11 | class Singleton(type): 12 | _instances = {} 13 | 14 | def __call__(cls, *args, **kwargs): 15 | if cls not in cls._instances: 16 | cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) 17 | return cls._instances[cls] 18 | 19 | 20 | class Options(metaclass=Singleton): 21 | """ 22 | Global options 23 | """ 24 | 25 | def __init__(self): 26 | self.ipdbfile = os.path.join(os.path.dirname(__file__), "db", "qqwry.ipdb") 27 | self.fake_headers = { 28 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", 29 | "Accept-Charset": "UTF-8,*;q=0.5", 30 | "Accept-Encoding": "gzip,deflate,sdch", 31 | "Accept-Language": "en-US,en;q=0.8", 32 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:60.0) Gecko/20100101 Firefox/60.0", 33 | "referer": "https://www.google.com", 34 | } 35 | self.ipaddrs = [] 36 | self.outfile = "" 37 | self.infile = "" 38 | self.proxies = None 39 | self.debug = False 40 | self.verbose = False 41 | self.ipstack_key = "" 42 | 43 | 44 | opts = Options() 45 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | ipip-ipdb 2 | click 3 | netifaces 4 | requests 5 | pyquery 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding=utf-8 -*- 3 | 4 | 5 | import setuptools 6 | from os import path 7 | 8 | 9 | proj_dir = path.dirname(path.realpath(__file__)) 10 | about_file = path.join(proj_dir, "ipip", "__version__.py") 11 | 12 | about = {} 13 | exec(open(about_file, "r", encoding="utf-8").read(), about) 14 | 15 | long_description = open("README.md", "r", encoding="utf-8").read() 16 | 17 | requirements = open("requirements.txt", "r", encoding="utf-8").read().splitlines() 18 | 19 | 20 | setuptools.setup( 21 | name=about["__title__"], 22 | version=about["__version__"], 23 | description=about["__description__"], 24 | author=about["__author__"], 25 | author_email=about["__author_email__"], 26 | url=about["__url__"], 27 | license=about["__license__"], 28 | long_description=long_description, 29 | long_description_content_type="text/markdown", 30 | packages=setuptools.find_packages(), 31 | test_suite="tests", 32 | package_data={"ipip": ["db/qqwry.ipdb", "db/ipipfree.ipdb"]}, 33 | entry_points={"console_scripts": ["ipip = ipip.__main__:main"]}, 34 | install_requires=requirements, 35 | classifiers=[ 36 | "Development Status :: 4 - Beta", 37 | "Environment :: Console", 38 | "Intended Audience :: Developers", 39 | "Intended Audience :: End Users/Desktop", 40 | "License :: OSI Approved :: MIT License", 41 | "Operating System :: OS Independent", 42 | "Programming Language :: Python", 43 | "Programming Language :: Python :: 3", 44 | "Programming Language :: Python :: 3 :: Only", 45 | "Programming Language :: Python :: 3.7", 46 | "Programming Language :: Python :: 3.8", 47 | "Programming Language :: Python :: 3.9", 48 | "Topic :: Internet", 49 | "Topic :: Internet :: WWW/HTTP", 50 | "Topic :: Security", 51 | "Topic :: System", 52 | "Topic :: Utilities", 53 | ], 54 | ) 55 | --------------------------------------------------------------------------------