Compare commits

...

22 Commits

Author SHA1 Message Date
Richard Mwewa
8de7b0d939 Update misc.py 2022-05-20 17:00:28 +02:00
Richard Mwewa
6afc37d9a4 Update octosuite 2022-05-20 16:52:52 +02:00
Richard Mwewa
b030d48152 Create helper.py 2022-05-20 16:51:07 +02:00
Richard Mwewa
508df0ea77 Create colors.py 2022-05-20 16:48:52 +02:00
Richard Mwewa
fb7ad2cda8 Delete colors.py 2022-05-20 16:47:57 +02:00
Richard Mwewa
b16eb762e0 Update and rename lib/banner.py to utils/misc.py 2022-05-20 16:46:34 +02:00
Richard Mwewa
8b7ac9e428 Update and rename src/main.py to core/main.py 2022-05-20 16:43:49 +02:00
Richard Mwewa
66e0da38b6 Update README.md 2022-05-20 16:39:46 +02:00
Richard Mwewa
fbf7e40404 Update README.md 2022-05-19 21:59:51 +02:00
Richard Mwewa
73bcb68012 Delete .img 2022-05-19 21:56:59 +02:00
Richard Mwewa
850d9bb5ab Add files via upload 2022-05-19 21:52:40 +02:00
Richard Mwewa
17cfb5920c Create .img 2022-05-19 21:51:44 +02:00
Richard Mwewa
5ffb7758a9 Update README.md 2022-05-15 21:20:47 +02:00
Richard Mwewa
2d6a808100 Update README.md 2022-05-06 14:14:25 +02:00
Richard Mwewa
02feed27a8 Update README.md 2022-05-04 17:25:01 +02:00
Richard Mwewa
77ddfa4b02 Update README.md 2022-04-30 10:32:31 +02:00
Richard Mwewa
66192c2a63 Create .log 2022-04-30 10:30:49 +02:00
Richard Mwewa
f591764343 Update banner.py 2022-04-30 10:28:50 +02:00
Richard Mwewa
f6dbd8ec49 Update main.py 2022-04-30 10:27:11 +02:00
Richard Mwewa
99deaf04f3 Update main.py 2022-04-29 23:53:10 +02:00
Richard Mwewa
56eead2f26 Update README.md 2022-04-29 23:46:12 +02:00
Richard Mwewa
73e7c339e6 Update README.md 2022-04-29 23:26:32 +02:00
11 changed files with 993 additions and 669 deletions

1
.logs/.log Normal file
View File

@@ -0,0 +1 @@

View File

@@ -1,12 +1,11 @@
![octosuite](https://user-images.githubusercontent.com/74001397/165550323-d880e320-a4c0-4f4e-87dd-d2e8319554ec.png)
![octosuite](img/logo.png)
![OS](https://img.shields.io/badge/OS-GNU%2FLinux-red?style=for-the-badge&logo=Linux)
![OS](https://img.shields.io/badge/OS-Windows-blue?style=for-the-badge&logo=Windows)
![OS](https://img.shields.io/badge/OS-Mac-white?style=for-the-badge&logo=apple)
![GitHub](https://img.shields.io/github/license/rly0nheart/octosuite?style=for-the-badge&logo=github)
![GitHub tag (latest by date)](https://img.shields.io/github/v/tag/rly0nheart/octosuite?style=for-the-badge&logo=github)
![GitHub commits since latest release (by date)](https://img.shields.io/github/commits-since/rly0nheart/octosuite/1.8.0?style=for-the-badge&logo=github)
![GitHub last commit](https://img.shields.io/github/last-commit/rly0nheart/octosuite?style=for-the-badge&logo=github)
![GitHub commits since latest release (by date)](https://img.shields.io/github/commits-since/rly0nheart/octosuite/1.9.0?style=for-the-badge&logo=github)
![GitHub repo size](https://img.shields.io/github/repo-size/rly0nheart/octosuite?style=for-the-badge&logo=github)
> *Simply gather OSINT on Github users & organizations like a God🔥*
@@ -16,69 +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)
![PyPI Downloads](https://pepy.tech/badge/octosuite)
***
# LICENSE
![license](https://user-images.githubusercontent.com/74001397/137917929-2f2cdb0c-4d1d-4e4b-9f0d-e01589e027b5.png)
***
# ABOUT DEVELOPER
[About.me](https://about.me/rly0nheart)
# SUPPORTERS
[![Stargazers repo roster for @rly0nheart/octosuite](https://reporoster.com/stars/rly0nheart/octosuite)](https://github.com/rly0nheart/octosuite/stargazers)
[![Forkers repo roster for @rly0nheart/octosuite](https://reporoster.com/forks/rly0nheart/octosuite)](https://github.com/rly0nheart/octosuite/members)
# DONATIONS
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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

@@ -1,23 +0,0 @@
import os
from lib.colors import red, white, green, red_bg,reset
banner = f'''{red}
▒█████ ▄████▄ ▄▄▄█████▓ ▒█████ ██████ █ ██ ██▓▄▄▄█████▓▓█████
▒██▒ ██▒▒██▀ ▀█ ▓ ██▒ ▓▒▒██▒ ██▒▒██ ▒ ██ ▓██▒▓██▒▓ ██▒ ▓▒▓█ ▀
▒██░ ██▒▒▓█ ▄ ▒ ▓██░ ▒░▒██░ ██▒░ ▓██▄ ▓██ ▒██░▒██▒▒ ▓██░ ▒░▒███
▒██ ██░▒▓▓▄ ▄██▒░ ▓██▓ ░ ▒██ ██░ ▒ ██▒▓▓█ ░██░░██░░ ▓██▓ ░ ▒▓█ ▄
░ ████▓▒░▒ ▓███▀ ░ ▒██▒ ░ ░ ████▓▒░▒██████▒▒▒█████▓ ░██░ ▒██▒ ░ ░▒████▒
░ ▒░▒░▒░ ░ ░▒ ▒ ░ ▒ ░░ ░ ▒░▒░▒░ ▒ ▒▓▒ ▒ ░░▒▓▒ ▒ ▒ ░▓ ▒ ░░ ░░ ▒░ ░
░ ▒ ▒░ ░ ▒ ░ ░ ▒ ▒░ ░ ░▒ ░ ░░░▒░ ░ ░ ▒ ░ ░ ░ ░ ░
░ ░ ░ ▒ ░ ░ ░ ░ ░ ▒ ░ ░ ░ ░░░ ░ ░ ▒ {red_bg} v1.8.0 {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}
'''

View File

@@ -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)")

View File

@@ -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}'))

View File

@@ -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 lib.banner import banner
from datetime import datetime
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'}
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} v1.8.0 [CHANGELOG] {reset}
• Cleaned code
• Changes and improvements (noticeable)
{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
View 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
View 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
View 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}
'''