mirror of
https://github.com/bellingcat/octosuite.git
synced 2026-06-10 20:38:34 +03:00
Compare commits
15 Commits
1.9.0
...
2.0.0-beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8de7b0d939 | ||
|
|
6afc37d9a4 | ||
|
|
b030d48152 | ||
|
|
508df0ea77 | ||
|
|
fb7ad2cda8 | ||
|
|
b16eb762e0 | ||
|
|
8b7ac9e428 | ||
|
|
66e0da38b6 | ||
|
|
fbf7e40404 | ||
|
|
73bcb68012 | ||
|
|
850d9bb5ab | ||
|
|
17cfb5920c | ||
|
|
5ffb7758a9 | ||
|
|
2d6a808100 | ||
|
|
02feed27a8 |
82
README.md
82
README.md
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
@@ -6,7 +6,6 @@
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
> *Simply gather OSINT on Github users & organizations like a God🔥*
|
||||
@@ -16,75 +15,64 @@ Installation instructions are on the wiki, in addition to all other documentatio
|
||||
|
||||
[Refer to the Wiki](https://github.com/rly0nheart/octosuite/wiki)
|
||||
|
||||
***
|
||||
|
||||
# FEATURES
|
||||
- [x] Fetches organization info
|
||||
- [x] Fetches user info
|
||||
- [x] Fetches repository info
|
||||
- [x] Returns contents of a path from a repository
|
||||
- [x] Returns a list of repos owned by an organization
|
||||
- [x] Returns a list of repos owned by a user
|
||||
- [x] Returns a list of gists owned by a user
|
||||
- [x] Returns a list of a user's followers
|
||||
- [x] Checks whether user A follows user B
|
||||
- [x] Fetches an organization's profile information
|
||||
- [x] Fetches an oganization's events
|
||||
- [x] Returns an organization's repositories
|
||||
- [x] Returns an organization's public members
|
||||
- [x] Fetches a repository's information
|
||||
- [x] Returns a repository's contributors
|
||||
- [x] Returns a repository's languages
|
||||
- [x] Fetches a repository's stargazers
|
||||
- [x] Fetches a repository's forks
|
||||
- [x] Fetches a repository's releases
|
||||
- [x] Returns a list of files in a specified path of a repository
|
||||
- [x] Fetches a user's profile information
|
||||
- [x] Returns a user's gists
|
||||
- [x] Returns organizations that a user owns/belongs to
|
||||
- [x] Fetches a user's events
|
||||
- [x] Fetches a user's followers
|
||||
- [x] Checks if user A follows user B
|
||||
- [x] Checks if user is a public member of an organizations
|
||||
- [x] Returns a user's subscriptions
|
||||
- [x] Gets a user's subscriptions
|
||||
- [x] Gets a user's events
|
||||
- [x] Searches users
|
||||
- [x] Searches repositories
|
||||
- [x] Searches topics
|
||||
- [x] Searches issues
|
||||
- [x] Searches commits
|
||||
- [x] Easily updates with the 'update' command
|
||||
- [x] Easily updates with the 'update:install' command
|
||||
- [x] Automatically logs network activity (.logs folder)
|
||||
- [x] User can view, read and delete log files
|
||||
- [x] User can view, read and delete logs
|
||||
|
||||
***
|
||||
# AVAILABLE COMMANDS
|
||||
| COMMAND | DESCRIPTION|
|
||||
| ------------- |:---------:|
|
||||
| ``info:org`` | *get organization info* |
|
||||
| ``info:user`` | *get user profile info* |
|
||||
| ``info:repo`` | *get repository info* |
|
||||
| ``info:dev`` | *show developer's info* |
|
||||
| ``path:contents`` | *get contents of a path from a specified repository* |
|
||||
| ``repos:org`` | *get a list of repositories owned by a specified organization* |
|
||||
| ``repos:user`` | *get a list of repositories owned by a specified user* |
|
||||
| ``user:gists`` | *get a list of gists owned by a specified user* |
|
||||
| ``user:followers`` | *get a list of a user's followers* |
|
||||
| ``user:following`` | *check whether user A follows user B* |
|
||||
| ``search:users`` | *search user(s)* |
|
||||
| ``search:repos`` | *search repositor(y)(ies)* |
|
||||
| ``search:topics`` | *search topics(s)* |
|
||||
| ``search:issues`` | *search issue(s)* |
|
||||
| ``search:commits`` | *search commit(s)* |
|
||||
| ``logs:view`` | *view octosuite 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* |
|
||||
|
||||
***
|
||||
# NOTES
|
||||
* *octosuite automatically logs network and minor user activity. The logs are saved by date and time in .logs folder*
|
||||
* *Although octosuite was developed to work on **Mac**, **Windows**, or any **Linux** *Distribution*, it has only been tested on **Termux** *and* **Kali Linux***
|
||||
* *If you believe octosuite can be better, feel free to open a pull request with your improvements* ✌🏾🙂
|
||||
|
||||
***
|
||||
|
||||
# PYPI
|
||||
[PyPI Package](https://pypi.org/project/octosuite)
|
||||
|
||||

|
||||
|
||||
***
|
||||
|
||||
# LICENSE
|
||||

|
||||
|
||||
***
|
||||
|
||||
# ABOUT DEVELOPER
|
||||
[About.me](https://about.me/rly0nheart)
|
||||
|
||||
***
|
||||
|
||||
# SUPPORTERS
|
||||
[](https://github.com/rly0nheart/octosuite/stargazers)
|
||||
[](https://github.com/rly0nheart/octosuite/members)
|
||||
|
||||
|
||||
# DONATIONS
|
||||
Love octosuite? Please consider buying me a coffee, I will appreciate really it. ☕👌🏾😊
|
||||
Love octosuite? Please consider buying me a coffee, I will really appreciate it. ☕👌🏾😊
|
||||
|
||||
<a href="https://www.buymeacoffee.com/189381184" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/default-orange.png" alt="Buy Me A Coffee" height="41" width="174"></a>
|
||||
|
||||
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)
|
||||
BIN
img/logo.png
Normal file
BIN
img/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
@@ -1,25 +0,0 @@
|
||||
import os
|
||||
from lib.colors import red, white, green, red_bg,reset
|
||||
|
||||
version = 'v1.9.0'
|
||||
banner = f'''{red}
|
||||
▒█████ ▄████▄ ▄▄▄█████▓ ▒█████ ██████ █ ██ ██▓▄▄▄█████▓▓█████
|
||||
▒██▒ ██▒▒██▀ ▀█ ▓ ██▒ ▓▒▒██▒ ██▒▒██ ▒ ██ ▓██▒▓██▒▓ ██▒ ▓▒▓█ ▀
|
||||
▒██░ ██▒▒▓█ ▄ ▒ ▓██░ ▒░▒██░ ██▒░ ▓██▄ ▓██ ▒██░▒██▒▒ ▓██░ ▒░▒███
|
||||
▒██ ██░▒▓▓▄ ▄██▒░ ▓██▓ ░ ▒██ ██░ ▒ ██▒▓▓█ ░██░░██░░ ▓██▓ ░ ▒▓█ ▄
|
||||
░ ████▓▒░▒ ▓███▀ ░ ▒██▒ ░ ░ ████▓▒░▒██████▒▒▒█████▓ ░██░ ▒██▒ ░ ░▒████▒
|
||||
░ ▒░▒░▒░ ░ ░▒ ▒ ░ ▒ ░░ ░ ▒░▒░▒░ ▒ ▒▓▒ ▒ ░░▒▓▒ ▒ ▒ ░▓ ▒ ░░ ░░ ▒░ ░
|
||||
░ ▒ ▒░ ░ ▒ ░ ░ ▒ ▒░ ░ ░▒ ░ ░░░▒░ ░ ░ ▒ ░ ░ ░ ░ ░
|
||||
░ ░ ░ ▒ ░ ░ ░ ░ ░ ▒ ░ ░ ░ ░░░ ░ ░ ▒ {red_bg} {version} {reset}{red}
|
||||
░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░
|
||||
░ {white}— Advanced Github {red}OSINT{white} Framework{reset}
|
||||
|
||||
|
||||
|
||||
> {white}Current user: {green}{os.getlogin()}{reset}
|
||||
> {white}Use {green}help{reset}{white} command for usage{reset}
|
||||
> {white}Commands are case sensitive{reset}
|
||||
{'-'*27}
|
||||
|
||||
|
||||
'''
|
||||
@@ -1,51 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
import platform
|
||||
from datetime import datetime
|
||||
|
||||
colors = True
|
||||
machine = sys.platform
|
||||
# Colors will be unavailable on non-linux machines
|
||||
if machine.lower().startswith(("os", "win", "darwin","ios")):
|
||||
colors = False
|
||||
|
||||
if not colors:
|
||||
reset = red = white = green = red_bg = ""
|
||||
|
||||
else:
|
||||
date_time = datetime.now()
|
||||
sys_info = [("Processor",platform.processor),
|
||||
("Node", platform.node),
|
||||
("Release", platform.release),
|
||||
("Architecture", platform.architecture),
|
||||
("Version", platform.version)]
|
||||
|
||||
banner = f"""
|
||||
OCTOSUITE © 2022 Richard Mwewa
|
||||
{date_time.strftime('%A %d %B %Y, %H:%M:%S%p')}
|
||||
|
||||
|
||||
{platform.system()}"""
|
||||
|
||||
print(banner)
|
||||
for key, value in sys_info:
|
||||
print(f"\t├─ {key}: {value()}")
|
||||
print("\n")
|
||||
while True:
|
||||
try:
|
||||
color_chooser = input(f"[ ? ] Welcome {os.getlogin()}, would you like to enable colors for this session? (y/n) ")
|
||||
if color_chooser.lower() == "y":
|
||||
white = "\033[97m"
|
||||
red = "\033[91m"
|
||||
reset = "\033[0m"
|
||||
green = "\033[92m"
|
||||
red_bg = "\033[41;37m"
|
||||
break
|
||||
elif color_chooser.lower() == "n":
|
||||
red = white = green = red_bg = reset = ""
|
||||
break
|
||||
else:
|
||||
print(f"\n[ ! ] Your response ({color_chooser}) is invalid (expected y or n)")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
exit(f"[ ! ] Process interrupted with (Ctrl+C)")
|
||||
16
octosuite
16
octosuite
@@ -1,17 +1,19 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import logging
|
||||
from src.main import *
|
||||
from lib.colors import red,white,reset
|
||||
from utils.colors import Color
|
||||
from utils.misc import Banner
|
||||
from core.main import Octosuite, Attributes, logMsg
|
||||
|
||||
print(Banner.nameLogo)
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
octosuite().on_start()
|
||||
Octosuite().onStart()
|
||||
|
||||
except KeyboardInterrupt:
|
||||
logging.warning('Session terminated with (Ctrl+C)')
|
||||
exit(f'\n{white}[{red} x {white}] Session terminated with ({red}Ctrl{white}+{red}C{reset}{white}).{reset}')
|
||||
logging.warning(logMsg.Ctrl)
|
||||
print(Attributes.warning, logMsg.Ctrl)
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f'Session terminated on error: {e}')
|
||||
exit(f'\n{white}[{red} ! {white}] Session terminated on error: {red}{e}{reset}')
|
||||
logging.error(logMsg.Error.format(e))
|
||||
print(Attributes.error, logMsg.Exception.format(f'{Color.red}{e}{Color.reset}'))
|
||||
|
||||
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)
|
||||
57
utils/colors.py
Normal file
57
utils/colors.py
Normal file
@@ -0,0 +1,57 @@
|
||||
import os
|
||||
import sys
|
||||
import platform
|
||||
from datetime import datetime
|
||||
|
||||
# The Color class is responsible for enabling/disabling colors in OctoSuite
|
||||
# This class gets called first at start up before any other class/method gets called (makes one think why this is not the firstBlood class)
|
||||
# Color class is the reason why users get to choose whether to enable/disable colors
|
||||
# Unfortunately for our friends the 'non-Linux' users, they will not yet have the opportunity to see what OctoSuite looks like with colors enabled lol
|
||||
class Color:
|
||||
colors = True
|
||||
# Colors will be unavailable on non-linux machines
|
||||
if sys.platform.lower().startswith(("os", "win", "darwin","ios")):
|
||||
colors = False
|
||||
|
||||
if not colors:
|
||||
reset = red = white = green = red_bg = ""
|
||||
|
||||
else:
|
||||
# Printing system information was completely unnecessary (just like most things in this program :D)
|
||||
# But at least users will get to know things they did not know about their machines ;)
|
||||
date_time = datetime.now()
|
||||
sys_info = [("Processor",platform.processor),
|
||||
("Node", platform.node),
|
||||
("Release", platform.release),
|
||||
("Architecture", platform.architecture),
|
||||
("Version", platform.version)]
|
||||
|
||||
banner = f"""
|
||||
OCTOSUITE © 2022 Richard Mwewa
|
||||
{date_time.strftime('%A %d %B %Y, %H:%M:%S%p')}
|
||||
|
||||
|
||||
|
||||
{platform.system()}"""
|
||||
print(banner)
|
||||
for key, value in sys_info:
|
||||
print(f"\t├─ {key}: {value()}")
|
||||
print("\n")
|
||||
while True:
|
||||
try:
|
||||
color_chooser = input(f"[ ? ] Welcome {os.getlogin()}, would you like to enable colors for this session? (y/n) ")
|
||||
if color_chooser.lower() == "y":
|
||||
white = "\033[97m"
|
||||
red = "\033[91m"
|
||||
reset = "\033[0m"
|
||||
green = "\033[92m"
|
||||
red_bg = "\033[41;37m"
|
||||
break
|
||||
elif color_chooser.lower() == "n":
|
||||
red = white = green = red_bg = reset = ""
|
||||
break
|
||||
else:
|
||||
print(f"\n[ ! ] Your response ({color_chooser}) is invalid (expected y or n) ")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
exit(f"[ ! ] Process interrupted with (Ctrl+C).")
|
||||
118
utils/helper.py
Normal file
118
utils/helper.py
Normal file
@@ -0,0 +1,118 @@
|
||||
from utils.colors import Color
|
||||
|
||||
class Help:
|
||||
usageText = 'Use {} to get started'
|
||||
def updateCommand():
|
||||
print(f'''
|
||||
{Color.white}Update subcommands{Color.reset}
|
||||
{'='*18}
|
||||
{Help.usageText.format('update:<subcommand>')}
|
||||
|
||||
Command Description
|
||||
------- -----------
|
||||
check Check for updates
|
||||
install Download and install updates''')
|
||||
|
||||
|
||||
def searchCommand():
|
||||
print(f'''
|
||||
{Color.white}Search subcommands{Color.reset}
|
||||
{'='*18}
|
||||
{Help.usageText.format('search:<subcommand>')}
|
||||
|
||||
Command Description
|
||||
------- -----------
|
||||
users Search user(s)
|
||||
repos Search repositor[yies]
|
||||
topics Search topic(s)
|
||||
issues Search issue(s)
|
||||
commits Search commit(s)''')
|
||||
|
||||
|
||||
def userCommand():
|
||||
print(f'''
|
||||
{Color.white}User subcommands{Color.reset}
|
||||
{'='*17}
|
||||
{Help.usageText.format('user:<subcommand>')}
|
||||
|
||||
Command Description
|
||||
------- -----------
|
||||
profile Get a user's profile info
|
||||
gists Return a users's gists
|
||||
orgs Return organizations that a user belongs to/owns
|
||||
repos Return a user's repositories
|
||||
events Return a user's events
|
||||
followers Return a user's followers
|
||||
following Check if user[A] follows user[B]
|
||||
subscriptions Return a user's subscriptions''')
|
||||
|
||||
|
||||
def orgCommand():
|
||||
print(f'''
|
||||
{Color.white}Org subcommands{Color.reset}
|
||||
{'='*16}
|
||||
{Help.usageText.format('org:<subcommand>')}
|
||||
|
||||
Command Description
|
||||
------- -----------
|
||||
profile Get an organization's info
|
||||
repos Return an organization's repositories
|
||||
events Return an organization's events
|
||||
member Check if a specified user is a public member of the target organization''')
|
||||
|
||||
|
||||
def repoCommand():
|
||||
print(f'''
|
||||
{Color.white}Repo subcommands{Color.reset}
|
||||
{'='*17}
|
||||
{Help.usageText.format('repo:<subcommand>')}
|
||||
|
||||
Command Description
|
||||
------- -----------
|
||||
profile Get a repository's info
|
||||
forks Return a repository's forks
|
||||
releases Return a repository's releases
|
||||
languages Return a repository's languages
|
||||
stargazers Return a repository's stargazers
|
||||
pathcontents List contents in a path of a repository''')
|
||||
|
||||
|
||||
def logsCommand():
|
||||
print(f'''
|
||||
{Color.white}Logs subcommands{Color.reset}
|
||||
{'='*17}
|
||||
{Help.usageText.format('logs:<subcommand>')}
|
||||
|
||||
Command Description
|
||||
------- -----------
|
||||
view View logs
|
||||
read Read log
|
||||
delete Delete log''')
|
||||
|
||||
|
||||
def helpCommand():
|
||||
print(f'''
|
||||
{Color.white}Core commands{Color.reset}
|
||||
{'='*13}
|
||||
|
||||
{Color.white}Command Description{Color.reset}
|
||||
------- -----------
|
||||
help Help menu
|
||||
exit Close session
|
||||
clear Clear screen
|
||||
about Program's info
|
||||
author Developer's info
|
||||
version Version info
|
||||
|
||||
|
||||
{Color.white}Help subcommands{Color.reset}
|
||||
{'='*16}
|
||||
{Help.usageText.format('help:<subcommand>')}
|
||||
|
||||
{Color.white}Command Description{Color.reset}
|
||||
------- -----------
|
||||
logs List all logs management commands
|
||||
repo List all repository investigation commands
|
||||
user List all users investigation commands
|
||||
search List all target discovery commands
|
||||
update List all program updates managememt commands''')
|
||||
20
utils/misc.py
Normal file
20
utils/misc.py
Normal file
@@ -0,0 +1,20 @@
|
||||
import os
|
||||
from utils.colors import Color
|
||||
|
||||
class Banner:
|
||||
versionTag = '2.0.0-beta'
|
||||
nameLogo = f'''{Color.white}
|
||||
_______ __ _______ __ __
|
||||
| |.----.| |_.-----.| __|.--.--.|__| |_.-----.
|
||||
| - || __|| _| _ ||__ || | || | _| -__|
|
||||
|_______||____||____|_____||_______||_____||__|____|_____|
|
||||
v{versionTag}
|
||||
{Color.white}— Advanced Github {Color.red}OSINT{Color.white} Framework{Color.reset}
|
||||
|
||||
|
||||
.:{Color.white}{Color.green}{os.getlogin()}{Color.reset}:.
|
||||
|
||||
- {Color.white}Use {Color.green}help{Color.reset}{Color.white} command for usage{Color.reset}
|
||||
- {Color.white}Commands are case insensitive{Color.reset}
|
||||
{'-'*32}
|
||||
'''
|
||||
Reference in New Issue
Block a user