Merge pull request #7 from bellingcat/dev

Dev
This commit is contained in:
Richard Mwewa
2022-12-31 21:16:02 +02:00
committed by GitHub
11 changed files with 291 additions and 161 deletions

View File

@@ -6,7 +6,6 @@ WORKDIR /app
COPY . .
RUN pip install --upgrade pip && pip install build && python -m build
RUN pip install dist/*.whl
RUN pip install --upgrade pip && pip install build && python -m build && pip install dist/*.whl
ENTRYPOINT ["octosuite"]

View File

@@ -1,6 +1,6 @@
![logo](https://user-images.githubusercontent.com/74001397/175805580-fffc96d4-e0ef-48bb-a55c-80b2da3e714d.png)
A framework fro gathering osint on GitHub users, repositories and organizations
A framework for gathering osint on GitHub users, repositories and organizations
[![Upload Python Package](https://github.com/bellingcat/octosuite/actions/workflows/python-publish.yml/badge.svg)](https://github.com/bellingcat/octosuite/actions/workflows/python-publish.yml)
[![CodeQL](https://github.com/bellingcat/octosuite/actions/workflows/codeql.yml/badge.svg)](https://github.com/bellingcat/octosuite/actions/workflows/codeql.yml)

23
octosuite/banner.py Normal file
View File

@@ -0,0 +1,23 @@
import getpass
from octosuite.config import red, white, green, reset, Tree
# banner.py
# This file holds the program's banner and version tag
version_tag = "3.0.0"
def banner():
banner_tree = Tree(getpass.getuser())
banner_tree.add(f"use {green}help{reset} command for usage")
banner_tree.add(f"commands are case insensitive\n")
return f"""
_______ __ _______ __ __
| |.----.| |_.-----.| __|.--.--.|__| |_.-----.
| - || __|| _| _ ||__ || | || | _| -__|
|_______||____||____|_____||_______||_____||__|____|_____|
v{version_tag}#dev
{white}— Advanced Github {red}OSINT{white} Framework
""", banner_tree

View File

@@ -1,44 +0,0 @@
import random
import getpass
from rich.tree import Tree
from octosuite.colors import red, white, green, reset
# banners.py
# This file holds the program's banners and version tag
version_tag = "3.0.0"
def ascii_banner():
banner_tree = Tree(getpass.getuser())
banner_tree.add(f"use {green}help{reset} command for usage")
banner_tree.add(f"commands are case insensitive\n")
ascii_banners = [
"""
_______ __ _______ __ __
| |.----.| |_.-----.| __|.--.--.|__| |_.-----.
| - || __|| _| _ ||__ || | || | _| -__|
|_______||____||____|_____||_______||_____||__|____|_____|
""",
"""
╔═╗┌─┐┌┬┐┌─┐╔═╗┬ ┬┬┌┬┐┌─┐
║ ║│ │ │ │╚═╗│ ││ │ ├┤
╚═╝└─┘ ┴ └─┘╚═╝└─┘┴ ┴ └─┘
""",
"""
░▒█▀▀▀█░█▀▄░▀█▀░▄▀▀▄░▒█▀▀▀█░█░▒█░░▀░░▀█▀░█▀▀
░▒█░░▒█░█░░░░█░░█░░█░░▀▀▀▄▄░█░▒█░░█▀░░█░░█▀▀
░▒█▄▄▄█░▀▀▀░░▀░░░▀▀░░▒█▄▄▄█░░▀▀▀░▀▀▀░░▀░░▀▀▀
""",
"""
▄▀▄ ▄▀▀ ▀█▀ ▄▀▄ ▄▀▀ █ █ █ ▀█▀ ██▀
▀▄▀ ▀▄▄ █ ▀▄▀ ▄██ ▀▄█ █ █ █▄▄
"""]
ascii_banner = random.choice(ascii_banners)
return banner_tree, f"""{ascii_banner} v{version_tag}#dev
{white}— Advanced Github {red}OSINT{white} Framework
"""

View File

@@ -1,51 +0,0 @@
import psutil
import platform
from rich.tree import Tree
from datetime import datetime
from rich import print as xprint
# This file is responsible for enabling/disabling colors in OctoSuite
# This file gets called first at start up before any other file gets called
# colors.py is the reason why users get to choose whether to enable/disable colors
# delete this file, the entire program breaks
system_info = [("RAM", f"{str(round(psutil.virtual_memory().total / (1024.0 ** 3)))}GB"),
("Node", platform.node()),
("Release", platform.release()),
("Version", platform.version()),
("Processor", platform.processor()),
("Architecture", platform.architecture())]
first_banner = f"""
OCTOSUITE © 2023 Richard Mwewa
{datetime.now().strftime('%A %d %B %Y, %H:%M:%S%p')}
"""
print(first_banner)
system_tree = Tree(platform.system())
for system_key, system_value in system_info:
system_tree.add(f"{system_key}: {system_value}")
xprint(system_tree)
print("\n")
while True:
try:
color_chooser = input(f"[PROMPT] Welcome, would you like to enable colors for this session? (yes/no) ").lower()
if color_chooser == "yes":
header_title = "bold white"
red = "[red]"
white = "[white]"
green = "[green]"
red_bold = "[white bold]"
white_bold = "[white bold]"
green_bold = "[green bold]"
reset = "[/]"
break
elif color_chooser == "no":
header_title = red = white = green = red_bold = white_bold = green_bold = reset = ""
break
else:
print(f"\n[INVALID] Your response '{color_chooser}' is invalid (expected yes or no)")
except KeyboardInterrupt:
exit(f"[WARNING] Process interrupted with Ctrl+C.")

200
octosuite/config.py Normal file
View File

@@ -0,0 +1,200 @@
import psutil
import platform
import argparse
from rich.tree import Tree
from rich.text import Text
from rich.table import Table
from datetime import datetime
from rich import print as xprint
from rich.prompt import Prompt, Confirm
def usage():
return """
Basic Usage
===========
Get User Profile Info
---------------------
octosuite --method user_profile --username <username>
Get User Repos
--------------
octosuite --method user_repos --username <username>
Get Organi[sz]ation Profile Info
-----------------------------
octosuite --method org_profile --organization <organization_name>
Get Organi[sz]ation Repos
-----------------------------
octosuite --method org_repos --organization <organization_name>
Get Repo Profile Info
---------------------
octosuite --method repo_profile --username <username> --repository <repo_name>
Get Repo Forks
--------------
octosuite --method repo_forks --username <username> --repository <repo_name>
Searching
=========
Search Users
------------
octosuite --method users_search --query <query>
Search Issues
-------------
octosuite --method issues_search --query <query>
Search Commits
--------------
octosuite --method commits_search --query <query>
Search Topics
-------------
octosuite --method topics_search --query <query>
Search Repositories
-------------------
octosuite --method repos_search --query <query>
Log Management
==============
View logs
---------
octosuite --method view_logs
Read log
--------
octosuite --method read_log --log_file <log_file>
Delete log
----------
octosuite --method delete_log --log_file <log_file>
Clear logs
----------
octosuite --method clear_logs
CSV Management
==============
View CSV
---------
octosuite --method view_csv
Read CSV
--------
octosuite --method read_csv --csv_file <csv_file>
Delete CSV
----------
octosuite --method delete_csv --csv_file <csv_file>
Clear CSV's
-----------
octosuite --method clear_csv
"""
def create_parser():
parser = argparse.ArgumentParser(description='OCTOSUITE: Advanced GitHub osint framework — by Richard Mwewa | https://about.me/rly0nheart', usage=usage())
parser.add_argument('-m', '--method', help='method', choices=['user_profile', 'user_repos', 'user_gists', 'user_orgs', 'user_events',
'user_subscriptions', 'user_following', 'user_followers', 'user_follows',
'org_profile', 'org_repos', 'org_events', 'org_member',
'repo_profile', 'repo_contributors', 'repo_stargazers', 'repo_forks',
'repo_issues', 'repo_releases', 'repo_path_contents', 'users_search', 'issues_search',
'commits_search', 'topics_search', 'repos_search', 'view_logs', 'read_log', 'delete_log',
'clear_logs', 'view_csv', 'read_csv', 'delete_csv', 'clear_csv', 'about', 'author'])
parser.add_argument('-u', '--username', help='username')
parser.add_argument('-uB', '--username_b', help='username_B (used with user_follows)')
parser.add_argument('-o', '--organization', '--organisation', help='organi[sz]ation name')
parser.add_argument('-r', '--repository', help='repository name')
parser.add_argument('-p', '--path_name', help='path name (used with repo_path_contents)')
parser.add_argument('-q', '--query', help='query (used with search methods)')
parser.add_argument('-l', '--limit', help='output limit (used with methods that return results in bulk) (default: %(default)s)', default=10)
parser.add_argument('-c', '--colors', '--colours', help='specify to run octosuite cli with colo[u]rs enabled', action='store_true')
parser.add_argument('--csv_file', help='csv file (used with csv management methods)')
parser.add_argument('--log_file', help='log file (used with logs management methods)')
parser.add_argument('--log-to-csv', help='log output to a csv file', action='store_true', dest='log_csv')
return parser
parser = create_parser()
args = parser.parse_args()
# This file is responsible for enabling/disabling colo[u]rs and configuring argparse in OctoSuite
# This file gets called first at start up before any other file
# config.py is the reason why users get to choose whether to enable/disable colo[u]rs, and call the program with command line arguments
# delete this file (I dare you), the entire program breaks
system_info = [("RAM", f"{str(round(psutil.virtual_memory().total / (1024.0 ** 3)))}GB"),
("Node", platform.node()),
("Release", platform.release()),
("Version", platform.version()),
("Processor", platform.processor()),
("Architecture", platform.architecture())]
first_banner = f"""
OCTOSUITE © 2023 Richard Mwewa
{datetime.now().strftime('%A %d %B %Y, %H:%M:%S%p')}
"""
if args.colors:
header_title = "bold white"
red = "[red]"
white = "[white]"
green = "[green]"
yellow = "[yellow]"
red_bold = "[white bold]"
white_bold = "[white bold]"
green_bold = "[green bold]"
reset = "[/]"
else:
print(first_banner)
system_tree = Tree(platform.system())
for system_key, system_value in system_info:
system_tree.add(f"{system_key}: {system_value}")
xprint(system_tree)
print("\n")
try:
color_chooser = Confirm.ask(f"Welcome, would you like to enable colo(u)rs for this session?")
if color_chooser:
header_title = "bold white"
red = "[red]"
white = "[white]"
green = "[green]"
yellow = "[yellow]"
red_bold = "[white bold]"
white_bold = "[white bold]"
green_bold = "[green bold]"
reset = "[/]"
else:
header_title = red = white = green = red_bold = white_bold = green_bold = reset = yellow = ""
except KeyboardInterrupt:
exit(f"[WARNING] Process interrupted with Ctrl+C.")

View File

@@ -2,7 +2,7 @@ import os
import csv
import logging
from rich import print as xprint
from octosuite.log_roller import prompt_log_csv, logged_to_csv, logging_skipped
from octosuite.log_roller import prompt_log_csv, logged_to_csv
from octosuite.message_prefixes import PROMPT, WARNING, POSITIVE, NEGATIVE, INFO
@@ -440,4 +440,3 @@ def log_commits_search(commit, query):
logging.info(logged_to_csv.format(file.name))
xprint(f"{POSITIVE} {logged_to_csv.format(file.name)}")

View File

@@ -1,6 +1,5 @@
from rich.table import Table
from rich import print as xprint
from octosuite.colors import white, green, white_bold, green_bold, header_title, reset
from octosuite.config import Tree, xprint, white, green, white_bold, green_bold, header_title, reset
# helper.py
# This file holds the help text for available commands.
@@ -146,6 +145,8 @@ def help_command():
core_cmd_table = Table(show_header=True, header_style=header_title)
core_cmd_table.add_column("Command", style="dim", width=12)
core_cmd_table.add_column("Description")
core_cmd_table.add_row("ls", "List contents of the specified directory")
core_cmd_table.add_row("cd", "Move to specified directory")
core_cmd_table.add_row("help", "Help menu")
core_cmd_table.add_row("exit", "Close session")
core_cmd_table.add_row("clear", "Clear screen")

View File

@@ -16,7 +16,6 @@ info_not_found = "Information not found: {}, {}, {}"
user_not_found = "User not found: @{}"
org_not_found = "Organization not found: @{}"
repo_or_user_not_found = "Repository or User not found: {}, @{}"
prompt_log_csv = "Do you wish to log this output to a CSV file?"
prompt_log_csv = "Would you like to log this output to a .csv file?"
logged_to_csv = "Output logged: {}"
logging_skipped = "Logging skipped: {}"
limit_output = "Limit '{}' output to how many? (1-100)"

View File

@@ -9,7 +9,7 @@ def octosuite():
clear_screen()
configure_logging()
check_updates()
if args:
if args.method:
"""
Iterate over the argument_map and check if the passed command line argument matches any argument in it [argument_map],
if there's a match, we return its method. If no match is found, we do nothing (which will return the usage).
@@ -18,29 +18,31 @@ def octosuite():
if args.method == argument:
method()
print("\n")
"""
Main loop keeps octosuite running, this will break if Octosuite detects a KeyboardInterrupt (Ctrl+C)
or if the 'exit' command is entered.
"""
xprint(banner()[0], banner()[1])
while True:
command_input = Prompt.ask(f"{white}┌──({red}{getpass.getuser()}{white}@{red}octosuite{white})\n├──[~{green}{os.getcwd()}{white}]\n└╼ {reset}")
else:
pass
else:
"""
Iterate over the command_map and check if the user input matches any command in it [command_map],
if there's a match, we return its method. If no match is found, we ignore it.
Main loop keeps octosuite running, this will break if Octosuite detects a KeyboardInterrupt (Ctrl+C)
or if the 'exit' command is entered.
"""
if command_input[:2] == 'cd':
os.chdir(command_input[3:])
elif command_input[:2] == 'ls':
os.system(f'dir {command_input[3:]}' if os.name == 'nt' else f'ls {command_input[3:]}')
else:
for command, method in run.command_map:
if command_input == command:
method()
print("\n")
else:
pass
xprint(banner()[0], banner()[1])
while True:
command_input = Prompt.ask(f"{white}┌──({red}{getpass.getuser()}{white}@{red}octosuite{white})\n├──[~{green}{os.getcwd()}{white}]\n└╼{reset}")
"""
Iterate over the command_map and check if the user input matches any command in it [command_map],
if there's a match, we return its method. If no match is found, we ignore it.
"""
if command_input[:2] == 'cd':
os.chdir(command_input[3:])
elif command_input[:2] == 'ls':
os.system(f'dir {command_input[3:]}' if os.name == 'nt' else f'ls {command_input[3:]}')
else:
for command, method in run.command_map:
if command_input == command:
method()
print("\n")
else:
pass
except KeyboardInterrupt:
logging.warning(ctrl_c)

View File

@@ -10,14 +10,14 @@ import platform
import subprocess
from datetime import datetime
from octosuite.banner import version_tag, banner
from octosuite.config import Tree, Text, Table, Prompt, xprint, create_parser, args, red, white, green, yellow, header_title, reset
from octosuite.config import Tree, Text, Table, Prompt, Confirm, xprint, create_parser, args, red, white, green, yellow, header_title, reset
from octosuite.message_prefixes import ERROR, WARNING, PROMPT, POSITIVE, NEGATIVE, INFO # wondering why I name all the variables instead of just using the * wildcard?, because it's the pythonic way lol
# seriously now, the reason why I am doing this, is so that you know exactly what I am importing from a named module :)
from octosuite.helper import help_command, source_command, search_command, user_command, repo_command, \
logs_command, csv_command, org_command, source, org, repo, user, search, logs, csv
from octosuite.log_roller import ctrl_c, error, session_opened, session_closed, viewing_logs, viewing_csv, \
deleted, reading, file_downloading, file_downloaded, info_not_found, \
user_not_found, org_not_found, repo_or_user_not_found, limit_output, prompt_log_csv, logging_skipped
user_not_found, org_not_found, repo_or_user_not_found, limit_output, prompt_log_csv
from octosuite.csv_loggers import log_org_profile, log_user_profile, log_repo_profile, log_repo_path_contents, \
log_repo_contributors, log_repo_stargazers, log_repo_forks, log_repo_issues, log_repo_releases, log_org_repos, \
log_org_profile, log_user_repos, log_user_gists, log_user_orgs, log_user_events, log_user_subscriptions, \
@@ -88,7 +88,7 @@ def delete_csv():
if args.csv_file:
csv_file = args.csv_file
else:
csv_file = Prompt.ask(f"{green}csv {white}(filename){reset}")
csv_file = Prompt.ask(f"{green}.csv {white}(filename){reset}")
os.remove(os.path.join("output", csv_file))
logging.info(deleted.format(csv_file))
xprint(f"{POSITIVE} {deleted.format(csv_file)}")
@@ -96,8 +96,8 @@ def delete_csv():
# Clear csv files
def clear_csv():
prompt = Prompt.ask(f"{PROMPT} This will clear all {len(os.listdir('output'))} csv files, continue?", choices=['yes', 'no'])
if prompt == "yes":
clear_csv_prompt = Confirm.ask(f"{PROMPT} This will clear all {len(os.listdir('output'))} csv files, continue?")
if clear_csv_prompt:
shutil.rmtree('output', ignore_errors=True)
xprint(f"{INFO} csv files cleared successfully!")
else:
@@ -121,7 +121,7 @@ def read_csv():
if args.csv_file:
csv_file = args.csv_file
else:
csv_file = Prompt.ask(f"{green}csv {white}(filename){reset}")
csv_file = Prompt.ask(f"{green}.csv {white}(filename){reset}")
with open(os.path.join("output", csv_file), "r") as file:
logging.info(reading.format(csv_file))
text = Text(file.read())
@@ -145,7 +145,7 @@ def read_log():
if args.log_file:
log_file = args.log_file
else:
log_file = Prompt.ask(f"{green}log date{white} (eg. 2022-04-27 10:09:36AM){reset}")
log_file = Prompt.ask(f"{green}.log date{white} (eg. 2022-04-27 10:09:36AM){reset}")
with open(os.path.join(".logs", log_file + ".log"), "r") as log:
logging.info(reading.format(log_file))
xprint("\n" + log.read())
@@ -156,7 +156,7 @@ def delete_log():
if args.log_file:
log_file = args.log_file
else:
log_file = Prompt.ask(f"{green}log date{white} (eg. 2022-04-27 10:09:36AM){reset}")
log_file = Prompt.ask(f"{green}.log date{white} (eg. 2022-04-27 10:09:36AM){reset}")
os.remove(os.path.join(".logs", log_file))
logging.info(deleted.format(log_file))
xprint(f"{POSITIVE} {deleted.format(log_file)}")
@@ -164,8 +164,8 @@ def delete_log():
# Clear logs
def clear_logs():
prompt = Prompt.ask(f"{PROMPT} This will clear all {len(os.listdir('output'))} log files, continue?", choices=['yes', 'no'])
if prompt == "yes":
clear_logs_prompt = Confirm.ask(f"{PROMPT} This will clear all {len(os.listdir('output'))} log files, continue?")
if clear_logs_prompt:
shutil.rmtree('.logs', ignore_errors=True)
xprint(f"{INFO} .log files cleared successfully!")
exit()
@@ -175,8 +175,8 @@ def clear_logs():
# Exit session
def exit_session():
exit_prompt = Prompt.ask(f"{PROMPT} This will close the current session, continue?", choices=['yes', 'no'])
if exit_prompt == "yes":
exit_prompt = Confirm.ask(f"{PROMPT} This will close the current session, continue?")
if exit_prompt:
logging.info(session_closed.format(datetime.now()))
xprint(f"{INFO} {session_closed.format(datetime.now())}")
exit()
@@ -521,7 +521,7 @@ class Octosuite:
if args.organization:
organization = args.organization
else:
organization = Prompt.ask(f"{white}@{green}Organization{reset}")
organization = Prompt.ask(f"{white}@{green}Organi[sz]ation{reset}")
response = requests.get(f"{self.endpoint}/orgs/{organization}")
if response.status_code == 404:
xprint(f"{NEGATIVE} {org_not_found.format(organization)}")
@@ -530,7 +530,9 @@ class Octosuite:
for attr in self.org_attrs:
org_profile_tree.add(f"{self.org_attr_dict[attr]}: {response.json()[attr]}")
xprint(org_profile_tree)
log_org_profile(response)
if args.log_csv or Confirm.ask(f"\n{PROMPT} {prompt_log_csv}"):
log_org_profile(response)
else:
xprint(response.json())
@@ -550,7 +552,7 @@ class Octosuite:
xprint(user_profile_tree)
# Logging output to a csv file
if args.log_csv or Prompt.ask(f"{PROMPT} {prompt_log_csv}", choices=['yes', 'no']) == "yes":
if args.log_csv or Confirm.ask(f"\n{PROMPT} {prompt_log_csv}"):
log_user_profile(response)
else:
xprint(response.json())
@@ -572,7 +574,7 @@ class Octosuite:
repo_profile_tree.add(f"{self.repo_attr_dict[attr]}: {response.json()[attr]}")
xprint(repo_profile_tree)
if args.log_csv or Prompt.ask(f"{PROMPT} {prompt_log_csv}", choices=['yes', 'no']) == "yes":
if args.log_csv or Confirm.ask(f"\n{PROMPT} {prompt_log_csv}"):
log_repo_profile(response)
else:
xprint(response.json())
@@ -621,7 +623,7 @@ class Octosuite:
contributor_tree.add(f"{self.user_attr_dict[attr]}: {contributor[attr]}")
xprint(contributor_tree)
if args.log_csv or Prompt.ask(f"{PROMPT} {prompt_log_csv}", choices=['yes', 'no']) == "yes":
if args.log_csv or Confirm.ask(f"\n{PROMPT} {prompt_log_csv}"):
log_repo_contributors(contributor, repo_name)
else:
xprint(response.json())
@@ -648,7 +650,7 @@ class Octosuite:
stargazer_tree.add(f"{self.user_attr_dict[attr]}: {stargazer[attr]}")
xprint(stargazer_tree)
if args.log_csv or Prompt.ask(f"{PROMPT} {prompt_log_csv}", choices=['yes', 'no']) == "yes":
if args.log_csv or Confirm.ask(f"\n{PROMPT} {prompt_log_csv}"):
log_repo_stargazers(stargazer, repo_name)
else:
xprint(response.json())
@@ -675,7 +677,7 @@ class Octosuite:
fork_tree.add(f"{self.repo_attr_dict[attr]}: {fork[attr]}")
xprint(fork_tree)
if args.log_csv or Prompt.ask(f"{PROMPT} {prompt_log_csv}", choices=['yes', 'no']) == "yes":
if args.log_csv or Confirm.ask(f"\n{PROMPT} {prompt_log_csv}"):
log_repo_forks(fork, count)
else:
xprint(response.json())
@@ -729,7 +731,7 @@ class Octosuite:
xprint(releases_tree)
xprint(release['body'])
if args.log_csv or Prompt.ask(f"{PROMPT} {prompt_log_csv}", choices=['yes', 'no']) == "yes":
if args.log_csv or Confirm.ask(f"\n{PROMPT} {prompt_log_csv}"):
log_repo_releases(release, repo_name)
else:
xprint(response.json())
@@ -740,7 +742,7 @@ class Octosuite:
organization = args.organization
limit = args.limit
else:
organization = Prompt.ask(f"{white}@{green}Organization{reset}")
organization = Prompt.ask(f"{white}@{green}Organi[sz]ation{reset}")
limit = Prompt.ask(limit_output.format("organization repositories"))
response = requests.get(f"{self.endpoint}/orgs/{organization}/repos?per_page={limit}")
if response.status_code == 404:
@@ -752,7 +754,7 @@ class Octosuite:
repos_tree.add(f"{self.repo_attr_dict[attr]}: {repository[attr]}")
xprint(repos_tree)
if args.log_csv or Prompt.ask(f"{PROMPT} {prompt_log_csv}", choices=['yes', 'no']) == "yes":
if args.log_csv or Prompt.ask(f"{PROMPT} {prompt_log_csv}") == "yes":
log_org_repos(repository, organization)
else:
xprint(response.json())
@@ -763,7 +765,7 @@ class Octosuite:
organization = args.organization
limit = args.limit
else:
organization = Prompt.ask(f"{white}@{green}Organization{reset}")
organization = Prompt.ask(f"{white}@{green}Organi[sz]ation{reset}")
limit = Prompt.ask(limit_output.format("organization events"))
response = requests.get(f"{self.endpoint}/orgs/{organization}/events?per_page={limit}")
if response.status_code == 404:
@@ -785,7 +787,7 @@ class Octosuite:
organization = args.organization
username = args.username
else:
organization = Prompt.ask(f"{white}@{green}Organization{reset}")
organization = Prompt.ask(f"{white}@{green}Organi[sz]ation{reset}")
username = Prompt.ask(f"{white}@{green}Username{reset}")
response = requests.get(f"{self.endpoint}/orgs/{organization}/public_members/{username}")
if response.status_code == 204:
@@ -811,7 +813,7 @@ class Octosuite:
repos_tree.add(f"{self.repo_attr_dict[attr]}: {repository[attr]}")
xprint(repos_tree)
if args.log_csv or Prompt.ask(f"{PROMPT} {prompt_log_csv}", choices=['yes', 'no']) == "yes":
if args.log_csv or Confirm.ask(f"\n{PROMPT} {prompt_log_csv}"):
log_user_repos(repository, username)
else:
xprint(response.json())
@@ -836,7 +838,7 @@ class Octosuite:
gists_tree.add(f"{self.gists_attr_dict[attr]}: {gist[attr]}")
xprint(gists_tree)
if args.log_csv or Prompt.ask(f"{PROMPT} {prompt_log_csv}", choices=['yes', 'no']) == "yes":
if args.log_csv or Confirm.ask(f"\n{PROMPT} {prompt_log_csv}"):
log_user_gists(gist)
else:
xprint(response.json())
@@ -861,7 +863,7 @@ class Octosuite:
org_tree.add(f"{self.user_orgs_attr_dict[attr]}: {organization[attr]}")
xprint(org_tree)
if args.log_csv or Prompt.ask(f"{PROMPT} {prompt_log_csv}", choices=['yes', 'no']) == "yes":
if args.log_csv or Confirm.ask(f"\n{PROMPT} {prompt_log_csv}"):
log_user_orgs(organization, username)
else:
xprint(response.json())
@@ -910,7 +912,7 @@ class Octosuite:
subscriptions_tree.add(f"{self.repo_attr_dict[attr]}: {repository[attr]}")
xprint(subscriptions_tree)
if args.log_csv or Prompt.ask(f"{PROMPT} {prompt_log_csv}", choices=['yes', 'no']) == "yes":
if args.log_csv or Confirm.ask(f"\n{PROMPT} {prompt_log_csv}"):
log_user_subscriptions(repository, username)
else:
xprint(response.json())
@@ -935,7 +937,7 @@ class Octosuite:
following_tree.add(f"{self.user_attr_dict[attr]}: {user[attr]}")
xprint(following_tree)
if args.log_csv or Prompt.ask(f"{PROMPT} {prompt_log_csv}", choices=['yes', 'no']) == "yes":
if args.log_csv or Confirm.ask(f"\n{PROMPT} {prompt_log_csv}"):
log_user_following(user, username)
else:
xprint(response.json())
@@ -960,7 +962,7 @@ class Octosuite:
followers_tree.add(f"{self.user_attr_dict[attr]}: {follower[attr]}")
xprint(followers_tree)
if args.log_csv or Prompt.ask(f"{PROMPT} {prompt_log_csv}", choices=['yes', 'no']) == "yes":
if args.log_csv or Confirm.ask(f"\n{PROMPT} {prompt_log_csv}"):
log_user_followers(follower, username)
else:
xprint(response.json())
@@ -994,7 +996,7 @@ class Octosuite:
users_search_tree.add(f"{self.user_attr_dict[attr]}: {user[attr]}")
xprint(users_search_tree)
if args.log_csv or Prompt.ask(f"{PROMPT} {prompt_log_csv}", choices=['yes', 'no']) == "yes":
if args.log_csv or Confirm.ask(f"\n{PROMPT} {prompt_log_csv}"):
log_users_search(user, query)
# Repository search
@@ -1012,7 +1014,7 @@ class Octosuite:
repos_search_tree.add(f"{self.repo_attr_dict[attr]}: {repository[attr]}")
xprint(repos_search_tree)
if args.log_csv or Prompt.ask(f"{PROMPT} {prompt_log_csv}", choices=['yes', 'no']) == "yes":
if args.log_csv or Confirm.ask(f"\n{PROMPT} {prompt_log_csv}"):
log_repos_search(repository, query)
# Topics search
@@ -1030,7 +1032,7 @@ class Octosuite:
topics_search_tree.add(f"{self.topic_attr_dict[attr]}: {topic[attr]}")
xprint(topics_search_tree)
if args.log_csv or Prompt.ask(f"{PROMPT} {prompt_log_csv}", choices=['yes', 'no']) == "yes":
if args.log_csv or Confirm.ask(f"\n{PROMPT} {prompt_log_csv}"):
log_topics_search(topic, query)
# Issue search
@@ -1049,7 +1051,7 @@ class Octosuite:
xprint(issues_search_tree)
xprint(issue['body'])
if args.log_csv or Prompt.ask(f"{PROMPT} {prompt_log_csv}", choices=['yes', 'no']) == "yes":
if args.log_csv or Confirm.ask(f"\n{PROMPT} {prompt_log_csv}"):
log_issues_search(issue, query)
# Commits search
@@ -1072,7 +1074,7 @@ class Octosuite:
xprint(commits_search_tree)
xprint(commit['commit']['message'])
if args.log_csv or Prompt.ask(f"{PROMPT} {prompt_log_csv}", choices=['yes', 'no']) == "yes":
if args.log_csv or Confirm.ask(f"\n{PROMPT} {prompt_log_csv}"):
log_commits_search(commit, query)
# Downloading release tarball