Projects STRLCPY Osintgram Commits fb1c82f8
🤬
Revision indexing in progress... (symbol navigation in revisions will be accurate after indexed)
  • ■ ■ ■ ■ ■ ■
    .dockerignore
     1 +.github
     2 +.img
     3 +doc
     4 +output
     5 +.dockerignore
     6 +.gitignore
     7 +Docker-compose.yml
     8 +Dockerfile
     9 +LICENSE
     10 +Makefile
     11 +README.md
  • ■ ■ ■ ■ ■ ■
    .gitignore
    skipped 2 lines
    3 3  output/
    4 4  *.pyc
    5 5  *.json
     6 +venv/
     7 +credentials.ini
  • .img/banner.png
  • .img/carbon.png
  • .img/carbon.svg
  • ■ ■ ■ ■ ■ ■
    Dockerfile
     1 +FROM python:3.9.2-alpine3.13 as build
     2 +WORKDIR /wheels
     3 +RUN apk add --no-cache \
     4 + ncurses-dev \
     5 + build-base
     6 +COPY docker_reqs.txt /opt/osintgram/requirements.txt
     7 +RUN pip3 wheel -r /opt/osintgram/requirements.txt
     8 + 
     9 + 
     10 +FROM python:3.9.2-alpine3.13
     11 +WORKDIR /home/osintgram
     12 +RUN adduser -D osintgram
     13 + 
     14 +COPY --from=build /wheels /wheels
     15 +COPY --chown=osintgram:osintgram requirements.txt /home/osintgram/
     16 +RUN pip3 install -r requirements.txt -f /wheels \
     17 + && rm -rf /wheels \
     18 + && rm -rf /root/.cache/pip/* \
     19 + && rm requirements.txt
     20 + 
     21 +COPY --chown=osintgram:osintgram src/ /home/osintgram/src
     22 +COPY --chown=osintgram:osintgram main.py /home/osintgram/
     23 +COPY --chown=osintgram:osintgram config/ /home/osintgram/config
     24 +USER osintgram
     25 + 
     26 +ENTRYPOINT ["python", "main.py"]
     27 + 
  • ■ ■ ■ ■ ■ ■
    Makefile
     1 +SHELL := /bin/bash
     2 + 
     3 +setup:
     4 + 
     5 + @echo -e "\e[34m####### Setup for Osintgram #######\e[0m"
     6 + @[ -d config ] || mkdir config || exit 1
     7 + @echo -n "{}" > config/settings.json
     8 + @read -p "Instagram Username: " uservar; \
     9 + read -sp "Instagram Password: " passvar; \
     10 + echo -en "[Credentials]\nusername = $$uservar\npassword = $$passvar" > config/credentials.ini || exit 1
     11 + @echo ""
     12 + @echo -e "\e[32mSetup Successful - config/credentials.ini created\e[0m"
     13 + 
     14 +run:
     15 + 
     16 + @echo -e "\e[34m######## Building and Running Osintgram with Docker-compose ########\e[0m"
     17 + @[ -d config ] || { echo -e "\e[31mConfig folder not found! Please run 'make setup' before running this command.\e[0m"; exit 1; }
     18 + @echo -e "\e[34m[#] Killing old docker processes\e[0m"
     19 + @docker-compose rm -fs || exit 1
     20 + @echo -e "\e[34m[#] Building docker container\e[0m"
     21 + @docker-compose build || exit 1
     22 + @read -p "Target Username: " username; \
     23 + docker-compose run --rm osintgram $$username
     24 + 
     25 +build-run-testing:
     26 + 
     27 + @echo -e "\e[34m######## Building and Running Osintgram with Docker-compose for Testing/Debugging ########\e[0m"
     28 + @[ -d config ] || { echo -e "\e[31mConfig folder not found! Please run 'make setup' before running this command.\e[0m"; exit 1; }
     29 + @echo -e "\e[34m[#] Killing old docker processes\e[0m"
     30 + @docker-compose rm -fs || exit 1
     31 + @echo -e "\e[34m[#] Building docker container\e[0m"
     32 + @docker-compose build || exit 1
     33 + @echo -e "\e[34m[#] Running docker container in detached mode\e[0m"
     34 + @docker-compose run --name osintgram-testing -d --rm --entrypoint "sleep infinity" osintgram || exit 1
     35 + @echo -e "\e[32m[#] osintgram-test container is now Running!\e[0m"
     36 + 
     37 +cleanup-testing:
     38 + @echo -e "\e[34m######## Cleanup Build-run-testing Container ########\e[0m"
     39 + @docker-compose down
     40 + @echo -e "\e[32m[#] osintgram-test container has been removed\e[0m"
  • ■ ■ ■ ■ ■
    README.md
    1  -[![](https://img.shields.io/badge/version-1.1-green)](https://github.com/Datalux/Osintgram/releases/tag/1.1)
    2  -[![](https://img.shields.io/badge/license-GPLv3-blue)](https://img.shields.io/badge/license-GPLv3-blue)
    3  -[![](https://img.shields.io/badge/language-Python3-red)](https://img.shields.io/badge/language-Python3-red)
    4  -[![](https://img.shields.io/badge/Telegram-Channel-blue.svg)](https://t.me/osintgram)
     1 +# Osintgram 🔎📸
    5 2   
    6  -# Osintgram
    7  -Osintgram is a **OSINT** tool on Instagram.
     3 +[![version-1.1](https://img.shields.io/badge/version-1.1-green)](https://github.com/Datalux/Osintgram/releases/tag/1.1)
     4 +[![GPLv3](https://img.shields.io/badge/license-GPLv3-blue)](https://img.shields.io/badge/license-GPLv3-blue)
     5 +[![Python3](https://img.shields.io/badge/language-Python3-red)](https://img.shields.io/badge/language-Python3-red)
     6 +[![Telegram](https://img.shields.io/badge/Telegram-Channel-blue.svg)](https://t.me/osintgram)
     7 +[![Docker](https://img.shields.io/badge/Docker-Supported-blue)](https://img.shields.io/badge/Docker-Supported-blue)
    8 8   
    9  -**I don't assume any responsability for the use of this tool**
     9 +Osintgram is a **OSINT** tool on Instagram to collect, analyze, and run reconnaissance.
     10 + 
     11 +<p align="center">
     12 +<img align="center" src=".img/carbon.svg" width="900">
     13 +</p>
     14 + 
     15 +Disclaimer: **The contributors do not assume any responsibility for the use of this tool**
     16 + 
     17 +Warning: It is advisable to **not** use your own/primary account when using this tool.
     18 + 
     19 +## Tools and Commands 🧰
    10 20   
    11 21  Osintgram offers an interactive shell to perform analysis on Instagram account of any users by its nickname. You can get:
    12  -```
     22 + 
     23 +```text
    13 24  - addrs Get all registered addressed by target photos
    14 25  - captions Get user's photos captions
    15 26  - comments Get total comments of target's posts
    skipped 15 lines
    31 42  - wcommented Get a list of user who commented target's photos
    32 43  - wtagged Get a list of user who tagged target
    33 44  ```
     45 + 
    34 46  You can find detailed commands usage [here](doc/COMMANDS.md).
    35 47   
    36 48  [**Latest version**](https://github.com/Datalux/Osintgram/releases/tag/1.1) |
     49 +[Commands](doc/COMMANDS.md) |
    37 50  [CHANGELOG](doc/CHANGELOG.md)
    38 51   
    39  -## Tools
    40  -<p align="center">
    41  -<img align="center" src=".img/banner.png" width="700">
    42  -</p>
     52 +## Installation ⚙️
    43 53   
    44  - 
    45  -# Installation
    46 54  1. Fork/Clone/Download this repo
    47 55   
    48 56   `git clone https://github.com/Datalux/Osintgram.git`
    49  - 
    50 57   
    51 58  2. Navigate to the directory
    52 59   
    skipped 1 lines
    54 61   
    55 62  3. Run `pip3 install -r requirements.txt`
    56 63   
     64 +4. Load the virtual environment
     65 + - On Windows Powershell: `.\venv\Scripts\activate.ps1`
     66 + - On Linux and Git Bash: `source venv/bin/activate`
    57 67  
    58  -4. Create a subdirectory `config`
     68 +5. Run `pip install -r requirements.txt`
     69 + 
     70 +6. Open the `credentials.ini` file in the `config` folder and write your Instagram account username and password in the corresponding fields
     71 +
     72 + Alternatively, you can run the `make setup` command to populate this file for you.
     73 + 
     74 +7. Run the main.py script
     75 + 
     76 + `python3 main.py <target username>`
     77 + 
     78 +## Docker Quick Start 🐳
    59 79   
    60  - `mkdir config`
     80 +This section will explain how you can quickly use this image with `Docker` or `Docker-compose`.
    61 81   
    62  -5. Create in `config` folder the file: `username.conf` and write your Instagram account username
     82 +### Prerequisites
    63 83   
    64  -6. Create in `config` folder the file: `pw.conf` and write your Instagram account password
     84 +Before you can use either `Docker` or `Docker-compose`, please ensure you do have the following prerequisites met.
    65 85   
    66  -7. Create in `config` folder the file: `settings.json` and write the following string: "{}" without quotation marks
     86 +1. **Docker** installed - [link](https://docs.docker.com/get-docker/)
     87 +2. **Docker-composed** installed (if using Docker-compose) - [link](https://docs.docker.com/compose/install/)
     88 +3. **Credentials** configured - This can be done manually or by running the `make setup` command from the root of this repo
    67 89   
    68  -8. Run the main.py script
     90 +**Important**: Your container will fail if you do not do step #3 and configure your credentials
    69 91   
    70  - `python3 main.py <target username>`
     92 +### Docker
    71 93   
    72  -## Development version
     94 +If docker is installed you can build an image and run this as a container.
     95 + 
     96 +Build:
     97 + 
     98 +```bash
     99 +docker build -t osintgram .
     100 +```
     101 + 
     102 +Run:
     103 + 
     104 +```bash
     105 +docker run --rm -it -v "$PWD/output:/home/osintgram/output" osintgram <target>
     106 +```
     107 + 
     108 +- The `<target>` is the Instagram account you wish to use as your target for recon.
     109 +- The required `-i` flag enables an interactive terminal to use commands within the container. [docs](https://docs.docker.com/engine/reference/commandline/run/#assign-name-and-allocate-pseudo-tty---name--it)
     110 +- The required `-v` flag mounts a volume between your local filesystem and the container to save to the `./output/` folder. [docs](https://docs.docker.com/engine/reference/commandline/run/#mount-volume--v---read-only)
     111 +- The optional `--rm` flag removes the container filesystem on completion to prevent cruft build-up. [docs](https://docs.docker.com/engine/reference/run/#clean-up---rm)
     112 +- The optional `-t` flag allocates a pseudo-TTY which allows colored output. [docs](https://docs.docker.com/engine/reference/run/#foreground)
     113 + 
     114 +### Using `docker-compose`
     115 + 
     116 +You can use the `docker-compose.yml` file this single command:
     117 + 
     118 +```bash
     119 +docker-compose run osintgram <target>
     120 +```
     121 + 
     122 +Where `target` is the Instagram target for recon.
     123 + 
     124 +Alternatively you may run `docker-compose` with the `Makefile`:
     125 + 
     126 +`make run` - Builds and Runs with compose. Prompts for a `target` before running.
     127 + 
     128 +### Makefile (easy mode)
     129 + 
     130 +For ease of use with Docker-compose, a `Makefile` has been provided.
     131 + 
     132 +Here is a sample work flow to spin up a container and run `osintgram` with just two commands!
     133 + 
     134 +1. `make setup` - Sets up your Instagram credentials
     135 +2. `make run` - Builds and Runs a osintgram container and prompts for a target
     136 + 
     137 +Sample workflow for development:
     138 + 
     139 +1. `make setup` - Sets up your Instagram credentials
     140 +2. `make build-run-testing` - Builds an Runs a container without invoking the `main.py` script. Useful for an `it` Docker session for development
     141 +3. `make cleanup-testing` - Cleans up the testing container created from `build-run-testing`
     142 + 
     143 +## Development version 💻
     144 + 
    73 145  To use the development version with the latest feature and fixes just switch to `development` branch using Git:
    74 146   
    75 147  `git checkout development`
    76 148   
    77  -## Updating
     149 +## Updating
    78 150   
    79  -Run `git pull` in Osintgram directory
     151 +and update to last version using:
    80 152   
    81  -# Contributing
     153 +`git pull origin development`
     154 + 
     155 +## Contributing 💡
     156 + 
    82 157  You can propose a feature request opening an issue or a pull request.
    83 158   
    84  -Here is a list of Osintgram's contributors.
     159 +Here is a list of Osintgram's contributors:
    85 160   
    86 161  <a href="https://github.com/Datalux/Osintgram/graphs/contributors">
    87 162   <img src="https://contributors-img.web.app/image?repo=Datalux/Osintgram" />
    88 163  </a>
    89 164   
    90  -## External library
    91  -Instagram API: https://github.com/ping/instagram_private_api
     165 +## External library
     166 + 
     167 +[Instagram API](https://github.com/ping/instagram_private_api)
    92 168   
  • ■ ■ ■ ■ ■ ■
    config/credentials.ini
     1 +[Credentials]
     2 +username =
     3 +password =
     4 + 
  • ■ ■ ■ ■ ■
    config/settings.json
     1 +{}
  • ■ ■ ■ ■ ■ ■
    doc/CHANGELOG.md
    skipped 22 lines
    23 23  **Enhancements**
    24 24  - Set itself as target (#53)
    25 25  - Get others info from user (`info` command):
    26  - - Whats'App number (if avaible)
    27  - - City Name (if avaible)
    28  - - Address Street (if avaible)
    29  - - Contact phone number (if avaible)
     26 + - Whats'App number (if available)
     27 + - City Name (if available)
     28 + - Address Street (if available)
     29 + - Contact phone number (if available)
    30 30   
    31 31  **Bug fixes**
    32 32  - Fix login issue (#79, #80, #81)
    skipped 95 lines
  • ■ ■ ■ ■ ■ ■
    doc/COMMANDS.md
    skipped 1 lines
    2 2  ```
    3 3  - addrs Get all registered addressed by target photos
    4 4  - captions Get user's photos captions
     5 +- commentdata Get a list of all the comments on the target's posts
    5 6  - comments Get total comments of target's posts
    6 7  - followers Get target followers
    7 8  - followings Get users followed by target
    skipped 61 lines
    69 70  - followed
    70 71  - follow
    71 72  - is business account?
    72  -- business catagory (if target has business account)
     73 +- business category (if target has business account)
    73 74  - is verified?
    74 75  - business email (if available)
    75 76  - HD profile picture url
    76 77  - connected Facebook page (if available)
    77  -- Whats'App number (if avaible)
    78  -- City Name (if avaible)
    79  -- Address Street (if avaible)
    80  -- Contact phone number (if avaible)
     78 +- Whats'App number (if available)
     79 +- City Name (if available)
     80 +- Address Street (if available)
     81 +- Contact phone number (if available)
    81 82   
    82 83  ### JSON
    83 84  Can set preference to export commands output as JSON in output folder. It save output in `<target username>_<command>.JSON` file.
    skipped 6 lines
    90 91  Return the total number of likes in target's posts
    91 92   
    92 93  ### list (or help)
    93  -Show all commands avaible.
     94 +Show all commands available.
    94 95   
    95 96  ### mediatype
    96 97  Return the number of photos and video shared by target
    skipped 4 lines
    101 102  ### photos
    102 103  Download all target's photos in output folder.
    103 104  When you run the command, script ask you how many photos you want to download.
    104  -Type ENTER to download all photos avaible or type a number to choose how many photos you want download.
     105 +Type ENTER to download all photos available or type a number to choose how many photos you want download.
    105 106  ```
    106 107  Run a command: photos
    107 108  How many photos you want to download (default all):
    skipped 19 lines
  • ■ ■ ■ ■ ■ ■
    docker-compose.yml
     1 +version: '3.7'
     2 + 
     3 +services:
     4 + osintgram:
     5 + container_name: osintgram
     6 + build: .
     7 + volumes:
     8 + - ./output:/home/osintgram/output
  • ■ ■ ■ ■ ■ ■
    docker_reqs.txt
     1 +requests==2.24.0
     2 +requests-toolbelt==0.9.1
     3 +geopy>=2.0.0
     4 +prettytable==0.7.2
     5 +instagram-private-api==1.6.0
     6 +gnureadline>=8.0.0
  • ■ ■ ■ ■ ■ ■
    main.py
    1 1  #!/usr/bin/env python3
    2  -# -*- coding: utf-8 -*-
    3 2   
    4 3  from src.Osintgram import Osintgram
    5 4  import argparse
    skipped 20 lines
    26 25   print('\n')
    27 26   pc.printout("Version 1.1 - Developed by Giuseppe Criscione\n\n", pc.YELLOW)
    28 27   pc.printout("Type 'list' to show all allowed commands\n")
    29  - pc.printout("Type 'FILE=y' to save results to files like '<target username>_<command>.txt (deafult is disabled)'\n")
     28 + pc.printout("Type 'FILE=y' to save results to files like '<target username>_<command>.txt (default is disabled)'\n")
    30 29   pc.printout("Type 'FILE=n' to disable saving to files'\n")
    31  - pc.printout("Type 'JSON=y' to export results to a JSON files like '<target username>_<command>.json (deafult is "
     30 + pc.printout("Type 'JSON=y' to export results to a JSON files like '<target username>_<command>.json (default is "
    32 31   "disabled)'\n")
    33 32   pc.printout("Type 'JSON=n' to disable exporting to files'\n")
    34 33   
    skipped 7 lines
    42 41   print("Get all registered addressed by target photos")
    43 42   pc.printout("captions\t")
    44 43   print("Get target's photos captions")
     44 + pc.printout("commentdata\t")
     45 + print("Get a list of all the comments on the target's posts")
    45 46   pc.printout("comments\t")
    46 47   print("Get total comments of target's posts")
    47 48   pc.printout("followers\t")
    skipped 80 lines
    128 129   'exit': _quit,
    129 130   'addrs': api.get_addrs,
    130 131   'captions': api.get_captions,
     132 + "commentdata": api.get_comment_data,
    131 133   'comments': api.get_total_comments,
    132 134   'followers': api.get_followers,
    133 135   'followings': api.get_followings,
    skipped 43 lines
  • ■ ■ ■ ■ ■
    requirements.txt
    skipped 4 lines
    5 5  instagram-private-api==1.6.0
    6 6  gnureadline>=8.0.0; platform_system != "Windows"
    7 7  pyreadline==2.1; platform_system == "Windows"
    8  - 
  • ■ ■ ■ ■ ■ ■
    src/Osintgram.py
    skipped 5 lines
    6 6  import codecs
    7 7   
    8 8  import requests
     9 +import ssl
     10 +ssl._create_default_https_context = ssl._create_unverified_context
    9 11   
    10 12  from geopy.geocoders import Nominatim
    11 13  from instagram_private_api import Client as AppClient
    skipped 2 lines
    14 16  from prettytable import PrettyTable
    15 17   
    16 18  from src import printcolors as pc
     19 +from src import config
    17 20   
    18 21   
    19 22  class Osintgram:
    skipped 9 lines
    29 32   jsonDump = False
    30 33   
    31 34   def __init__(self, target, is_file, is_json):
    32  - u = self.__getUsername__()
    33  - p = self.__getPassword__()
     35 + u = config.getUsername()
     36 + p = config.getPassword()
    34 37   print("\nAttempt to login...")
    35 38   self.login(u, p)
    36 39   self.setTarget(target)
    skipped 7 lines
    44 47   self.is_private = user['is_private']
    45 48   self.following = self.check_following()
    46 49   self.__printTargetBanner__()
    47  - 
    48  - def __getUsername__(self):
    49  - try:
    50  - u = open("config/username.conf", "r").read()
    51  - u = u.replace("\n", "")
    52  - return u
    53  - except FileNotFoundError:
    54  - pc.printout("Error: file \"config/username.conf\" not found!", pc.RED)
    55  - pc.printout("\n")
    56  - sys.exit(0)
    57  - 
    58  - def __getPassword__(self):
    59  - try:
    60  - p = open("config/pw.conf", "r").read()
    61  - p = p.replace("\n", "")
    62  - return p
    63  - except FileNotFoundError:
    64  - pc.printout("Error: file \"config/pw.conf\" not found!", pc.RED)
    65  - pc.printout("\n")
    66  - sys.exit(0)
    67 50   
    68 51   def __get_feed__(self):
    69 52   data = []
    skipped 202 lines
    272 255   pc.printout(str(comments_counter), pc.MAGENTA)
    273 256   pc.printout(" comments in " + str(posts) + " posts\n")
    274 257   
     258 + def get_comment_data(self):
     259 + if self.check_private_profile():
     260 + return
     261 + 
     262 + pc.printout("Retrieving all comments, this may take a moment...\n")
     263 + data = self.__get_feed__()
     264 +
     265 + _comments = []
     266 + t = PrettyTable(['POST ID', 'ID', 'Username', 'Comment'])
     267 + t.align["POST ID"] = "l"
     268 + t.align["ID"] = "l"
     269 + t.align["Username"] = "l"
     270 + t.align["Comment"] = "l"
     271 + 
     272 + for post in data:
     273 + post_id = post.get('id')
     274 + comments = self.api.media_n_comments(post_id)
     275 + for comment in comments:
     276 + t.add_row([post_id, comment.get('user_id'), comment.get('user').get('username'), comment.get('text')])
     277 + comment = {
     278 + "post_id": post_id,
     279 + "user_id":comment.get('user_id'),
     280 + "username": comment.get('user').get('username'),
     281 + "comment": comment.get('text')
     282 + }
     283 + _comments.append(comment)
     284 +
     285 + print(t)
     286 + if self.writeFile:
     287 + file_name = "output/" + self.target + "_comment_data.txt"
     288 + with open(file_name, 'w') as f:
     289 + f.write(str(t))
     290 + f.close()
     291 +
     292 + if self.jsonDump:
     293 + file_name_json = "output/" + self.target + "_comment_data.json"
     294 + with open(file_name_json, 'w') as f:
     295 + f.write("{ \"Comments\":[ \n")
     296 + f.write('\n'.join(json.dumps(comment) for comment in _comments) + ',\n')
     297 + f.write("]} ")
     298 + 
     299 + 
    275 300   def get_followers(self):
    276 301   if self.check_private_profile():
    277 302   return
    skipped 536 lines
    814 839   user_input = input()
    815 840   try:
    816 841   if user_input == "":
    817  - pc.printout("Downloading all photos avaible...\n")
     842 + pc.printout("Downloading all photos available...\n")
    818 843   else:
    819 844   limit = int(user_input)
    820 845   pc.printout("Downloading " + user_input + " photos...\n")
    skipped 235 lines
    1056 1081   settings_file = "config/settings.json"
    1057 1082   if not os.path.isfile(settings_file):
    1058 1083   # settings file does not exist
    1059  - print('Unable to find file: {0!s}'.format(settings_file))
     1084 + print(f'Unable to find file: {settings_file!s}')
    1060 1085   
    1061 1086   # login new
    1062 1087   self.api = AppClient(auto_patch=True, authenticate=True, username=u, password=p,
    skipped 11 lines
    1074 1099   on_login=lambda x: self.onlogin_callback(x, settings_file))
    1075 1100   
    1076 1101   except (ClientCookieExpiredError, ClientLoginRequiredError) as e:
    1077  - print('ClientCookieExpiredError/ClientLoginRequiredError: {0!s}'.format(e))
     1102 + print(f'ClientCookieExpiredError/ClientLoginRequiredError: {e!s}')
    1078 1103   
    1079 1104   # Login expired
    1080 1105   # Do relogin but use default ua, keys and such
    skipped 211 lines
    1292 1317   if self.check_private_profile():
    1293 1318   return
    1294 1319  
    1295  - results = []
    1296  -
    1297 1320   try:
    1298 1321   
    1299 1322   pc.printout("Searching for phone numbers of users followed by target... this can take a few minutes\n")
    skipped 26 lines
    1326 1349   
    1327 1350   next_max_id = results.get('next_max_id')
    1328 1351  
     1352 + results = []
    1329 1353   
    1330 1354   for follow in followings:
    1331 1355   sys.stdout.write("\rCatched %i followings phone numbers" % len(results))
    skipped 183 lines
  • ■ ■ ■ ■ ■ ■
    src/config.py
     1 +import configparser
     2 +import sys
     3 + 
     4 +from src import printcolors as pc
     5 + 
     6 +try:
     7 + config = configparser.ConfigParser(interpolation=None)
     8 + config.read("config/credentials.ini")
     9 +except FileNotFoundError:
     10 + pc.printout('Error: file "config/credentials.ini" not found!\n', pc.RED)
     11 + sys.exit(0)
     12 +except Exception as e:
     13 + pc.printout("Error: {}\n".format(e), pc.RED)
     14 + sys.exit(0)
     15 + 
     16 +def getUsername():
     17 + try:
     18 + 
     19 + username = config["Credentials"]["username"]
     20 + 
     21 + if username == '':
     22 + pc.printout('Error: "username" field cannot be blank in "config/credentials.ini"\n', pc.RED)
     23 + sys.exit(0)
     24 + 
     25 + return username
     26 + except KeyError:
     27 + pc.printout('Error: missing "username" field in "config/credentials.ini"\n', pc.RED)
     28 + sys.exit(0)
     29 + 
     30 +def getPassword():
     31 + try:
     32 + 
     33 + password = config["Credentials"]["password"]
     34 + 
     35 + if password == '':
     36 + pc.printout('Error: "password" field cannot be blank in "config/credentials.ini"\n', pc.RED)
     37 + sys.exit(0)
     38 + 
     39 + return password
     40 + except KeyError:
     41 + pc.printout('Error: missing "password" field in "config/credentials.ini"\n', pc.RED)
     42 + sys.exit(0)
     43 + 
Please wait...
Page is in error, reload to recover