├── LICENSE ├── Makefile ├── README.md ├── requirements.txt ├── run.py ├── search.py ├── setup.py └── skype_contact_parser ├── __init__.py ├── __main__.py ├── _version.py ├── cli.py ├── core.py ├── executor.py ├── report.py └── server.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 soxoj 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 | PROJECT_NAME=skype_contact_parser 2 | LINT_FILES=skype_contact_parser 3 | 4 | test: 5 | coverage run --source=./skype_contact_parser -m pytest tests 6 | coverage report -m 7 | coverage html 8 | 9 | rerun-tests: 10 | pytest --lf -vv 11 | 12 | lint: 13 | @echo 'syntax errors or undefined names' 14 | flake8 --count --select=E9,F63,F7,F82 --show-source --statistics ${LINT_FILES} 15 | @echo 'warning' 16 | flake8 --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics --ignore=E731,W503,E501 ${LINT_FILES} 17 | 18 | @echo 'mypy' 19 | mypy ${LINT_FILES} 20 | 21 | format: 22 | @echo 'black' 23 | black --skip-string-normalization ${LINT_FILES} 24 | 25 | clean: 26 | rm -rf reports htmcov dist build *.egg-info 27 | 28 | install: 29 | pip3 install . 30 | 31 | rename: 32 | @python3 update.py -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # skype-contact-parser 2 | 3 | Get any Skype contact by phone number! And do Skype search by any term, literally. 4 | 5 | How it works? This script exploits Skype search API functionality. 6 | 7 | WARNING: Skype antifraud system bans accounts periodically. You should check your mailbox 8 | and reset passwords through Microsoft email link when you account stop working. Also, you can 9 | create several accounts and use 3rd option of authorization (see below). 10 | 11 | ## Usage 12 | 13 | **The tool based on the template [osint-cli-tool-skeleton](https://github.com/soxoj/osint-cli-tool-skeleton)**. Read its README to explore all the available functionality. 14 | 15 | You need to authorize first to get Skype token. You can: 16 | 17 | 1. Use env variables 18 | ```sh 19 | export SKYPE_LOGIN=login 20 | export SKYPE_PASS=pass 21 | ``` 22 | 23 | 2. Enter credentials after the launch: 24 | ``` 25 | ./run.py +79218657070 26 | Login: 27 | Password: 28 | ``` 29 | 30 | 3. Use special file `credentials.txt` with `login:password` pairs, the tool will automatically select the working one. 31 | ``` 32 | login1:pass1 33 | login2:pass2 34 | ``` 35 | 36 | Script will get the token and save it to the `token.txt` file. 37 | 38 | ## Development 39 | 40 | Set `SKPY_DEBUG_HTTP=1` in your environment to output all HTTP requests between the library and Skype’s APIs. 41 | 42 | Read the unoffical `skpy` library documentation [here](https://skpy.t.allofti.me/). 43 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | asyncio==3.4.3 2 | skpy==0.10.4 3 | tqdm==4.62.3 4 | colorama==0.4.4 5 | termcolor==1.1.0 6 | -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | from skype_contact_parser import cli 3 | 4 | if __name__ == "__main__": 5 | cli.run() 6 | -------------------------------------------------------------------------------- /search.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import json 3 | import os 4 | import sys 5 | import random 6 | 7 | import requests 8 | import skpy 9 | 10 | 11 | class SkypeSearch: 12 | def __init__(self, token): 13 | self.skype_token = token 14 | self.session = requests.session() 15 | self.session.headers.update({ 16 | "X-Skypetoken": self.skype_token, 17 | "X-ECS-ETag": "Fzn/9gnnfHwbTYyoLcWa1FhbSVkgRg28SzNJqolgQHg=", 18 | "X-Skype-Client": "1419/8.26.0.70", 19 | "X-SkypeGraphServiceSettings": '{"experiment":"MinimumFriendsForAnnotationsEnabled","geoProximity":"disabled","minimumFriendsForAnnotationsEnabled":"true","minimumFriendsForAnnotations":2,"demotionScoreEnabled":"true"}', 20 | 'accept-encoding': 'gzip', 21 | 'user-agent': 'okhttp/3.10.0', 22 | }) 23 | 24 | def search(self, search_query): 25 | params = { 26 | "searchString": search_query, 27 | "requestId": random.randint(1e13, 9e13) 28 | } 29 | 30 | search_response = self.session.get("https://skypegraph.skype.com/v2.0/search", params = params) 31 | if search_response.status_code == 200: 32 | json_response = search_response.json() 33 | relevant_data = json_response["results"] 34 | output = [] 35 | 36 | for info in relevant_data: 37 | output.append(info["nodeProfileData"]) 38 | 39 | return output 40 | 41 | return [] 42 | 43 | 44 | def extract(): 45 | if len(sys.argv) == 1: 46 | term = input('Input phone (or something other) for Skype contact search: ') 47 | else: 48 | term = sys.argv[1] 49 | 50 | sk = None 51 | token_file = './tokens.txt' 52 | 53 | if os.path.exists(token_file): 54 | sk = skpy.Skype(connect=False) 55 | sk.conn.tokenFile = token_file 56 | sk.conn.readToken() 57 | 58 | if not sk or not sk.conn.tokens: 59 | login = os.getenv('SKYPE_LOGIN') or input('Login: ') 60 | passw = os.getenv('SKYPE_PASS') or input('Password: ') 61 | sk = skpy.Skype(login, passw, token_file) 62 | 63 | search = SkypeSearch(token=sk.conn.tokens['skype']) 64 | res = search.search(search_query=term) 65 | 66 | print(json.dumps(res, indent=4)) 67 | 68 | 69 | if __name__ == '__main__': 70 | extract() -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | exec(open('skype_contact_parser/_version.py').read()) 4 | 5 | with open('requirements.txt') as rf: 6 | requires = rf.read().splitlines() 7 | 8 | with open('README.md') as fh: 9 | long_description = fh.read() 10 | 11 | setup( 12 | name="skype_contact_parser", 13 | version=__version__, 14 | description="A skeleton for OSINT CLI tool", 15 | long_description=long_description, 16 | long_description_content_type="text/markdown", 17 | url="https://github.com/soxoj/osint-cli-tool-skeleton", 18 | author="Soxoj", 19 | author_email="soxoj@protonmail.com", 20 | entry_points={'console_scripts': ['skype_contact_parser = skype_contact_parser.__init__:run']}, 21 | license="MIT", 22 | packages=find_packages(), 23 | include_package_data=True, 24 | install_requires=requires, 25 | ) 26 | -------------------------------------------------------------------------------- /skype_contact_parser/__init__.py: -------------------------------------------------------------------------------- 1 | from ._version import __version__ 2 | from .cli import * -------------------------------------------------------------------------------- /skype_contact_parser/__main__.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | """ 3 | Entrypoint 4 | """ 5 | from cli import run 6 | 7 | if __name__ == "__main__": 8 | run() 9 | -------------------------------------------------------------------------------- /skype_contact_parser/_version.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.0.1' 2 | -------------------------------------------------------------------------------- /skype_contact_parser/cli.py: -------------------------------------------------------------------------------- 1 | """ 2 | Commandline interface 3 | """ 4 | import asyncio 5 | import logging 6 | import os 7 | import platform 8 | import sys 9 | 10 | from argparse import ArgumentParser, RawDescriptionHelpFormatter 11 | 12 | from .core import * 13 | from .report import * 14 | from .server import * 15 | 16 | 17 | def setup_arguments_parser(): 18 | from aiohttp import __version__ as aiohttp_version 19 | from ._version import __version__ 20 | 21 | version_string = '\n'.join( 22 | [ 23 | f'%(prog)s {__version__}', 24 | f'Python: {platform.python_version()}', 25 | f'Aiohttp: {aiohttp_version}', 26 | ] 27 | ) 28 | 29 | parser = ArgumentParser( 30 | formatter_class=RawDescriptionHelpFormatter, 31 | description=f"Skype contact parser v{__version__}\n" 32 | ) 33 | target_group = parser.add_argument_group( 34 | 'INPUT', 'Options for input data' 35 | ) 36 | target_group.add_argument( 37 | "target", 38 | nargs='*', 39 | metavar="TARGET", 40 | help="One or more target to get info by.", 41 | ) 42 | target_group.add_argument( 43 | "--target-list", 44 | action="store", 45 | dest="target_list_filename", 46 | default='', 47 | help="Path to text file with list of targets.", 48 | ) 49 | target_group.add_argument( 50 | "--targets-from-stdin", 51 | action="store_true", 52 | dest="target_list_stdin", 53 | default=False, 54 | help="Read all the lines from standard input.", 55 | ) 56 | out_group = parser.add_argument_group( 57 | 'OUTPUT', 'Options for output reports' 58 | ) 59 | out_group.add_argument( 60 | "--csv-report", 61 | "-oC", 62 | action="store", 63 | dest="csv_filename", 64 | default='', 65 | help="Path to file for saving CSV report.", 66 | ) 67 | out_group.add_argument( 68 | "--json-report", 69 | "-oJ", 70 | action="store", 71 | dest="json_filename", 72 | default='', 73 | help="Path to file for saving JSON report.", 74 | ) 75 | parser.add_argument( 76 | "--version", 77 | action="version", 78 | version=version_string, 79 | help="Display version information and dependencies.", 80 | ) 81 | parser.add_argument( 82 | "--timeout", 83 | action="store", 84 | metavar='TIMEOUT', 85 | dest="timeout", 86 | default=100, 87 | help="Time in seconds to wait for execution.", 88 | ) 89 | parser.add_argument( 90 | "--cookie-jar-file", 91 | metavar="COOKIE_FILE", 92 | dest="cookie_file", 93 | default='', 94 | help="File with cookies.", 95 | ) 96 | parser.add_argument( 97 | "--proxy", 98 | "-p", 99 | metavar='PROXY_URL', 100 | action="store", 101 | dest="proxy", 102 | default='', 103 | help="Make requests over a proxy. e.g. socks5://127.0.0.1:1080", 104 | ) 105 | parser.add_argument( 106 | "--verbose", 107 | "-v", 108 | action="store_true", 109 | dest="verbose", 110 | default=False, 111 | help="Display extra information and metrics.", 112 | ) 113 | parser.add_argument( 114 | "--silent", 115 | "-s", 116 | action="store_true", 117 | dest="silent", 118 | default=False, 119 | help="Suppress console output.", 120 | ) 121 | parser.add_argument( 122 | "--info", 123 | "-vv", 124 | action="store_true", 125 | dest="info", 126 | default=False, 127 | help="Display extra/service information and metrics.", 128 | ) 129 | parser.add_argument( 130 | "--debug", 131 | "-vvv", 132 | "-d", 133 | action="store_true", 134 | dest="debug", 135 | default=False, 136 | help="Display extra/service/debug information and metrics, save responses in debug.log.", 137 | ) 138 | parser.add_argument( 139 | "--server", 140 | metavar='SERVER_ADDR', 141 | action="store", 142 | dest="server", 143 | default='', 144 | help="Start a server on a specific IP:PORT. e.g. 0.0.0.0:8080", 145 | ) 146 | parser.add_argument( 147 | "--no-color", 148 | action="store_true", 149 | dest="no_color", 150 | default=False, 151 | help="Don't color terminal output", 152 | ) 153 | parser.add_argument( 154 | "--no-progressbar", 155 | action="store_true", 156 | dest="no_progressbar", 157 | default=False, 158 | help="Don't show progressbar.", 159 | ) 160 | 161 | return parser 162 | 163 | 164 | async def main(): 165 | # Logging 166 | log_level = logging.ERROR 167 | logging.basicConfig( 168 | format='[%(filename)s:%(lineno)d] %(levelname)-3s %(asctime)s %(message)s', 169 | datefmt='%H:%M:%S', 170 | level=log_level, 171 | ) 172 | logger = logging.getLogger('osint-cli-tool-skeleton') 173 | logger.setLevel(log_level) 174 | 175 | arg_parser = setup_arguments_parser() 176 | args = arg_parser.parse_args() 177 | 178 | if args.debug: 179 | log_level = logging.DEBUG 180 | elif args.info: 181 | log_level = logging.INFO 182 | elif args.verbose: 183 | log_level = logging.WARNING 184 | 185 | logger.setLevel(log_level) 186 | 187 | # server 188 | if args.server: 189 | await CheckServer( 190 | proxy=args.proxy, 191 | addr=args.server, 192 | loop=asyncio.get_event_loop(), 193 | ).start(debug=args.debug) 194 | 195 | input_data = [] 196 | 197 | # read from file 198 | if args.target_list_filename: 199 | if not os.path.exists(args.target_list_filename): 200 | print(f'There is no file {args.target_list_filename}') 201 | else: 202 | with open(args.target_list_filename) as f: 203 | input_data = [InputData(t) for t in f.read().splitlines()] 204 | 205 | # read from stdin 206 | # e.g. cat list.txt | ./run.py --targets-from-stdin 207 | elif args.target_list_stdin: 208 | for line in sys.stdin: 209 | input_data.append(InputData(line.strip())) 210 | 211 | # read from arguments 212 | elif args.target: 213 | input_data = [InputData(t) for t in args.target] 214 | 215 | if not input_data: 216 | print('There are no targets to check!') 217 | sys.exit(1) 218 | 219 | processor = Processor() 220 | output_data = await processor.process(input_data) 221 | 222 | if not args.silent: 223 | r = PlainOutput(output_data, colored=not args.no_color) 224 | print(r.put()) 225 | 226 | if args.csv_filename: 227 | r = CSVOutput(output_data, filename=args.csv_filename) 228 | print(r.put()) 229 | 230 | # save JSON report 231 | if args.json_filename: 232 | r = JSONOutput(output_data, filename=args.json_filename) 233 | print(r.put()) 234 | 235 | await processor.close() 236 | 237 | 238 | def run(): 239 | loop = asyncio.get_event_loop() 240 | try: 241 | loop.run_until_complete(main()) 242 | except KeyboardInterrupt: 243 | pass 244 | loop.close() 245 | 246 | if __name__ == "__main__": 247 | run() -------------------------------------------------------------------------------- /skype_contact_parser/core.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import os 3 | import random 4 | from json import JSONEncoder 5 | from typing import List, Any 6 | 7 | from aiohttp import TCPConnector, ClientSession 8 | from bs4 import BeautifulSoup as bs 9 | import skpy 10 | 11 | from .executor import AsyncioProgressbarQueueExecutor, AsyncioSimpleExecutor 12 | 13 | 14 | CRED_FILE = 'credentials.txt' 15 | 16 | 17 | class InputData: 18 | def __init__(self, value: str): 19 | self.value = value 20 | 21 | def __str__(self): 22 | return self.value 23 | 24 | def __repr__(self): 25 | return self.value 26 | 27 | 28 | class OutputData: 29 | def __init__(self, values, error): 30 | self.error = error 31 | self.skype_id = values.get('skypeId') 32 | self.avatar_url = f'https://avatar.skype.com/v1/avatars/{self.skype_id}/public' 33 | self.handle = values.get('skypeHandle') 34 | self.name = values.get('name') 35 | self.country_code = values.get('countryCode') 36 | self.city = values.get('city') 37 | self.state = values.get('state') 38 | self.contact_type = values.get('contactType') 39 | 40 | @property 41 | def fields(self): 42 | fields = list(self.__dict__.keys()) 43 | fields.remove('error') 44 | 45 | return fields 46 | 47 | def __str__(self): 48 | error = '' 49 | if self.error: 50 | error = f' (error: {str(self.error)}' 51 | 52 | result = '' 53 | result += f'Skype ID: {str(self.skype_id)}\n' 54 | if self.skype_id != self.handle: 55 | result += f'Username: {str(self.handle)}\n' 56 | 57 | for field in ('name', 'country_code', 'city', 'state', 'avatar_url'): 58 | field_pretty_name = field.title().replace('_', ' ') 59 | value = self.__dict__.get(field) 60 | if value: 61 | result += f'{field_pretty_name}: {str(value)}\n' 62 | 63 | result += f'{error}' 64 | 65 | return result 66 | 67 | 68 | class OutputDataList: 69 | def __init__(self, input_data: InputData, results: List[OutputData]): 70 | self.input_data = input_data 71 | self.results = results 72 | 73 | def __repr__(self): 74 | return f'Target {self.input_data}:\n' + '--------\n'.join(map(str, self.results)) 75 | 76 | 77 | class OutputDataListEncoder(JSONEncoder): 78 | def default(self, o): 79 | if isinstance(o, OutputDataList): 80 | return {'input': o.input_data, 'output': o.results} 81 | elif isinstance(o, OutputData): 82 | return {k:o.__dict__[k] for k in o.fields} 83 | else: 84 | return o.__dict__ 85 | 86 | 87 | class Processor: 88 | def __init__(self, *args, **kwargs): 89 | connector = TCPConnector(ssl=False) 90 | self.session = ClientSession( 91 | connector=connector, trust_env=True 92 | ) 93 | 94 | sk = None 95 | token_file = './tokens.txt' 96 | 97 | try: 98 | if os.path.exists(token_file): 99 | sk = skpy.Skype(connect=False) 100 | sk.conn.tokenFile = token_file 101 | sk.conn.readToken() 102 | except skpy.core.SkypeAuthException as e: 103 | print(e) 104 | 105 | credentials = [] 106 | # try to use credentials from credentials.txt 107 | if (not sk or not sk.conn.tokens) and os.path.exists(CRED_FILE): 108 | lines = open(CRED_FILE).read().splitlines() 109 | for line in lines: 110 | credentials.append(line.strip().split(':')) 111 | 112 | for c in credentials: 113 | print(f'Trying to use {c[0]} from {CRED_FILE}') 114 | try: 115 | sk = skpy.Skype(c[0], c[1], token_file) 116 | except skpy.core.SkypeAuthException as e: 117 | print('Auth failed, go to next credentials pair') 118 | continue 119 | 120 | if not sk or not sk.conn.tokens: 121 | print('Fail, go to next credentials pair') 122 | else: 123 | print('Success') 124 | break 125 | 126 | if not sk or not sk.conn.tokens: 127 | print(f'Trying to use credentials from ENV or CLI input') 128 | login = os.getenv('SKYPE_LOGIN') or input('Login: ') 129 | passw = os.getenv('SKYPE_PASS') or input('Password: ') 130 | sk = skpy.Skype(login, passw, token_file) 131 | 132 | self.skype_token = sk.conn.tokens['skype'] 133 | 134 | if kwargs.get('no_progressbar'): 135 | self.executor = AsyncioSimpleExecutor() 136 | else: 137 | self.executor = AsyncioProgressbarQueueExecutor() 138 | 139 | async def close(self): 140 | await self.session.close() 141 | 142 | async def request(self, input_data: InputData) -> OutputDataList: 143 | contacts = [] 144 | error = None 145 | 146 | headers = { 147 | "X-Skypetoken": self.skype_token, 148 | "X-ECS-ETag": "Fzn/9gnnfHwbTYyoLcWa1FhbSVkgRg28SzNJqolgQHg=", 149 | "X-Skype-Client": "1419/8.26.0.70", 150 | "X-SkypeGraphServiceSettings": '{"experiment":"MinimumFriendsForAnnotationsEnabled","geoProximity":"disabled","minimumFriendsForAnnotationsEnabled":"true","minimumFriendsForAnnotations":2,"demotionScoreEnabled":"true"}', 151 | 'accept-encoding': 'gzip', 152 | 'user-agent': 'okhttp/3.10.0', 153 | } 154 | 155 | try: 156 | term = input_data.value 157 | params = { 158 | "searchString": term, 159 | "requestId": random.randint(1e13, 9e13) 160 | } 161 | 162 | search_response = await self.session.get("https://skypegraph.skype.com/v2.0/search", headers=headers, params=params) 163 | if search_response.status == 200: 164 | json_response = await search_response.json() 165 | relevant_data = json_response["results"] 166 | 167 | for info in relevant_data: 168 | contacts.append(OutputData(info["nodeProfileData"], None)) 169 | 170 | except Exception as e: 171 | error = e 172 | 173 | results = OutputDataList(input_data, contacts) 174 | 175 | return results 176 | 177 | 178 | async def process(self, input_data: List[InputData]) -> List[OutputDataList]: 179 | tasks = [ 180 | ( 181 | self.request, # func 182 | [i], # args 183 | {} # kwargs 184 | ) 185 | for i in input_data 186 | ] 187 | 188 | results = await self.executor.run(tasks) 189 | 190 | return results 191 | -------------------------------------------------------------------------------- /skype_contact_parser/executor.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import logging 3 | import time 4 | import tqdm 5 | import sys 6 | from typing import Iterable, Any, List, Tuple, Callable, Dict 7 | 8 | 9 | QueryDraft = Tuple[Callable, List, List] 10 | 11 | 12 | def create_task_func(): 13 | if sys.version_info.minor > 6: 14 | create_asyncio_task = asyncio.create_task 15 | else: 16 | loop = asyncio.get_event_loop() 17 | create_asyncio_task = loop.create_task 18 | return create_asyncio_task 19 | 20 | 21 | class AsyncExecutor: 22 | def __init__(self, *args, **kwargs): 23 | self.logger = kwargs.get('logger', logging.getLogger('osint-tool')) 24 | 25 | async def run(self, tasks: Iterable[QueryDraft]): 26 | start_time = time.time() 27 | results = await self._run(tasks) 28 | self.execution_time = time.time() - start_time 29 | self.logger.debug(f'Spent time: {self.execution_time}') 30 | return results 31 | 32 | async def _run(self, tasks: Iterable[QueryDraft]): 33 | await asyncio.sleep(0) 34 | 35 | 36 | class AsyncioSimpleExecutor(AsyncExecutor): 37 | def __init__(self, *args, **kwargs): 38 | super().__init__(*args, **kwargs) 39 | 40 | async def _run(self, tasks: Iterable[QueryDraft]): 41 | futures = [f(*args, **kwargs) for f, args, kwargs in tasks] 42 | return await asyncio.gather(*futures) 43 | 44 | 45 | class AsyncioProgressbarExecutor(AsyncExecutor): 46 | def __init__(self, *args, **kwargs): 47 | super().__init__(*args, **kwargs) 48 | 49 | async def _run(self, tasks: Iterable[QueryDraft]): 50 | futures = [f(*args, **kwargs) for f, args, kwargs in tasks] 51 | results = [] 52 | for f in tqdm.asyncio.tqdm.as_completed(futures): 53 | results.append(await f) 54 | return results 55 | 56 | 57 | class AsyncioProgressbarSemaphoreExecutor(AsyncExecutor): 58 | def __init__(self, *args, **kwargs): 59 | super().__init__(*args, **kwargs) 60 | self.semaphore = asyncio.Semaphore(kwargs.get('in_parallel', 1)) 61 | 62 | async def _run(self, tasks: Iterable[QueryDraft]): 63 | async def _wrap_query(q: QueryDraft): 64 | async with self.semaphore: 65 | f, args, kwargs = q 66 | return await f(*args, **kwargs) 67 | 68 | async def semaphore_gather(tasks: Iterable[QueryDraft]): 69 | coros = [_wrap_query(q) for q in tasks] 70 | results = [] 71 | for f in tqdm.asyncio.tqdm.as_completed(coros): 72 | results.append(await f) 73 | return results 74 | 75 | return await semaphore_gather(tasks) 76 | 77 | 78 | class AsyncioProgressbarQueueExecutor(AsyncExecutor): 79 | def __init__(self, *args, **kwargs): 80 | super().__init__(*args, **kwargs) 81 | self.workers_count = kwargs.get('in_parallel', 10) 82 | self.progress_func = kwargs.get('progress_func', tqdm.tqdm) 83 | self.queue = asyncio.Queue(self.workers_count) 84 | self.timeout = kwargs.get('timeout') 85 | 86 | async def worker(self): 87 | while True: 88 | try: 89 | f, args, kwargs = self.queue.get_nowait() 90 | except asyncio.QueueEmpty: 91 | return 92 | 93 | query_future = f(*args, **kwargs) 94 | query_task = create_task_func()(query_future) 95 | try: 96 | result = await asyncio.wait_for(query_task, timeout=self.timeout) 97 | except asyncio.TimeoutError: 98 | result = kwargs.get('default') 99 | 100 | self.results.append(result) 101 | self.progress.update(1) 102 | self.queue.task_done() 103 | 104 | async def _run(self, queries: Iterable[QueryDraft]): 105 | self.results: List[Any] = [] 106 | 107 | queries_list = list(queries) 108 | 109 | min_workers = min(len(queries_list), self.workers_count) 110 | 111 | workers = [create_task_func()(self.worker()) for _ in range(min_workers)] 112 | 113 | self.progress = self.progress_func(total=len(queries_list)) 114 | for t in queries_list: 115 | await self.queue.put(t) 116 | await self.queue.join() 117 | for w in workers: 118 | w.cancel() 119 | self.progress.close() 120 | return self.results 121 | -------------------------------------------------------------------------------- /skype_contact_parser/report.py: -------------------------------------------------------------------------------- 1 | from colorama import init 2 | import csv 3 | import json 4 | import termcolor 5 | 6 | from .core import OutputData, OutputDataList, OutputDataListEncoder 7 | 8 | 9 | # use Colorama to make Termcolor work on Windows too 10 | init() 11 | 12 | 13 | class Output: 14 | def __init__(self, data: OutputDataList, *args, **kwargs): 15 | self.data = data 16 | 17 | def put(self): 18 | pass 19 | 20 | 21 | class PlainOutput(Output): 22 | def __init__(self, *args, **kwargs): 23 | self.is_colored = kwargs.get('colored', True) 24 | super().__init__(*args, **kwargs) 25 | 26 | def colored(self, val, color): 27 | if not self.is_colored: 28 | return val 29 | 30 | return termcolor.colored(val, color) 31 | 32 | def put(self): 33 | text = '' 34 | total = 0 35 | olist = self.data 36 | 37 | for o in olist: 38 | i = o.input_data 39 | 40 | text += f'Target: {self.colored(str(i), "green")}\n' 41 | text += f'Results found: {len(o.results)}\n' 42 | 43 | for n, r in enumerate(o.results): 44 | text += f'{n+1}) ' 45 | total += 1 46 | 47 | for k in r.fields: 48 | key = k.title().replace('_', ' ') 49 | val = r.__dict__.get(k) 50 | if val is None: 51 | val = '' 52 | 53 | text += f'{self.colored(key, "yellow")}: {val}\n' 54 | 55 | text += '\n' 56 | 57 | text += '-'*30 + '\n' 58 | 59 | text += f'Total found: {total}\n' 60 | 61 | return text 62 | 63 | 64 | class CSVOutput(Output): 65 | def __init__(self, *args, **kwargs): 66 | self.filename = kwargs.get('filename', True) 67 | super().__init__(*args, **kwargs) 68 | 69 | def put(self): 70 | if not len(self.data) and not len(self.data[0].results): 71 | return '' 72 | 73 | fields = self.data[0].results[0].fields 74 | fieldnames = ['Target'] + [k.title().replace('_', ' ') for k in fields] 75 | 76 | with open(self.filename, 'w') as csvfile: 77 | writer = csv.DictWriter(csvfile, fieldnames=fieldnames, quoting=csv.QUOTE_ALL) 78 | writer.writeheader() 79 | 80 | for o in self.data: 81 | i = o.input_data 82 | row = {'Target': i} 83 | 84 | for r in o.results: 85 | for k in fields: 86 | key = k.title().replace('_', ' ') 87 | val = r.__dict__.get(k) 88 | row[key] = val 89 | 90 | writer.writerow(row) 91 | 92 | return f'Results were saved to file {self.filename}' 93 | 94 | 95 | class JSONOutput(Output): 96 | def __init__(self, *args, **kwargs): 97 | self.filename = kwargs.get('filename', 'report.csv') 98 | super().__init__(*args, **kwargs) 99 | 100 | def put(self): 101 | data = [d for d in self.data if d] 102 | 103 | with open(self.filename, 'w') as jsonfile: 104 | json.dump(data, jsonfile, cls=OutputDataListEncoder) 105 | 106 | return f'Results were saved to file {self.filename}' 107 | -------------------------------------------------------------------------------- /skype_contact_parser/server.py: -------------------------------------------------------------------------------- 1 | import aiohttp 2 | import asyncio 3 | import json 4 | 5 | from aiohttp import web 6 | from aiohttp.web import HTTPNotFound 7 | 8 | from .core import Processor, InputData, OutputDataListEncoder 9 | 10 | 11 | class CheckServer: 12 | """ 13 | Simple HTTP server 14 | """ 15 | def __init__(self, addr, *args, **kwargs): 16 | self.addr = addr 17 | self.proxy = kwargs.get('proxy') 18 | self.loop = kwargs.get('loop') 19 | 20 | async def status(self, request): 21 | """ 22 | Default page answer 23 | """ 24 | return web.Response() 25 | 26 | async def check(self, request): 27 | """ 28 | Check targets list 29 | """ 30 | result = {} 31 | 32 | try: 33 | request_data = await request.json() 34 | 35 | targets = request_data.get('targets', []) 36 | 37 | input_data = [] 38 | # convert input to output 39 | processor = Processor( 40 | no_progressbar=True, 41 | proxy=self.proxy, 42 | ) 43 | 44 | for t in targets: 45 | input_data.append(InputData(t)) 46 | 47 | if not input_data: 48 | await processor.close() 49 | return web.json_response(result) 50 | 51 | output_data = await processor.process(input_data) 52 | clean_data = [d for d in output_data if d] 53 | json_data = json.dumps(clean_data, cls=OutputDataListEncoder) 54 | result = json.loads(json_data) 55 | 56 | await processor.close() 57 | 58 | except Exception as e: 59 | print(e) 60 | 61 | return web.json_response(result) 62 | 63 | async def start(self, debug=False): 64 | """ 65 | Starts an HTTP server 66 | """ 67 | app = web.Application(loop=asyncio.get_event_loop()) 68 | 69 | routes = [ 70 | web.get('/', self.status), 71 | web.post('/check', self.check), 72 | ] 73 | 74 | app.add_routes(routes) 75 | 76 | host, port = self.addr.split(':') 77 | 78 | runner = aiohttp.web.AppRunner(app) 79 | await runner.setup() 80 | site = aiohttp.web.TCPSite(runner, host, port) 81 | await site.start() 82 | 83 | print('Server started') 84 | await asyncio.Event().wait() 85 | --------------------------------------------------------------------------------