mirror of
https://github.com/bellingcat/octosuite.git
synced 2026-06-12 05:18:33 +03:00
Update and rename src/main.py to core/main.py
This commit is contained in:
748
core/main.py
Normal file
748
core/main.py
Normal file
@@ -0,0 +1,748 @@
|
||||
'''
|
||||
OCTOSUITE Advanced Github OSINT Framework
|
||||
Copyright (C) 2022 Richard Mwewa
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
'''
|
||||
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import requests
|
||||
import platform
|
||||
import subprocess
|
||||
from tqdm import tqdm
|
||||
from pprint import pprint
|
||||
from utils.misc import Banner
|
||||
from utils.helper import Help
|
||||
from utils.colors import Color
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
# logMsg
|
||||
# This class is where the main notification strings/messages are held,
|
||||
# and are being used in two different cases (they're beig used by logging to be written to log files, and being printed out to the screen).
|
||||
class logMsg:
|
||||
Ctrl = 'Session terminated with (Ctrl+C).'
|
||||
Error = 'Session terminated on error: {}'
|
||||
sessionStart = 'Started new session on {}:{}'
|
||||
sessionClosed = 'Session closed with (exit) command.'
|
||||
fileDeleted = 'Deleted log: {}'
|
||||
readingFile = 'Reading log: {}'
|
||||
viewingLogs = 'Viewing logs...'
|
||||
checkingUpdates = 'Checking for update(s)...'
|
||||
installingUpdates = 'Installing update(s)...'
|
||||
installedUpdates = '{} Update(s) installed.'
|
||||
|
||||
|
||||
# firstBlood
|
||||
# *I couldn't think of a good name for this.*
|
||||
# The firstBlood is responsible for creating/checking the availability of the .logs folder
|
||||
# and enabling logging to automatically log network/user activity to a file.
|
||||
class firstBlood:
|
||||
# If .logs folder exists, we ignore (pass)
|
||||
if os.path.exists('.logs'):
|
||||
pass
|
||||
else:
|
||||
# Creating the .logs directory
|
||||
# If the current system is Windows based, we run mkdir command (without sudo?)
|
||||
# Else we run the mkdir command (with sudo)
|
||||
# As of writing, I have absolutely no idea if Windows users also use sudo to run commands as root/admin
|
||||
if sys.platform.lower().startswith(('win','darwin')):
|
||||
subprocess.run(['mkdir','.logs'])
|
||||
else:
|
||||
subprocess.run(['sudo','mkdir','.logs'],shell=False)
|
||||
|
||||
# Set to automatically monitor and log network and user activity into the .logs folder
|
||||
now = datetime.now()
|
||||
now_formatted = now.strftime('%Y-%m-%d %H:%M:%S%p')
|
||||
logging.basicConfig(filename=f'.logs/{now_formatted}.log',format='[%(asctime)s] [%(levelname)s] %(message)s',datefmt='%Y-%m-%d %H:%M:%S%p',level=logging.DEBUG)
|
||||
|
||||
|
||||
# Attributes
|
||||
# *Even here, I couldn't think of a good name.*
|
||||
# The Attributes class holds the signs/symbols that show what a notification in OctoSuite might be all about.
|
||||
# This might not be very important or necessary in some cases, but I think it's better to know the severerity of the notifications you get in a program.
|
||||
class Attributes:
|
||||
prompt = f'\n{Color.white}[{Color.green}?{Color.white}]{Color.reset}'
|
||||
warning = f'\n{Color.white}[{Color.red}!{Color.white}]{Color.reset}'
|
||||
error = f'\n{Color.white}[{Color.red}x{Color.white}]{Color.reset}'
|
||||
positive = f'\n{Color.white}[{Color.green}+{Color.white}]{Color.reset}'
|
||||
negative = f'\n{Color.white}[{Color.red}-{Color.white}]{Color.reset}'
|
||||
info = f'\n{Color.white}[{Color.green}*{Color.white}]{Color.reset}'
|
||||
|
||||
|
||||
# Octosuite
|
||||
# This class is the backbone of the program.
|
||||
# It holds all important methods/information for the program.
|
||||
class Octosuite:
|
||||
def __init__(self):
|
||||
firstBlood()
|
||||
# A list of tuples, mapping commands to their respective functionalities
|
||||
self.commands_map = [('org:events', self.orgEvents),
|
||||
('org:profile', self.orgProfile),
|
||||
('org:repos', self.orgRepos),
|
||||
('org:member', self.orgMember),
|
||||
('repo:pathcontents', self.pathContents),
|
||||
('repo:profile', self.repoProfile),
|
||||
('repo:contributors', self.repoContributors),
|
||||
('repo:languages', self.repoLanguages),
|
||||
('repo:stargazers', self.repoStargazers),
|
||||
('repo:forks', self.repoForks),
|
||||
('repo:releases', self.repoReleases),
|
||||
('user:repos', self.userRepos),
|
||||
('user:gists', self.userGists),
|
||||
('user:orgs', self.userOrgs),
|
||||
('user:profile', self.userProfile),
|
||||
('user:events', self.userEvents),
|
||||
('user:followers', self.userFollowers),
|
||||
('user:following', self.userFollowing),
|
||||
('user:subscriptions', self.userSubscriptions),
|
||||
('search:users', self.userSearch),
|
||||
('search:repos', self.repoSearch),
|
||||
('search:topics', self.topicSearch),
|
||||
('search:issues', self.issueSearch),
|
||||
('search:commits', self.commitsSearch),
|
||||
('logs:view',self.viewLogs),
|
||||
('logs:read',self.readLog),
|
||||
('logs:delete',self.deleteLog),
|
||||
('update:check', self.checkUpdate),
|
||||
('update:install', self.installUpdate),
|
||||
('help', Help.helpCommand),
|
||||
('help:search', Help.searchCommand),
|
||||
('help:user', Help.userCommand),
|
||||
('help:repo', Help.repoCommand),
|
||||
('help:logs', Help.logsCommand),
|
||||
('help:org', Help.orgCommand),
|
||||
('help:update', Help.updateCommand),
|
||||
('author', self.author),
|
||||
('about', self.about),
|
||||
('clear',self.clearScreen),
|
||||
('version', self.versionInfo),
|
||||
('exit', self.exitSession)]
|
||||
|
||||
|
||||
# Path attribute
|
||||
self.path_attrs =['size','type','path','sha','html_url']
|
||||
# Path attribute dictionary
|
||||
self.path_attr_dict = {'size': 'Size (bytes)',
|
||||
'type': 'Type',
|
||||
'path': 'Path',
|
||||
'sha': 'SHA',
|
||||
'html_url': 'URL'}
|
||||
|
||||
|
||||
# Organization attributes
|
||||
self.org_attrs = ['avatar_url','login','id','node_id','email','description','blog','location','followers','following','twitter_username','public_gists','public_repos','type','is_verified','has_organization_projects','has_repository_projects','created_at','updated_at']
|
||||
# Organization attribute dictionary
|
||||
self.org_attr_dict = {'avatar_url': 'Profile Photo',
|
||||
'login': 'Username',
|
||||
'id': 'ID#',
|
||||
'node_id': 'Node ID',
|
||||
'email': 'Email',
|
||||
'description': 'About',
|
||||
'location': 'Location',
|
||||
'blog': 'Blog',
|
||||
'followers': 'Followers',
|
||||
'following': 'Following',
|
||||
'twitter_username': 'Twitter Handle',
|
||||
'public_gists': 'Gists (public)',
|
||||
'public_repos': 'Repositories (public)',
|
||||
'type': 'Account type',
|
||||
'is_verified': 'Is verified?',
|
||||
'has_organization_projects': 'Has organization projects?',
|
||||
'has_repository_projects': 'Has repository projects?',
|
||||
'created_at': 'Created at',
|
||||
'updated_at': 'Updated at'}
|
||||
|
||||
|
||||
# Repository attributes
|
||||
self.repo_attrs = ['id','description','forks','stargazers_count','watchers','license','default_branch','visibility','language','open_issues','topics','homepage','clone_url','ssh_url','fork','allow_forking','private','archived','has_downloads','has_issues','has_pages','has_projects','has_wiki','pushed_at','created_at','updated_at']
|
||||
# Repository attribute dictionary
|
||||
self.repo_attr_dict = {'id': 'ID#',
|
||||
'description': 'About',
|
||||
'forks': 'Forks',
|
||||
'stargazers_count': 'Stars',
|
||||
'watchers': 'Watchers',
|
||||
'license': 'License',
|
||||
'default_branch': 'Branch',
|
||||
'visibility': 'Visibility',
|
||||
'language': 'Language(s)',
|
||||
'open_issues': 'Open issues',
|
||||
'topics': 'Topics',
|
||||
'homepage': 'Homepage',
|
||||
'clone_url': 'Clone URL',
|
||||
'ssh_url': 'SSH URL',
|
||||
'fork': 'Is fork?',
|
||||
'allow_forking': 'Is forkable?',
|
||||
'private': 'Is private?',
|
||||
'archived': 'Is archived?',
|
||||
'is_template': 'Is template?',
|
||||
'has_wiki': 'Has wiki?',
|
||||
'has_pages': 'Has pages?',
|
||||
'has_projects': 'Has projects?',
|
||||
'has_issues': 'Has issues?',
|
||||
'has_downloads': 'Has downloads?',
|
||||
'pushed_at': 'Pushed at',
|
||||
'created_at': 'Created at',
|
||||
'updated_at': 'Updated at'}
|
||||
|
||||
|
||||
# Repo releases attributes
|
||||
self.repo_releases_attrs = ['node_id','tag_name','target_commitish','assets','draft','prerelease','created_at','published_at']
|
||||
# Repo releases attribute dictionary
|
||||
self.repo_releases_attr_dict = {'node_id': 'Node ID',
|
||||
'tag_name': 'Tag',
|
||||
'target_commitish': 'Branch',
|
||||
'assets': 'Assets',
|
||||
'draft': 'Is draft?',
|
||||
'prerelease': 'Is prerelease?',
|
||||
'created_at': 'Created at',
|
||||
'published_at': 'Published at'}
|
||||
|
||||
|
||||
# Profile attributes
|
||||
self.profile_attrs = ['avatar_url','login','id','node_id','bio','blog','location','followers','following','twitter_username','public_gists','public_repos','company','hireable','site_admin','created_at','updated_at']
|
||||
# Profile attribute dictionary
|
||||
self.profile_attr_dict = {'avatar_url': 'Profile Photo',
|
||||
'login': 'Username',
|
||||
'id': 'ID#',
|
||||
'node_id': 'Node ID',
|
||||
'bio': 'Bio',
|
||||
'blog': 'Blog',
|
||||
'location': 'Location',
|
||||
'followers': 'Followers',
|
||||
'following': 'Following',
|
||||
'twitter_username': 'Twitter Handle',
|
||||
'public_gists': 'Gists (public)',
|
||||
'public_repos': 'Repositories (public)',
|
||||
'company': 'Organization',
|
||||
'hireable': 'Is hireable?',
|
||||
'site_admin': 'Is site admin?',
|
||||
'created_at': 'Joined at',
|
||||
'updated_at': 'Updated at'}
|
||||
|
||||
|
||||
# User attributes
|
||||
self.user_attrs = ['avatar_url','id','node_id','gravatar_id','site_admin','type','html_url']
|
||||
# User attribute dictionary
|
||||
self.user_attr_dict = {'avatar_url': 'Profile Photo',
|
||||
'id': 'ID#',
|
||||
'node_id': 'Node ID',
|
||||
'gravatar_id': 'Gravatar ID',
|
||||
'site_admin': 'Is site admin?',
|
||||
'type': 'Account type',
|
||||
'html_url': 'URL'}
|
||||
|
||||
|
||||
# Topic atrributes
|
||||
self.topic_attrs = ['score','curated','featured','display_name','created_by','created_at','updated_at']
|
||||
# Topic attribute dictionary
|
||||
self.topic_attr_dict = {'score': 'Score',
|
||||
'curated': 'Curated',
|
||||
'featured': 'Featured',
|
||||
'display_name': 'Display Name',
|
||||
'created_by': 'Created by',
|
||||
'created_at': 'Created at',
|
||||
'updated_at': 'Updated at'}
|
||||
|
||||
|
||||
# Gists attributes
|
||||
self.gists_attrs = ['node_id','description','comments','files','git_push_url','public','truncated','updated_at']
|
||||
# Gists attribute dictionary
|
||||
self.gists_attr_dict = {'node_id': 'Node ID',
|
||||
'description': 'About',
|
||||
'comments': 'Comments',
|
||||
'files': 'Files',
|
||||
'git_push_url': 'Git Push URL',
|
||||
'public': 'Is public?',
|
||||
'truncated': 'Is truncated?',
|
||||
'updated_at': 'Updated at'}
|
||||
|
||||
|
||||
# Issue attributes
|
||||
self.issue_attrs = ['id','node_id','score','state','number','comments','milestone','assignee','assignees','labels','locked','draft','closed_at','body']
|
||||
# Issue attribute dict
|
||||
self.issue_attr_dict = {'id': 'ID#',
|
||||
'node_id': 'Node ID',
|
||||
'score': 'Score',
|
||||
'state': 'State',
|
||||
'closed_at': 'Closed at',
|
||||
'number': 'Number',
|
||||
'comments': 'Comments',
|
||||
'milestone': 'Milestone',
|
||||
'assignee': 'Assignee',
|
||||
'assignees': 'Assignees',
|
||||
'labels': 'Labels',
|
||||
'draft': 'Is draft?',
|
||||
'locked': 'Is locked?',
|
||||
'created_at': 'Created at',
|
||||
'body': 'Body'}
|
||||
|
||||
|
||||
# User organizations attributes
|
||||
self.user_orgs_attrs = ['avatar_url','id','node_id','url','description']
|
||||
self.user_orgs_attr_dict = {'avatar_url': 'Profile Photo',
|
||||
'id': 'ID#',
|
||||
'node_id': 'Node ID',
|
||||
'url': 'URL',
|
||||
'description': 'About'}
|
||||
|
||||
|
||||
# Author dictionary
|
||||
self.author_dict = {'Alias': 'rly0nheart',
|
||||
'Country': 'Zambia, Africa',
|
||||
'About.me': 'https://about.me/rly0nheart',
|
||||
'BuyMeACoffee': 'https://buymeacoffee.com/189381184'}
|
||||
|
||||
# About dictionary
|
||||
self.about_dict = {'Version': Banner.versionTag,'Category': 'Open Source Intelligence'}
|
||||
|
||||
|
||||
|
||||
def onStart(self):
|
||||
# Log the beginning of a session
|
||||
logging.info(logMsg.sessionStart.format(platform.node(), os.getlogin()))
|
||||
|
||||
# Use 'cls' to clear screen on Windows based machines
|
||||
# Otherwise, use 'clear'
|
||||
while True:
|
||||
command_input = input(f'''\n{Color.white}┌──({Color.red}{os.getlogin()}{Color.white}@{Color.red}octosuite{Color.white})-[{Color.green}{os.getcwd()}{Color.white}]\n└╼[{Color.green}:_{Color.white}]{Color.reset} ''')
|
||||
# Looping through the commands base to check if the user input command matches any command in the commands base, and return its functionality
|
||||
# If no match is found, we ignore it
|
||||
for command, functionality in self.commands_map:
|
||||
if command_input.lower() == command:
|
||||
functionality()
|
||||
else:
|
||||
pass
|
||||
|
||||
|
||||
# Fetching organization info
|
||||
def orgProfile(self):
|
||||
organization = input(f'\n{Color.white}--> @{Color.green}organization{Color.white} (username){Color.reset} ')
|
||||
response = requests.get(f'https://api.github.com/orgs/{organization}')
|
||||
if response.status_code == 404:
|
||||
print(f"{Attributes.negative} Organization {response.json()['message']}{Color.reset}")
|
||||
else:
|
||||
print(f"\n{Color.white}{response.json()['name']}{Color.reset}")
|
||||
for attr in self.org_attrs:
|
||||
print(f'{Color.white}├─ {self.org_attr_dict[attr]}: {Color.green}{response.json()[attr]}{Color.reset}')
|
||||
|
||||
|
||||
# Fetching user information
|
||||
def userProfile(self):
|
||||
username = input(f'\n{Color.white}--> @{Color.green}username{Color.reset} ')
|
||||
response = requests.get(f'https://api.github.com/users/{username}')
|
||||
if response.status_code == 404:
|
||||
print(f"{Attributes.negative} User {response.json()['message']}{Color.reset}")
|
||||
else:
|
||||
print(f"\n{Color.white}{response.json()['name']}{Color.reset}")
|
||||
for attr in self.profile_attrs:
|
||||
print(f'{Color.white}├─ {self.profile_attr_dict[attr]}: {Color.green}{response.json()[attr]}{Color.reset}')
|
||||
|
||||
|
||||
# Fetching repository information
|
||||
def repoProfile(self):
|
||||
repo_name = input(f'\n{Color.white}--> %{Color.green}reponame{Color.reset} ')
|
||||
username = input(f'{Color.white}--> @{Color.green}owner{Color.white} (username){Color.reset} ')
|
||||
response = requests.get(f'https://api.github.com/repos/{username}/{repo_name}')
|
||||
if response.status_code == 404:
|
||||
print(f"{Attributes.negative} Repository or user {response.json()['message']}{Color.reset}")
|
||||
else:
|
||||
print(f"\n{Color.white}{response.json()['full_name']}{Color.reset}")
|
||||
for attr in self.repo_attrs:
|
||||
print(f"{Color.white}├─ {self.repo_attr_dict[attr]}: {Color.green}{response.json()[attr]}{Color.reset}")
|
||||
|
||||
|
||||
# Get path contents
|
||||
def pathContents(self):
|
||||
repo_name = input(f'\n{Color.white}--> %{Color.green}reponame{Color.reset} ')
|
||||
username = input(f'{Color.white}--> @{Color.green}owner{Color.white} (username){Color.reset} ')
|
||||
path_name = input(f'{Color.white}--> ~{Color.green}/path/name{Color.reset} ')
|
||||
response = requests.get(f'https://api.github.com/repos/{username}/{repo_name}/contents/{path_name}')
|
||||
if response.status_code == 404:
|
||||
print(f"{Attributes.negative} Information {response.json()['message']}{Color.reset}")
|
||||
else:
|
||||
for item in response.json():
|
||||
print(f"\n{Color.white}{item['name']}{Color.reset}")
|
||||
for attr in self.path_attrs:
|
||||
print(f'{Color.white}├─ {self.path_attr_dict[attr]}: {Color.green}{item[attr]}{Color.reset}')
|
||||
|
||||
|
||||
# repo contributors
|
||||
def repoContributors(self):
|
||||
repo_name = input(f'\n{Color.white}--> %{Color.green}reponame{Color.reset} ')
|
||||
username = input(f'{Color.white}--> @{Color.green}owner{Color.white} (username){Color.reset} ')
|
||||
response = requests.get(f'https://api.github.com/repos/{username}/{repo_name}/contributors')
|
||||
if response.status_code == 404:
|
||||
print(f"{Attributes.negative} Repository or user {response.json()['message']}{Color.reset}")
|
||||
else:
|
||||
for item in response.json():
|
||||
print(f"\n{Color.white}{item['login']}{Color.reset}")
|
||||
for attr in self.user_attrs:
|
||||
print(f'{Color.white}├─ {self.user_attr_dict[attr]}: {Color.green}{item[attr]}{Color.reset}')
|
||||
|
||||
|
||||
# repo downloads
|
||||
def repoLanguages(self):
|
||||
repo_name = input(f'\n{Color.white}--> %{Color.green}reponame{Color.reset} ')
|
||||
username = input(f'{Color.white}--> @{Color.green}owner{Color.white} (username){Color.reset} ')
|
||||
response = requests.get(f'https://api.github.com/repos/{username}/{repo_name}/languages')
|
||||
if response.status_code == 404:
|
||||
print(f"{Attributes.negative} Repository or user {response.json()['message']}{Color.reset}")
|
||||
elif response.json() == {}:
|
||||
print(f'{Attributes.negative} Repository has no supported language(s){Color.reset}')
|
||||
else:
|
||||
for language in response.json():
|
||||
print(language)
|
||||
|
||||
|
||||
# repo stargazers
|
||||
def repoStargazers(self):
|
||||
repo_name = input(f'\n{Color.white}--> %{Color.green}reponame{Color.reset} ')
|
||||
username = input(f'{Color.white}--> @{Color.green}owner{Color.white} (username){Color.reset} ')
|
||||
response = requests.get(f'https://api.github.com/repos/{username}/{repo_name}/stargazers')
|
||||
if response.status_code == 404:
|
||||
print(f"{Attributes.negative} Repository or user {response.json()['message']}{Color.reset}")
|
||||
elif response.json() == {}:
|
||||
print(f'{Attributes.negative} Repository does not have stargazers.{Color.reset}')
|
||||
else:
|
||||
for item in response.json():
|
||||
print(f"\n{Color.white}{item['login']}{Color.reset}")
|
||||
for attr in self.user_attrs:
|
||||
print(f'{Color.white}├─ {self.user_attr_dict[attr]}: {Color.green}{item[attr]}{Color.reset}')
|
||||
|
||||
|
||||
# repo forks
|
||||
def repoForks(self):
|
||||
repo_name = input(f'\n{Color.white}--> %{Color.green}reponame{Color.reset} ')
|
||||
username = input(f'{Color.white}--> @{Color.green}owner{Color.white} (username){Color.reset} ')
|
||||
response = requests.get(f'https://api.github.com/repos/{username}/{repo_name}/forks')
|
||||
if response.status_code == 404:
|
||||
print(f"{Attributes.negative} Repository or user {response.json()['message']}{Color.reset}")
|
||||
elif response.json() == {}:
|
||||
print(f'{Attributes.negative} Repository does not have forks.{Color.reset}')
|
||||
else:
|
||||
for item in response.json():
|
||||
print(f"\n{Color.white}{item['full_name']}{Color.reset}")
|
||||
for attr in self.repo_attrs:
|
||||
print(f'{Color.white}├─ {self.repo_attr_dict[attr]}: {Color.green}{item[attr]}{Color.reset}')
|
||||
|
||||
|
||||
# Repo releases
|
||||
def repoReleases(self):
|
||||
repo_name = input(f'\n{Color.white}--> %{Color.green}reponame{Color.reset} ')
|
||||
username = input(f'{Color.white}--> @{Color.green}owner{Color.white} (username){Color.reset} ')
|
||||
response = requests.get(f'https://api.github.com/repos/{username}/{repo_name}/releases')
|
||||
if response.status_code == 404:
|
||||
print(f"{Attributes.negative} Repository or user not found{Color.reset}")
|
||||
elif response.json() == []:
|
||||
print(f"\n{Attributes.negative} Repository does not have releases{Color.reset}")
|
||||
else:
|
||||
for item in response.json():
|
||||
print(f"\n{Color.white}{item['name']}{Color.reset}")
|
||||
for attr in self.repo_releases_attrs:
|
||||
print(f'{Color.white}├─ {self.repo_releases_attr_dict[attr]}: {Color.green}{item[attr]}{Color.reset}')
|
||||
print(item['body'])
|
||||
|
||||
|
||||
# Fetching organization repositories
|
||||
def orgRepos(self):
|
||||
organization = input(f'\n{Color.white}--> @{Color.green}organization{Color.white} (username){Color.reset} ')
|
||||
response = requests.get(f'https://api.github.com/orgs/{organization}/repos?per_page=100')
|
||||
if response.status_code == 404:
|
||||
print(f"{Attributes.negative} Organization {response.json()['message']}{Color.reset}")
|
||||
else:
|
||||
for repo in response.json():
|
||||
print(f"\n{Color.white}{repo['full_name']}{Color.reset}")
|
||||
for attr in self.repo_attrs:
|
||||
print(f"{Color.white}├─ {self.repo_attr_dict[attr]}: {Color.green}{repo[attr]}{Color.reset}")
|
||||
|
||||
|
||||
# organization events
|
||||
def orgEvents(self):
|
||||
organization = input(f"\n{Color.white}--> @{Color.green}organization{Color.white} (username){Color.reset} ")
|
||||
response = requests.get(f'https://api.github.com/orgs/{organization}/events')
|
||||
if response.status_code == 404:
|
||||
print(f"{Attributes.negative} Organization {response.json()['message']}{Color.reset}")
|
||||
else:
|
||||
for item in response.json():
|
||||
print(f"\n{Color.white}{item['id']}{Color.reset}")
|
||||
print(f"{Color.white}├─ Type: {Color.green}{item['type']}{Color.reset}\n{Color.white}├─ Created at: {Color.green}{item['created_at']}{Color.green}")
|
||||
pprint(item['payload'])
|
||||
print(f"{Color.reset}\n")
|
||||
|
||||
|
||||
# organization member
|
||||
def orgMember(self):
|
||||
organization = input(f"\n{Color.white}--> @{Color.green}organization{Color.white} (username){Color.reset} ")
|
||||
username = input(f'{Color.white}--> @{Color.green}username{Color.reset} ')
|
||||
response = requests.get(f'https://api.github.com/orgs/{organization}/public_members/{username}')
|
||||
if response.status_code == 204:
|
||||
print(f"{Attributes.positive} User is a public member of the organization{Color.reset}")
|
||||
else:
|
||||
print(f"{Attributes.negative} {response.json()['message']}{Color.reset}")
|
||||
|
||||
|
||||
# Fetching user repositories
|
||||
def userRepos(self):
|
||||
username = input(f'\n{Color.white}--> @{Color.green}username{Color.reset} ')
|
||||
response = requests.get(f'https://api.github.com/users/{username}/repos?per_page=100')
|
||||
if response.status_code == 404:
|
||||
print(f"{Attributes.negative} User {response.json()['message']}{Color.reset}")
|
||||
else:
|
||||
for repo in response.json():
|
||||
print(f"\n{Color.white}{repo['full_name']}{Color.reset}")
|
||||
for attr in self.repo_attrs:
|
||||
print(f"{Color.white}├─ {self.repo_attr_dict[attr]}: {Color.green}{repo[attr]}{Color.reset}")
|
||||
|
||||
|
||||
# Fetching user's gists
|
||||
def userGists(self):
|
||||
username = input(f'\n{Color.white}--> @{Color.green}username{Color.reset} ')
|
||||
response = requests.get(f'https://api.github.com/users/{username}/gists')
|
||||
if response.json() == []:
|
||||
print(f'{Attributes.negative} User does not have any active gists{Color.reset}')
|
||||
elif response.status_code == 404:
|
||||
print(f"{Attributes.negative} User {response.json()['message']}{Color.reset}")
|
||||
else:
|
||||
for item in response.json():
|
||||
print(f"\n{Color.white}{item['id']}{Color.reset}")
|
||||
for attr in self.gists_attrs:
|
||||
print(f"{Color.white}├─ {self.gists_attr_dict[attr]}: {Color.green}{item[attr]}{Color.reset}")
|
||||
|
||||
|
||||
# Fetching a list of organizations that a user owns or belongs to
|
||||
def userOrgs(self):
|
||||
username = input(f'\n{Color.white}--> @{Color.green}username{Color.reset} ')
|
||||
response = requests.get(f'https://api.github.com/users/{username}/orgs')
|
||||
if response.json() == []:
|
||||
print(f'{Attributes.negative} User does not belong to or own any organizations.{Color.reset}')
|
||||
elif response.status_code == 404:
|
||||
print(f"{Attributes.negative} User {response.json()['message']}{Color.reset}")
|
||||
else:
|
||||
for item in response.json():
|
||||
print(f'\n{Color.white}{item["login"]}{Color.reset}')
|
||||
for attr in self.user_orgs_attrs:
|
||||
print(f'{Color.white}├─ {self.user_orgs_attr_dict[attr]}: {Color.green}{item[attr]}{Color.reset}')
|
||||
|
||||
|
||||
# Fetching a users events
|
||||
def userEvents(self):
|
||||
username = input(f'\n{Color.white}--> @{Color.green}username{Color.reset} ')
|
||||
response = requests.get(f'https://api.github.com/users/{username}/events/public')
|
||||
if response.status_code == 404:
|
||||
print(f"{Attributes.negative} User {response.json()['message']}{Color.reset}")
|
||||
else:
|
||||
for item in response.json():
|
||||
print(f"\n{Color.white}{item['id']}{Color.reset}")
|
||||
print(f"{Color.white}├─ Type: {Color.green}{item['type']}{Color.reset}\n{Color.white}├─ Created at: {Color.green}{item['created_at']}{Color.green}")
|
||||
pprint(item['payload'])
|
||||
print(reset)
|
||||
|
||||
|
||||
# Fetching a target user's subscriptions
|
||||
def userSubscriptions(self):
|
||||
username = input(f'\n{Color.white}--> @{Color.green}username{Color.reset} ')
|
||||
response = requests.get(f'https://api.github.com/users/{username}/subscriptions')
|
||||
if response.json() == []:
|
||||
print(f"{Attributes.negative} User does not have any subscriptions.{Color.reset}")
|
||||
elif response.status_code == 404:
|
||||
print(f"{Attributes.negative} User {response.json()['message']}{Color.reset}")
|
||||
else:
|
||||
for item in response.json():
|
||||
print(f"\n{Color.white}{item['full_name']}{Color.reset}")
|
||||
for attr in self.repo_attrs:
|
||||
print(f"{Color.white}├─ {self.repo_attr_dict[attr]}: {Color.green}{item[attr]}{Color.reset}")
|
||||
|
||||
|
||||
# Fetching user's followera'
|
||||
def userFollowers(self):
|
||||
username = input(f'\n{Color.white}--> @{Color.green}username{Color.reset} ')
|
||||
response = requests.get(f'https://api.github.com/users/{username}/followers?per_page=100')
|
||||
if response.json() == []:
|
||||
print(f'{Attributes.negative} User does not have followers.{Color.reset}')
|
||||
elif response.status_code == 404:
|
||||
print(f"{Attributes.negative} User {response.json()['message']}{Color.reset}")
|
||||
else:
|
||||
for item in response.json():
|
||||
print(f"\n{Color.white}@{item['login']}{Color.reset}")
|
||||
for attr in self.user_attrs:
|
||||
print(f"{Color.white}├─ {self.user_attr_dict[attr]}: {Color.green}{item[attr]}{Color.reset}")
|
||||
|
||||
|
||||
# Checking whether or not user[A] follows user[B]
|
||||
def userFollowing(self):
|
||||
user_a = input(f'\n{Color.white}--> @{Color.green}user{Color.white}[A] (username){Color.reset} ')
|
||||
user_b = input(f'{Color.white}--> @{Color.green}user{Color.white}[B] (username){Color.reset} ')
|
||||
response = requests.get(f'https://api.github.com/users/{user_a}/following/{user_b}')
|
||||
if response.status_code == 204:
|
||||
print(f'{Attributes.positive} @{user_a} follows @{user_b}{Color.reset}')
|
||||
else:
|
||||
print(f'{Attributes.negative} @{user_a} does not follow @{user_b}{Color.reset}')
|
||||
|
||||
|
||||
# User search
|
||||
def userSearch(self):
|
||||
query = input(f'\n{Color.white}--> @{Color.green}query{Color.white} (eg. john){Color.reset} ')
|
||||
response = requests.get(f'https://api.github.com/search/users?q={query}&per_page=100').json()
|
||||
for item in response['items']:
|
||||
print(f"\n{Color.white}@{item['login']}{Color.reset}")
|
||||
for attr in self.user_attrs:
|
||||
print(f"{Color.white}├─ {self.user_attr_dict[attr]}: {Color.green}{item[attr]}{Color.reset}")
|
||||
|
||||
|
||||
# Repository search
|
||||
def repoSearch(self):
|
||||
query = input(f'\n{Color.white}--> %{Color.green}query{Color.white} (eg. git){Color.reset} ')
|
||||
response = requests.get(f'https://api.github.com/search/repositories?q={query}&per_page=100').json()
|
||||
for item in response['items']:
|
||||
print(f"\n{Color.white}{item['full_name']}{Color.reset}")
|
||||
for attr in self.repo_attrs:
|
||||
print(f"{Color.white}├─ {self.repo_attr_dict[attr]}: {Color.green}{item[attr]}{Color.reset}")
|
||||
|
||||
|
||||
# Topics search
|
||||
def topicSearch(self):
|
||||
query = input(f'\n{Color.white}--> #{Color.green}query{Color.white} (eg. osint){Color.reset} ')
|
||||
response = requests.get(f'https://api.github.com/search/topics?q={query}&per_page=100').json()
|
||||
for item in response['items']:
|
||||
print(f"\n{Color.white}{item['name']}{Color.reset}")
|
||||
for attr in self.topic_attrs:
|
||||
print(f"{Color.white}├─ {self.topic_attr_dict[attr]}: {Color.green}{item[attr]}{Color.reset}")
|
||||
|
||||
|
||||
# Issue search
|
||||
def issueSearch(self):
|
||||
query = input(f'\n{Color.white}--> !{Color.green}query{Color.white} (eg. error){Color.reset} ')
|
||||
response = requests.get(f'https://api.github.com/search/issues?q={query}&per_page=100').json()
|
||||
for item in response['items']:
|
||||
print(f"\n{Color.white}{item['title']}{Color.reset}")
|
||||
for attr in self.issue_attrs:
|
||||
print(f"{Color.white}├─ {self.issue_attr_dict[attr]}: {Color.green}{item[attr]}{Color.reset}")
|
||||
|
||||
|
||||
# Commits search
|
||||
def commitsSearch(self):
|
||||
query = input(f'\n{Color.white}--> :{Color.green}query{Color.white} (eg. filename:index.php){Color.reset} ')
|
||||
response = requests.get(f'https://api.github.com/search/commits?q={query}&per_page=100').json()
|
||||
number=0
|
||||
for item in response['items']:
|
||||
number+=1
|
||||
print(f'\n{Color.white}-> {number}.{Color.reset}')
|
||||
pprint(item['commit'])
|
||||
|
||||
|
||||
# View octosuite log files
|
||||
def viewLogs(self):
|
||||
logging.info(logMsg.viewingLogs)
|
||||
logs = os.listdir('.logs')
|
||||
print(f'''\n{Color.white}Log Size{Color.reset}
|
||||
--- ---------''')
|
||||
for log in logs:
|
||||
print(f"{log}\t ",os.path.getsize(".logs/"+log),"bytes")
|
||||
|
||||
|
||||
# Delete a specified log file
|
||||
def deleteLog(self):
|
||||
log_file = input(f"\n{Color.white}--> logfile (eg. 2022-04-27 10:09:36AM.log){Color.reset} ")
|
||||
if sys.platform.lower().startswith(('win','darwin')):
|
||||
subprocess.run(['del',f'{os.getcwd()}/.logs/{log_file}'])
|
||||
else:
|
||||
subprocess.run(['sudo','rm',f'.logs/{log_file}'],shell=False)
|
||||
|
||||
logging.info(logMsg.fileDeleted.format(log_file))
|
||||
print(Attributes.positive, logMsg.fileDeleted)
|
||||
|
||||
|
||||
# Read a specified log file
|
||||
def readLog(self):
|
||||
log_file = input(f"\n{Color.white}--> logfile (eg. 2022-04-27 10:09:36AM.log){Color.reset} ")
|
||||
with open(f'.logs/{log_file}', 'r') as log:
|
||||
logging.info(logMsg.readingFile.format(log_file))
|
||||
print("\n"+log.read())
|
||||
|
||||
|
||||
# Update program
|
||||
def installUpdate(self):
|
||||
files_to_update = ['core/main.py','utils/helper.py','utils/misc.py','utils/colors.py','utils/changelog.py','octosuite','.github/dependabot.yml','.github/ISSUE_TEMPLATE/bug_report.md','.github/ISSUE_TEMPLATE/feature_request.md','.github/ISSUE_TEMPLATE/config.yml','LICENSE','README.md','requirements.txt']
|
||||
logging.info(logMsg.installingUpdates)
|
||||
for file in tqdm(files_to_update,desc = logMsg.installingUpdates):
|
||||
data = requests.get(f'https://raw.githubusercontent.com/rly0nheart/octosuite/master/{file}')
|
||||
with open(file, 'wb') as code:
|
||||
code.write(data.content)
|
||||
code.close()
|
||||
|
||||
logging.info(logMsg.installedUpdates.format(len(files_to_update)))
|
||||
print(Attributes.positive, logMsg.installedUpdates.format(len(files_to_update)));exit()
|
||||
|
||||
|
||||
def checkUpdate(self):
|
||||
logging.info(logMsg.checkingUpdates)
|
||||
response = requests.get("https://api.github.com/repos/rly0nheart/octosuite/releases/latest")
|
||||
if response.json()['tag_name'] == Banner.versionTag:
|
||||
print(f"{Attributes.positive} OctoSuite is up to date. Check again soon :)")
|
||||
else:
|
||||
print(f"{Attributes.info} A new release is available ({response.json()['tag_name']}). Use command {Color.green}update:install{Color.white} to download and install the updates.{Color.reset}")
|
||||
|
||||
|
||||
# Show version information
|
||||
def versionInfo(self):
|
||||
# Yes... the changelog is actually hard coded
|
||||
# It's actually frustrating having to change this everytime I publish a new release lol
|
||||
print(f'''
|
||||
Tag: {Banner.versionTag}
|
||||
Released at: 2022-05-20 03:26AM
|
||||
{'-'*31}
|
||||
|
||||
What's changed?
|
||||
{'-'*15}
|
||||
[✓] The PyPI package has been deprecated and will no longer receive any further updates
|
||||
[✓] Added a functionality for returning organizations belonging to a target user (user:orgs)
|
||||
[✓] Added a functionality for returning a target user's subscriptions (user:subscriptions)
|
||||
[✓] Added a functionality for returning a target user's events (user:events)
|
||||
[✓] Added a functionality for returning a list of contributors of a repository (repo:contributors)
|
||||
[✓] Added a functionality for returning languages of a repository (repo:languages)
|
||||
[✓] Added a functionality for returning stargazers of a repository (repo:stargazers)
|
||||
[✓] Added a functionality for returning forks of a repository (repo:forks)
|
||||
[✓] Added a functionality for checking for latest releases of Octosuite (update:check)
|
||||
[✓] Added the "clear" command for clearing the screen in the Octosuite command prompt
|
||||
[✓] Moved the use of the 'update' comnand to 'update:install'
|
||||
[✓] Will no longer show the "Press any key to continue" prompt, it will instead return its command prompt
|
||||
[✓] Commands are no longer case sensitive
|
||||
[✓] Major perfomance improvements''')
|
||||
|
||||
|
||||
# Author info
|
||||
def author(self):
|
||||
print(f'\n{Color.white}Richard Mwewa (Ritchie){Color.reset}')
|
||||
for key,value in self.author_dict.items():
|
||||
print(f'{Color.white}├─ {key}: {Color.green}{value}{Color.reset}')
|
||||
|
||||
|
||||
def about(self):
|
||||
print('''
|
||||
OCTOSUITE (C) 2022 Richard Mwewa
|
||||
|
||||
is an advanced and lightning fast framework for gathering open-source intelligence on GitHub users and organizations.''')
|
||||
|
||||
|
||||
# Close session
|
||||
def exitSession(self):
|
||||
logging.info(logMsg.sessionClosed)
|
||||
print(Attributes.info, logMsg.sessionClosed);exit()
|
||||
|
||||
|
||||
def clearScreen(self):
|
||||
if sys.platform.lower().startswith(('win','darwin')):
|
||||
subprocess.run(['cls'])
|
||||
else:
|
||||
subprocess.run(['clear'],shell=False)
|
||||
542
src/main.py
542
src/main.py
@@ -1,542 +0,0 @@
|
||||
'''
|
||||
octosuite Advanced Github OSINT Framework
|
||||
Copyright (C) 2022 Richard Mwewa
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
'''
|
||||
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import requests
|
||||
import platform
|
||||
import subprocess
|
||||
from tqdm import tqdm
|
||||
from pprint import pprint
|
||||
from datetime import datetime
|
||||
from lib.banner import banner, version
|
||||
from lib.colors import red, white, green, red_bg, reset
|
||||
|
||||
class octosuite:
|
||||
def __init__(self):
|
||||
# A list of tuples, mapping commands to their respective functionalities
|
||||
self.commands_base = [('info:org', self.org_info),
|
||||
('info:user', self.user_profile),
|
||||
('info:repo', self.repo_info),
|
||||
('path:contents', self.path_contents),
|
||||
('repos:org', self.org_repos),
|
||||
('repos:user', self.user_repos),
|
||||
('user:gists', self.user_gists),
|
||||
('user:followers', self.followers),
|
||||
('user:following', self.following),
|
||||
('search:users', self.user_search),
|
||||
('search:repos', self.repo_search),
|
||||
('search:topics', self.topic_search),
|
||||
('search:issues', self.issue_search),
|
||||
('search:commits', self.commits_search),
|
||||
('logs:view',self.view_logs),
|
||||
('logs:read',self.read_log),
|
||||
('logs:delete',self.delete_log),
|
||||
('update', self.update),
|
||||
('changelog', self.changelog),
|
||||
('info:dev', self.author),
|
||||
('help', self.help),
|
||||
('exit', self.exit_session)]
|
||||
|
||||
|
||||
# Path attribute
|
||||
self.path_attrs =['size','type','path','sha','html_url']
|
||||
# Path attribute dictionary
|
||||
self.path_attr_dict = {'size': 'Size (bytes)',
|
||||
'type': 'Type',
|
||||
'path': 'Path',
|
||||
'sha': 'SHA',
|
||||
'html_url': 'URL'}
|
||||
|
||||
|
||||
# Organization attributes
|
||||
self.org_attrs = ['avatar_url','login','id','node_id','email','description','blog','location','followers','following','twitter_username','public_gists','public_repos','type','is_verified','has_organization_projects','has_repository_projects','created_at','updated_at']
|
||||
# Organization attribute dictionary
|
||||
self.org_attr_dict = {'avatar_url': 'Profile Photo',
|
||||
'login': 'Username',
|
||||
'id': 'ID#',
|
||||
'node_id': 'Node ID',
|
||||
'email': 'Email',
|
||||
'description': 'About',
|
||||
'location': 'Location',
|
||||
'blog': 'Blog',
|
||||
'followers': 'Followers',
|
||||
'following': 'Following',
|
||||
'twitter_username': 'Twitter Handle',
|
||||
'public_gists': 'Gists (public)',
|
||||
'public_repos': 'Repositories (public)',
|
||||
'type': 'Account type',
|
||||
'is_verified': 'Is verified?',
|
||||
'has_organization_projects': 'Has organization projects?',
|
||||
'has_repository_projects': 'Has repository projects?',
|
||||
'created_at': 'Created at',
|
||||
'updated_at': 'Updated at'}
|
||||
|
||||
|
||||
# Repository attributes
|
||||
self.repo_attrs = ['id','description','forks','allow_forking','fork','stargazers_count','watchers','license','default_branch','visibility','language','open_issues','topics','homepage','clone_url','ssh_url','private','archived','has_downloads','has_issues','has_pages','has_projects','has_wiki','pushed_at','created_at','updated_at']
|
||||
# Repository attribute dictionary
|
||||
self.repo_attr_dict = {'id': 'ID#',
|
||||
'description': 'About',
|
||||
'forks': 'Forks',
|
||||
'allow_forking': 'Is forkable?',
|
||||
'fork': 'Is fork?',
|
||||
'stargazers_count': 'Stars',
|
||||
'watchers': 'Watchers',
|
||||
'license': 'License',
|
||||
'default_branch': 'Branch',
|
||||
'visibility': 'Visibility',
|
||||
'language': 'Language(s)',
|
||||
'open_issues': 'Open issues',
|
||||
'topics': 'Topics',
|
||||
'homepage': 'Homepage',
|
||||
'clone_url': 'Clone URL',
|
||||
'ssh_url': 'SSH URL',
|
||||
'private': 'Is private?',
|
||||
'archived': 'Is archived?',
|
||||
'is_template': 'Is template?',
|
||||
'has_wiki': 'Has wiki?',
|
||||
'has_pages': 'Has pages?',
|
||||
'has_projects': 'Has projects?',
|
||||
'has_issues': 'Has issues?',
|
||||
'has_downloads': 'Has downloads?',
|
||||
'pushed_at': 'Pushed at',
|
||||
'created_at': 'Created at',
|
||||
'updated_at': 'Updated at'}
|
||||
|
||||
|
||||
# Profile attributes
|
||||
self.profile_attrs = ['avatar_url','login','id','node_id','bio','blog','location','followers','following','twitter_username','public_gists','public_repos','company','hireable','site_admin','created_at','updated_at']
|
||||
# Profile attribute dictionary
|
||||
self.profile_attr_dict = {'avatar_url': 'Profile Photo',
|
||||
'login': 'Username',
|
||||
'id': 'ID#',
|
||||
'node_id': 'Node ID',
|
||||
'bio': 'Bio',
|
||||
'blog': 'Blog',
|
||||
'location': 'Location',
|
||||
'followers': 'Followers',
|
||||
'following': 'Following',
|
||||
'twitter_username': 'Twitter Handle',
|
||||
'public_gists': 'Gists (public)',
|
||||
'public_repos': 'Repositories (public)',
|
||||
'company': 'Organization',
|
||||
'hireable': 'Is hireable?',
|
||||
'site_admin': 'Is site admin?',
|
||||
'created_at': 'Joined at',
|
||||
'updated_at': 'Updated at'}
|
||||
|
||||
|
||||
# User attributes
|
||||
self.user_attrs = ['avatar_url','id','node_id','gravatar_id','site_admin','type','html_url']
|
||||
# User attribute dictionary
|
||||
self.user_attr_dict = {'avatar_url': 'Profile Photo',
|
||||
'id': 'ID#',
|
||||
'node_id': 'Node ID',
|
||||
'gravatar_id': 'Gravatar ID',
|
||||
'site_admin': 'Is site admin?',
|
||||
'type': 'Account type',
|
||||
'html_url': 'URL'}
|
||||
|
||||
|
||||
# Topic atrributes
|
||||
self.topic_attrs = ['score','curated','featured','display_name','created_by','created_at','updated_at']
|
||||
# Topic attribute dictionary
|
||||
self.topic_attr_dict = {'score': 'Score',
|
||||
'curated': 'Curated',
|
||||
'featured': 'Featured',
|
||||
'display_name': 'Display Name',
|
||||
'created_by': 'Created by',
|
||||
'created_at': 'Created at',
|
||||
'updated_at': 'Updated at'}
|
||||
|
||||
|
||||
# Gists attributes
|
||||
self.gists_attrs = ['node_id','description','comments','files','git_push_url','public','truncated','updated_at']
|
||||
# Gists attribute dictionary
|
||||
self.gists_attr_dict = {'node_id': 'Node ID',
|
||||
'description': 'About',
|
||||
'comments': 'Comments',
|
||||
'files': 'Files',
|
||||
'git_push_url': 'Git Push URL',
|
||||
'public': 'Is public?',
|
||||
'truncated': 'Is truncated?',
|
||||
'updated_at': 'Updated at'}
|
||||
|
||||
|
||||
# Issue attributes
|
||||
self.issue_attrs = ['id','node_id','score','state','number','comments','milestone','assignee','assignees','labels','locked','draft','closed_at','body']
|
||||
# Issue attribute dict
|
||||
self.issue_attr_dict = {'id': 'ID#',
|
||||
'node_id': 'Node ID',
|
||||
'score': 'Score',
|
||||
'state': 'State',
|
||||
'closed_at': 'Closed at',
|
||||
'number': 'Number',
|
||||
'comments': 'Comments',
|
||||
'milestone': 'Milestone',
|
||||
'assignee': 'Assignee',
|
||||
'assignees': 'Assignees',
|
||||
'labels': 'Labels',
|
||||
'draft': 'Is draft?',
|
||||
'locked': 'Is locked?',
|
||||
'created_at': 'Created at',
|
||||
'body': 'Body'}
|
||||
|
||||
|
||||
# Author dictionary
|
||||
self.author_dict = {'Alias': 'rly0nheart',
|
||||
'Country': 'Zambia, Africa',
|
||||
'About.me': 'https://about.me/rly0nheart',
|
||||
'BuyMeACoffee': 'https://buymeacoffee.com/189381184'}
|
||||
|
||||
|
||||
def on_start(self):
|
||||
# Start new session
|
||||
logging.info(f'Started new session on {platform.node()}:{os.getlogin()}')
|
||||
|
||||
# Use 'cls' to clear screen on Windows based machines
|
||||
# Otherwise, use 'clear'
|
||||
while True:
|
||||
if sys.platform.lower().startswith(('win','darwin')):
|
||||
subprocess.run(['cls'])
|
||||
else:
|
||||
subprocess.run(['clear'],shell=False)
|
||||
|
||||
print(banner)
|
||||
command_input = input(f'''{white}┌──({red}{os.getlogin()}{white}@{red}octosuite{white})-[{green}{os.getcwd()}{white}]\n└╼[{green}:~{white}]{reset} ''')
|
||||
# Looping through the commands base to check if the user input command matches any command in the commands base, and return its functionality
|
||||
# If no match is found, we ignore it
|
||||
for command, functionality in self.commands_base:
|
||||
if command == command_input:
|
||||
functionality()
|
||||
else:
|
||||
pass
|
||||
|
||||
input(f'\n{white}[{green} ? {white}] Press any key to continue{reset} ')
|
||||
|
||||
# Fetching organization info
|
||||
def org_info(self):
|
||||
organization = input(f'\n{white}--> @{green}organization{white} (username){reset} ')
|
||||
api = f'https://api.github.com/orgs/{organization}'
|
||||
response = requests.get(api)
|
||||
if response.status_code != 200:
|
||||
print(f'\n{white}[{red} - {white}] Organization ({organization}) not found.{reset}')
|
||||
else:
|
||||
response = response.json()
|
||||
print(f"\n{white}{response['name']}{reset}")
|
||||
for attr in self.org_attrs:
|
||||
print(f'{white}├─ {self.org_attr_dict[attr]}: {green}{response[attr]}{reset}')
|
||||
|
||||
|
||||
# Fetching user information
|
||||
def user_profile(self):
|
||||
username = input(f'\n{white}--> @{green}username{reset} ')
|
||||
api = f'https://api.github.com/users/{username}'
|
||||
response = requests.get(api)
|
||||
if response.status_code != 200:
|
||||
print(f'\n{white}[{red} - {white}] User ({username}) not found.{reset}')
|
||||
else:
|
||||
response = response.json()
|
||||
print(f"\n{white}{response['name']}{reset}")
|
||||
for attr in self.profile_attrs:
|
||||
print(f'{white}├─ {self.profile_attr_dict[attr]}: {green}{response[attr]}{reset}')
|
||||
|
||||
|
||||
# Fetching repository information
|
||||
def repo_info(self):
|
||||
repo_name = input(f'\n{white}--> %{green}reponame{reset} ')
|
||||
username = input(f'{white}--> @{green}owner{white} (username){reset} ')
|
||||
api = f'https://api.github.com/repos/{username}/{repo_name}'
|
||||
response = requests.get(api)
|
||||
if response.status_code != 200:
|
||||
print(f'\n{white}[{red} - {white}] Repository ({repo_name}) or user ({username}) not found.{reset}')
|
||||
else:
|
||||
response = response.json()
|
||||
print(f"\n{white}{response['full_name']}{reset}")
|
||||
for attr in self.repo_attrs:
|
||||
print(f"{white}├─ {self.repo_attr_dict[attr]}: {green}{response[attr]}{reset}")
|
||||
|
||||
|
||||
# Get path contents
|
||||
def path_contents(self):
|
||||
repo_name = input(f'\n{white}--> %{green}reponame{reset} ')
|
||||
username = input(f'{white}--> @{green}owner{white} (username){reset} ')
|
||||
path_name = input(f'{white}--> ~{green}/path/name{reset} ')
|
||||
api = f'https://api.github.com/repos/{username}/{repo_name}/contents/{path_name}'
|
||||
response = requests.get(api)
|
||||
if response.status_code != 200:
|
||||
print(f'\n{white}[{red} - {white}] Information not found.{reset}')
|
||||
else:
|
||||
response = response.json()
|
||||
for item in response:
|
||||
print(f"\n{white}{item['name']}{reset}")
|
||||
for attr in self.path_attrs:
|
||||
print(f'{white}├─ {self.path_attr_dict[attr]}: {green}{item[attr]}{reset}')
|
||||
|
||||
|
||||
# Fetching organization repositories
|
||||
def org_repos(self):
|
||||
organization = input(f'\n{white}--> @{green}organization{white} (username){reset} ')
|
||||
api = f'https://api.github.com/orgs/{organization}/repos?per_page=100'
|
||||
response = requests.get(api)
|
||||
if response.status_code != 200:
|
||||
print(f'\n{white}[{red} - {white}] Organization ({organization}) not found.{reset}')
|
||||
else:
|
||||
response = response.json()
|
||||
for repo in response:
|
||||
print(f"\n{white}{repo['full_name']}{reset}")
|
||||
for attr in self.repo_attrs:
|
||||
print(f"{white}├─ {self.repo_attr_dict[attr]}: {green}{repo[attr]}{reset}")
|
||||
print('\n')
|
||||
|
||||
|
||||
# Fetching user repositories
|
||||
def user_repos(self):
|
||||
username = input(f'\n{white}--> @{green}username{reset} ')
|
||||
api = f'https://api.github.com/users/{username}/repos?per_page=100'
|
||||
response = requests.get(api)
|
||||
if response.status_code != 200:
|
||||
print(f'\n{white}[{red} - {white}] User ({username}) not found.{reset}')
|
||||
else:
|
||||
response = response.json()
|
||||
for repo in response:
|
||||
print(f"\n{white}{repo['full_name']}{reset}")
|
||||
for attr in self.repo_attrs:
|
||||
print(f"{white}├─ {self.repo_attr_dict[attr]}: {green}{repo[attr]}{reset}")
|
||||
print('\n')
|
||||
|
||||
|
||||
# Fetching user's gists
|
||||
def user_gists(self):
|
||||
username = input(f'\n{white}--> @{green}username{reset} ')
|
||||
api = f'https://api.github.com/users/{username}/gists'
|
||||
response = requests.get(api).json()
|
||||
if response == []:
|
||||
print(f'\n{white}[{red} - {white}] User ({username}) does not have any active gists.{reset}')
|
||||
elif "not found" in response['message']:
|
||||
print(f'\n{white}[{red} - {white}] User ({username}) not found.{reset}')
|
||||
else:
|
||||
for item in response:
|
||||
print(f"\n{white}{item['id']}{reset}")
|
||||
for attr in self.gists_attrs:
|
||||
print(f"{white}├─ {self.gists_attr_dict[attr]}: {green}{item[attr]}{reset}")
|
||||
print('\n')
|
||||
|
||||
|
||||
# Fetching user's followera'
|
||||
def followers(self):
|
||||
username = input(f'\n{white}--> @{green}username{reset} ')
|
||||
api = f'https://api.github.com/users/{username}/followers?per_page=100'
|
||||
response = requests.get(api).json()
|
||||
if response == []:
|
||||
print(f'\n{white}[{red} - {white}]User ({username}) does not have followers.{reset}')
|
||||
elif "not found" in response['message']:
|
||||
print(f'\n{white}[{red} - {white}] User ({username}) not found.{reset}')
|
||||
else:
|
||||
for item in response:
|
||||
print(f"\n{white}@{item['login']}{reset}")
|
||||
for attr in self.user_attrs:
|
||||
print(f"{white}├─ {self.user_attr_dict[attr]}: {green}{item[attr]}{reset}")
|
||||
print('\n')
|
||||
|
||||
|
||||
# Checking whether or not user[A] follows user[B]
|
||||
def following(self):
|
||||
user_a = input(f'\n{white}--> @{green}user{white}[A] (username){reset} ')
|
||||
user_b = input(f'{white}--> @{green}user{white}[B] (username){reset} ')
|
||||
api = f'https://api.github.com/users/{user_a}/following/{user_b}'
|
||||
response = requests.get(api)
|
||||
if response.status_code == 204:
|
||||
print(f'\n{white}[{green} + {white}] @{user_a} follows @{user_b}.{reset}')
|
||||
else:
|
||||
print(f'\n{white}[{red} - {white}] @{user_a} does not follow @{user_b}.{reset}')
|
||||
|
||||
|
||||
# User search
|
||||
def user_search(self):
|
||||
query = input(f'\n{white}--> @{green}query{white} (eg. john){reset} ')
|
||||
api = f'https://api.github.com/search/users?q={query}&per_page=100'
|
||||
response = requests.get(api).json()
|
||||
for item in response['items']:
|
||||
print(f"\n{white}@{item['login']}{reset}")
|
||||
for attr in self.user_attrs:
|
||||
print(f"{white}├─ {self.user_attr_dict[attr]}: {green}{item[attr]}{reset}")
|
||||
print('\n')
|
||||
|
||||
|
||||
# Repository search
|
||||
def repo_search(self):
|
||||
query = input(f'\n{white}--> %{green}query{white} (eg. git){reset} ')
|
||||
api = f'https://api.github.com/search/repositories?q={query}&per_page=100'
|
||||
response = requests.get(api).json()
|
||||
for item in response['items']:
|
||||
print(f"\n{white}{item['full_name']}{reset}")
|
||||
for attr in self.repo_attrs:
|
||||
print(f"{white}├─ {self.repo_attr_dict[attr]}: {green}{item[attr]}{reset}")
|
||||
print('\n')
|
||||
|
||||
|
||||
# Topics search
|
||||
def topic_search(self):
|
||||
query = input(f'\n{white}--> #{green}query{white} (eg. osint){reset} ')
|
||||
api = f'https://api.github.com/search/topics?q={query}&per_page=100'
|
||||
response = requests.get(api).json()
|
||||
for item in response['items']:
|
||||
print(f"\n{white}{item['name']}{reset}")
|
||||
for attr in self.topic_attrs:
|
||||
print(f"{white}├─ {self.topic_attr_dict[attr]}: {green}{item[attr]}{reset}")
|
||||
print('\n')
|
||||
|
||||
|
||||
# Issue search
|
||||
def issue_search(self):
|
||||
query = input(f'\n{white}--> !{green}query{white} (eg. error){reset} ')
|
||||
api = f'https://api.github.com/search/issues?q={query}&per_page=100'
|
||||
response = requests.get(api).json()
|
||||
for item in response['items']:
|
||||
print(f"\n{white}{item['title']}{reset}")
|
||||
for attr in self.issue_attrs:
|
||||
print(f"{white}├─ {self.issue_attr_dict[attr]}: {green}{item[attr]}{reset}")
|
||||
print('\n')
|
||||
|
||||
|
||||
# Commits search
|
||||
def commits_search(self):
|
||||
query = input(f'\n{white}--> :{green}query{white} (eg. filename:index.php){reset} ')
|
||||
api = f'https://api.github.com/search/commits?q={query}&per_page=100'
|
||||
response = requests.get(api).json()
|
||||
number=0
|
||||
for item in response['items']:
|
||||
number+=1
|
||||
print(f'\n{white}-> {number}.{reset}')
|
||||
pprint(item['commit'])
|
||||
print('\n')
|
||||
|
||||
|
||||
# View octosuite log files
|
||||
def view_logs(self):
|
||||
logs = os.listdir('.logs')
|
||||
print(f"\n {red_bg}[LOG] [SIZE] {reset}")
|
||||
for log in logs:
|
||||
print(f" {log}\t ",os.path.getsize(".logs/"+log),"bytes")
|
||||
print(f" {red_bg} {reset}")
|
||||
|
||||
|
||||
# Delete a specified log file
|
||||
def delete_log(self):
|
||||
log_file = input(f"\n{white}--> logfile (eg. 2022-04-27 10:09:36.068312.log){reset} ")
|
||||
if sys.platform.lower().startswith(('win','darwin')):
|
||||
subprocess.run(['del',f'{os.getcwd()}/.logs/{log_file}'])
|
||||
else:
|
||||
subprocess.run(['sudo','rm',f'.logs/{log_file}'],shell=False)
|
||||
|
||||
logging.info(f'Deleted log file: {log_file}')
|
||||
print(f"{white}[{green} + {white}] Deleted log file: {green}{log_file}{reset}")
|
||||
|
||||
|
||||
# Read a specified log file
|
||||
def read_log(self):
|
||||
log_file = input(f"\n{white}--> logfile (eg. 2022-04-27 10:09:36.068312.log){reset} ")
|
||||
with open(f'.logs/{log_file}', 'r') as log:
|
||||
logging.info(f'Reading log file: {log_file}')
|
||||
print("\n"+log.read())
|
||||
|
||||
|
||||
# Update program
|
||||
def update(self):
|
||||
logging.info('Updating...')
|
||||
files_to_update = ['src/main.py','lib/banner.py','lib/colors.py','octosuite','.github/dependabot.yml','.github/ISSUE_TEMPLATE/bug_report.md','.github/ISSUE_TEMPLATE/feature_request.md','.github/ISSUE_TEMPLATE/config.yml','LICENSE','README.md','requirements.txt']
|
||||
for file in tqdm(files_to_update,desc=f'{white}[{green} * {white}] Updating{reset}'):
|
||||
data = requests.get(f'https://raw.githubusercontent.com/rly0nheart/octosuite/master/{file}')
|
||||
with open(file, 'wb') as code:
|
||||
code.write(data.content)
|
||||
code.close()
|
||||
|
||||
logging.info('Update complete.')
|
||||
exit(f'{white}[{green} + {white}] Update complete. Re-run octosuite.{reset}')
|
||||
|
||||
|
||||
# Show changelog
|
||||
def changelog(self):
|
||||
# lol yes the changelog is hard coded
|
||||
changelog_text = f'''
|
||||
|
||||
{red_bg} {version} [CHANGELOG] {reset}
|
||||
• Minor bug fixes and improvements
|
||||
{red_bg} {reset}'''
|
||||
print(changelog_text)
|
||||
|
||||
|
||||
# Author info
|
||||
def author(self):
|
||||
print(f'\n{white}Richard Mwewa (Ritchie){reset}')
|
||||
for key,value in self.author_dict.items():
|
||||
print(f'{white}├─ {key}: {green}{value}{reset}')
|
||||
|
||||
|
||||
# Close session
|
||||
def exit_session(self):
|
||||
logging.info('Session closed with (exit) command.')
|
||||
exit(f'\n{white}[{green} ! {white}] Session closed with ({green}exit{reset}{white}) command.{reset}')
|
||||
|
||||
|
||||
# Help/usage
|
||||
def help(self):
|
||||
help = f'''
|
||||
|
||||
{red_bg}[COMMAND] [DESCRIPTION] {reset}
|
||||
info:org Get target organization info
|
||||
info:user Get target user profile info
|
||||
info:repo Get target repository info
|
||||
info:dev Show developer's info
|
||||
path:contents Get contents of a specified path from a target repository
|
||||
repos:org Get a list of repositories owned by a target organization
|
||||
repos:user Get a list of repositories owned by a target user
|
||||
user:gists Get a list of gists owned by a target user
|
||||
user:followers Get a list of the target's followers
|
||||
user:following Check whether or not User[A] follows User[B]
|
||||
search:users Search user(s)
|
||||
search:repos Search repositor[y][ies]
|
||||
search:topics Search topic(s)
|
||||
search:issues Search issue(s)
|
||||
search:commits Search commit(s)
|
||||
logs:view View log files
|
||||
logs:read Read a specified log file
|
||||
logs:delete Delete a specified log file
|
||||
update Update octosuite
|
||||
changelog Show changelog
|
||||
help Show usage/help
|
||||
exit Exit session
|
||||
{red_bg} {reset}'''
|
||||
print(help)
|
||||
|
||||
|
||||
# If .logs folder exists, pass
|
||||
if os.path.exists('.logs'):
|
||||
pass
|
||||
|
||||
else:
|
||||
# Creating the .logs directory
|
||||
# If the current system is Windows based, we run mkdir command without sudo
|
||||
# Else we run the mkdir command with sudo
|
||||
if sys.platform.lower().startswith(('win','darwin')):
|
||||
subprocess.run(['mkdir','.logs'])
|
||||
else:
|
||||
subprocess.run(['sudo','mkdir','.logs'],shell=False)
|
||||
|
||||
# Set to automatically monitor and log network and user activity into the .logs folder
|
||||
logging.basicConfig(filename=f'.logs/{datetime.now()}.log',format='[%(asctime)s] [%(levelname)s] %(message)s',datefmt='%Y-%m-%d %H:%M:%S%p',level=logging.DEBUG)
|
||||
Reference in New Issue
Block a user