Compare commits

...

51 Commits

Author SHA1 Message Date
Richard Mwewa
11e55f7804 Update README.md 2022-06-26 10:18:39 +02:00
Richard Mwewa
96c62c7097 Update banner.py 2022-06-26 10:08:44 +02:00
Richard Mwewa
587641cd18 Update setup.py 2022-06-26 10:07:55 +02:00
Richard Mwewa
d1ad3d14b9 Update banner.py 2022-06-26 10:06:55 +02:00
Richard Mwewa
0e3568f967 Update log_roller.py 2022-06-26 10:05:59 +02:00
Richard Mwewa
51d27c4996 [fix] error in source commands 2022-06-26 10:04:32 +02:00
Richard Mwewa
ae9d9f4af5 Update README.md 2022-06-25 09:47:51 +02:00
Richard Mwewa
482f2ee55a Update README.md 2022-06-24 15:05:38 +02:00
Richard Mwewa
9894b15209 Update README.md 2022-06-24 15:04:48 +02:00
Richard Mwewa
4d563683d2 Update README.md 2022-06-24 14:57:00 +02:00
Richard Mwewa
614a24d69c Update main.py 2022-06-23 21:12:51 +02:00
Richard Mwewa
2c18f116e9 Create python-publish.yml 2022-06-23 20:56:36 +02:00
Richard Mwewa
f67e876773 Delete output directory 2022-06-23 12:37:32 +02:00
Richard Mwewa
cf7ab5eef1 Delete downloads directory 2022-06-23 12:37:07 +02:00
Richard Mwewa
bdb88135fe Delete .logs directory 2022-06-23 12:36:49 +02:00
Richard Mwewa
e062065c75 Create setup.py 2022-06-23 12:36:11 +02:00
Richard Mwewa
b2557fd714 Create sign_vars.py 2022-06-23 12:31:33 +02:00
Richard Mwewa
2a025190bd Create log_roller.py 2022-06-23 12:30:21 +02:00
Richard Mwewa
3b4f38e76a Create helper.py 2022-06-23 12:29:18 +02:00
Richard Mwewa
5bffb8a4b5 Create csv_loggers.py 2022-06-23 12:28:01 +02:00
Richard Mwewa
1e95f70e3e Create colors.py 2022-06-23 12:25:43 +02:00
Richard Mwewa
88cc04a104 Create __init__.py 2022-06-23 12:24:48 +02:00
Richard Mwewa
3339614984 Create banner.py 2022-06-23 12:24:22 +02:00
Richard Mwewa
c9cb27f3fd Create main.py 2022-06-23 12:23:14 +02:00
Richard Mwewa
f58736f27d Delete octosuite 2022-06-23 12:22:05 +02:00
Richard Mwewa
822be0d088 Delete requirements.txt 2022-06-23 12:21:32 +02:00
Richard Mwewa
9fe13482ff Delete utilities directory 2022-06-23 12:20:44 +02:00
Richard Mwewa
c4b8e70762 Delete core directory 2022-06-23 12:19:40 +02:00
Richard Mwewa
557e64c22f Update README.md 2022-06-20 14:59:57 +02:00
Richard Mwewa
ba6d6da734 Delete python-publish.yml 2022-06-16 03:27:42 +02:00
Richard Mwewa
94a22c6de1 Update python-publish.yml 2022-06-16 03:26:25 +02:00
Richard Mwewa
0ff5765dc5 Update python-publish.yml 2022-06-16 02:39:16 +02:00
Richard Mwewa
bb4596c294 Update README.md 2022-06-16 02:06:33 +02:00
Richard Mwewa
8967e9ae7a Update python-publish.yml 2022-06-16 02:03:54 +02:00
Richard Mwewa
ccb40ec942 Update python-publish.yml 2022-06-16 01:42:23 +02:00
Richard Mwewa
4e2291eba3 Update README.md 2022-06-15 17:33:50 +02:00
Richard Mwewa
a9787efbe1 Update python-publish.yml 2022-06-15 01:46:18 +02:00
Richard Mwewa
efd7b44131 Update python-publish.yml 2022-06-15 01:08:27 +02:00
Richard Mwewa
2f40b22ab5 Create python-publish.yml 2022-06-15 00:45:44 +02:00
Richard Mwewa
17075b8a4b Rename investigations/.investigations to output/.output 2022-06-14 03:12:01 +02:00
Richard Mwewa
5d7f8cfbd7 Create .investigations 2022-06-09 18:05:07 +02:00
Richard Mwewa
daffefe24e Create README.md 2022-06-08 12:31:20 +02:00
Richard Mwewa
887957b73c Update helper.py 2022-06-05 14:42:57 +02:00
Richard Mwewa
2ab70c0737 Update colors.py 2022-06-05 14:36:18 +02:00
Richard Mwewa
0bca1d6e42 Update misc.py 2022-06-05 14:34:49 +02:00
Richard Mwewa
8f426eae26 Update misc.py 2022-06-05 14:26:49 +02:00
Richard Mwewa
d350a37cb6 Update colors.py 2022-06-05 10:55:07 +02:00
Richard Mwewa
151e8ba3dd Update misc.py 2022-06-05 10:54:04 +02:00
Richard Mwewa
23f3b43362 Update README.md 2022-05-29 13:05:13 +02:00
Richard Mwewa
985325550b Update README.md 2022-05-28 10:51:03 +02:00
Richard Mwewa
be3e14889d Update main.py 2022-05-26 15:25:29 +02:00
17 changed files with 1775 additions and 934 deletions

39
.github/workflows/python-publish.yml vendored Normal file
View File

@@ -0,0 +1,39 @@
# This workflow will upload a Python Package using Twine when a release is created
# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
name: Upload Python Package
on:
release:
types: [published]
permissions:
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v3
with:
python-version: '3.x'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install build
- name: Build package
run: python -m build
- name: Publish package
uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}

View File

@@ -1,22 +1,17 @@
![octosuite](images/logo.png)
![logo](https://user-images.githubusercontent.com/74001397/175805580-fffc96d4-e0ef-48bb-a55c-80b2da3e714d.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/2.0.0-beta?style=for-the-badge&logo=github)
![GitHub repo size](https://img.shields.io/github/repo-size/rly0nheart/octosuite?style=for-the-badge&logo=github)
[![Upload Python Package](https://github.com/rly0nheart/octosuite/actions/workflows/python-publish.yml/badge.svg)](https://github.com/rly0nheart/octosuite/actions/workflows/python-publish.yml)
![GitHub](https://img.shields.io/github/license/rly0nheart/octosuite?style=flat)
![GitHub tag (latest by date)](https://img.shields.io/github/v/tag/rly0nheart/octosuite?style=flat&logo=github)
![GitHub commits since latest release (by date)](https://img.shields.io/github/commits-since/rly0nheart/octosuite/2.1.1?style=flat&logo=github)
![GitHub repo size](https://img.shields.io/github/repo-size/rly0nheart/octosuite?style=flat&logo=github)
> *Simply gather OSINT on Github users & organizations like a God🔥*
# INSTALLATION
Installation instructions are on the wiki, in addition to all other documentation.
## Installation
[Refer to the Wiki](https://github.com/rly0nheart/octosuite/wiki) for installation instructions, in addition to all other documentation.
[Refer to the Wiki](https://github.com/rly0nheart/octosuite/wiki)
# FEATURES
## Features
- [x] Fetches an organization's profile information
- [x] Fetches an oganization's events
- [x] Returns an organization's repositories
@@ -32,6 +27,7 @@ Installation instructions are on the wiki, in addition to all other documentatio
- [x] Returns a user's gists
- [x] Returns organizations that a user owns/belongs to
- [x] Fetches a user's events
- [x] Fetches a list of users followed by the target
- [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
@@ -43,36 +39,28 @@ Installation instructions are on the wiki, in addition to all other documentatio
- [x] Searches topics
- [x] Searches issues
- [x] Searches commits
- [x] Easily updates with the 'update:install' command
- [x] Automatically logs network activity (.logs folder)
- [x] User can view, read and delete logs
- [x] ...And more
# 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* ✌🏾🙂
> *octosuite automatically logs network and minor user activity of each session. The logs are saved by date and time in the .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
![license](https://user-images.githubusercontent.com/74001397/137917929-2f2cdb0c-4d1d-4e4b-9f0d-e01589e027b5.png)
# ABOUT DEVELOPER
## About developer
[About.me](https://about.me/rly0nheart)
# SUPPORTERS
## 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
## 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>

View File

@@ -1,736 +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 getpass
import requests
import platform
import subprocess
from tqdm import tqdm
from pprint import pprint
from utilities.misc import Banner
from utilities.helper import Help
from utilities.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}{getpass.getuser()}{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','utilities/helper.py','utilities/misc.py','utilities/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']
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-25 11:05AM
{'-'*31}
What's changed?
{'-'*15}
[✓] Fixed a bug in issue #2''')
# 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)

View File

@@ -1 +0,0 @@

View File

@@ -1,19 +0,0 @@
#!/usr/bin/env python3
import logging
from utilities.colors import Color
from utilities.misc import Banner
from core.main import Octosuite, Attributes, logMsg
print(Banner.nameLogo)
if __name__ == '__main__':
try:
Octosuite().onStart()
except KeyboardInterrupt:
logging.warning(logMsg.Ctrl)
print(Attributes.warning, logMsg.Ctrl)
except Exception as e:
logging.error(logMsg.Error.format(e))
print(Attributes.error, logMsg.Error.format(f'{Color.red}{e}{Color.reset}'))

23
octosuite/banner.py Normal file
View File

@@ -0,0 +1,23 @@
import getpass
from octosuite.colors import Color
'''
Banner
This class holds the program's banner logo and version tag
'''
class Banner:
versionTag = '2.1.1'
nameLogo = f'''{Color.white}
_______ __ _______ __ __
| |.----.| |_.-----.| __|.--.--.|__| |_.-----.
| - || __|| _| _ ||__ || | || | _| -__|
|_______||____||____|_____||_______||_____||__|____|_____|
v{versionTag}
{Color.white}— Advanced Github {Color.red}OSINT{Color.white} Framework{Color.reset}
.:{getpass.getuser()}:.
{Color.white}├─ use {Color.green}help{Color.reset}{Color.white} command for usage{Color.reset}
{Color.white}└╼ commands are case insensitive{Color.reset}
'''

View File

@@ -1,5 +1,4 @@
import sys
import getpass
import platform
from datetime import datetime
@@ -21,10 +20,10 @@ class Color:
# 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)]
("Node", platform.node),
("Release", platform.release),
("Architecture", platform.architecture),
("Version", platform.version)]
banner = f"""
OCTOSUITE © 2022 Richard Mwewa
@@ -39,13 +38,12 @@ class Color:
print("\n")
while True:
try:
color_chooser = input(f"[ ? ] Welcome {getpass.getuser()}, would you like to enable colors for this session? (y/n) ")
color_chooser = input(f"[ ? ] Welcome, 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 = ""
@@ -54,4 +52,4 @@ class Color:
print(f"\n[ ! ] Your response ({color_chooser}) is invalid (expected y or n) ")
except KeyboardInterrupt:
exit(f"[ ! ] Process interrupted with (Ctrl+C).")
exit(f"[ ! ] Process interrupted with [Ctrl+C].")

446
octosuite/csv_loggers.py Normal file
View File

@@ -0,0 +1,446 @@
import csv
import sys
import logging
from octosuite.sign_vars import SignVar
from octosuite.log_roller import logRoller
'''
csvLogger
This class holds the methods for creating .csv files of each functionality in main
'''
class csvLogger:
# .csv for organization' profile
def logOrgProfile(response):
org_profile_fields = ['Profile photo', 'Name', 'Username', 'ID', 'Node ID', 'Email', 'About', 'Location', 'Blog', 'Followers', 'Following', 'Twitter handle', 'Gists', 'Repositories', 'Account type', 'Is verified?', 'Has organization projects?', 'Has repository projects?', 'Created at', 'Updated at']
org_profile_row = [response.json()['avatar_url'], response.json()['name'], response.json()['login'], response.json()['id'], response.json()['node_id'], response.json()['email'], response.json()['description'], response.json()['location'], response.json()['blog'], response.json()['followers'], response.json()['following'], response.json()['twitter_username'], response.json()['public_gists'], response.json()['public_repos'], response.json()['type'], response.json()['is_verified'], response.json()['has_organization_projects'], response.json()['has_repository_projects'], response.json()['created_at'], response.json()['updated_at']]
prompt = input(f'\n{SignVar.prompt} {logRoller.askLogCsv}').lower()
if prompt == 'y':
with open(f"output/{response.json()['name']}.csv", 'w') as file:
writecsv = csv.writer(file)
writecsv.writerow(org_profile_fields)
writecsv.writerow(org_profile_row)
logging.info(logRoller.loggedToCsv.format(file.name))
sys.stdout.write(f"{SignVar.positive} {logRoller.loggedToCsv.format(file.name)}\n")
else:
logging.info(logRoller.loggingSkipped)
sys.stdout.write(f"{SignVar.info} ({prompt}) {logRoller.loggingSkipped}\n")
# Creating a .csv file of a user' profile
def logUserProfile(response):
user_profile_fields = ['Profile photo', 'Name', 'Username', 'ID', 'Node ID', 'Bio', 'Blog', 'Location', 'Followers', 'Following', 'Twitter handle', 'Gists', 'Repositories', 'Organization', 'Is hireable?', 'Is site admin?', 'Joined at', 'Updated at']
user_profile_row = [response.json()['avatar_url'], response.json()['name'], response.json()['login'], response.json()['id'], response.json()['node_id'], response.json()['bio'], response.json()['blog'], response.json()['location'], response.json()['followers'], response.json()['following'], response.json()['twitter_username'], response.json()['public_gists'], response.json()['public_repos'], response.json()['company'], response.json()['hireable'], response.json()['site_admin'], response.json()['created_at'], response.json()['updated_at']]
prompt = input(f'\n{SignVar.prompt} {logRoller.askLogCsv}').lower()
if prompt == 'y':
with open(f"output/{response.json()['login']}.csv", 'w',) as file:
writecsv = csv.writer(file)
writecsv.writerow(user_profile_fields)
writecsv.writerow(user_profile_row)
logging.info(logRoller.loggedToCsv.format(file.name))
sys.stdout.write(f"{SignVar.positive} {logRoller.loggedToCsv.format(file.name)}\n")
else:
logging.info(logRoller.loggingSkipped)
sys.stdout.write(f"{SignVar.info} ({prompt}) {logRoller.loggingSkipped}\n")
# create .csv for repository profile
def logRepoProfile(response):
repo_profile_fields = [ 'Name','ID', 'About', 'Forks', 'Stars', 'Watchers', 'License', 'Branch', 'Visibility', 'Language(s)', 'Open issues', 'Topics', 'Homepage', 'Clone URL', 'SSH URL', 'Is fork?', 'Is forkable?', 'Is private?', 'Is archived?', 'Is template?', 'Has wiki?', 'Has pages?', 'Has projects?', 'Has issues?', 'Has downloads?', 'Pushed at', 'Created at', 'Updated at']
repo_profile_row = [response.json()['name'], response.json()['id'], response.json()['description'], response.json()['forks'], response.json()['stargazers_count'], response.json()['watchers'], response.json()['license'], response.json()['default_branch'], response.json()['visibility'], response.json()['language'], response.json()['open_issues'], response.json()['topics'], response.json()['homepage'], response.json()['clone_url'], response.json()['ssh_url'], response.json()['fork'], response.json()['allow_forking'], response.json()['private'], response.json()['archived'], response.json()['is_template'], response.json()['has_wiki'], response.json()['has_pages'], response.json()['has_projects'], response.json()['has_issues'], response.json()['has_downloads'], response.json()['pushed_at'], response.json()['created_at'], response.json()['updated_at']]
prompt = input(f'\n{SignVar.prompt} {logRoller.askLogCsv}').lower()
if prompt == 'y':
with open(f"output/{response.json()['name']}.csv", 'w') as file:
writecsv = csv.writer(file)
writecsv.writerow(repo_profile_fields)
writecsv.writerow(repo_profile_row)
logging.info(logRoller.loggedToCsv.format(file.name))
sys.stdout.write(f"{SignVar.positive} {logRoller.loggedToCsv.format(file.name)}\n")
else:
logging.info(logRoller.loggingSkipped)
sys.stdout.write(f"{SignVar.info} ({prompt}) {logRoller.loggingSkipped}\n")
# create .csv for repository path contents
def logRepoPathContents(content, repo_name):
path_content_fields = ['Filename', 'Size (bytes)', 'Type', 'Path', 'SHA', 'URL']
path_content_row = [content['name'], content['size'], content['type'], content['path'], content['sha'], content['html_url']]
prompt = input(f'\n{SignVar.prompt} {logRoller.askLogCsv}').lower()
if prompt == 'y':
with open(f"output/{content['name']}_content_from_{repo_name}.csv", 'w') as file:
writecsv = csv.writer(file)
writecsv.writerow(path_content_fields)
writecsv.writerow(path_content_row)
logging.info(logRoller.loggedToCsv.format(file.name))
sys.stdout.write(f"{SignVar.positive} {logRoller.loggedToCsv.format(file.name)}\n")
else:
logging.info(logRoller.loggingSkipped)
sys.stdout.write(f"{SignVar.info} ({prompt}) {logRoller.loggingSkipped}\n")
# create .csv for repository stargazer
def logRepoStargazers(stargazer, repo_name):
user_follower_fields = ['Profile photo', 'Username', 'ID', 'Node ID', 'Gravatar ID', 'Account type', 'Is site admin?', 'URL']
user_follower_row = [stargazer['avatar_url'], stargazer['login'], stargazer['id'], stargazer['node_id'], stargazer['gravatar_id'], stargazer['type'], stargazer['site_admin'], stargazer['html_url']]
prompt = input(f'\n{SignVar.prompt} {logRoller.askLogCsv}').lower()
if prompt == 'y':
with open(f"output/{stargazer['login']}_stargazer_of_{repo_name}.csv", 'w') as file:
writecsv = csv.writer(file)
writecsv.writerow(user_follower_fields)
writecsv.writerow(user_follower_row)
logging.info(logRoller.loggedToCsv.format(file.name))
sys.stdout.write(f"{SignVar.positive} {logRoller.loggedToCsv.format(file.name)}\n")
else:
logging.info(logRoller.loggingSkipped)
sys.stdout.write(f"{SignVar.info} ({prompt}) {logRoller.loggingSkipped}\n")
# create .csv for repository forks
def logRepoForks(fork, count):
repo_fork_fields = [ 'Name','ID', 'About', 'Forks', 'Stars', 'Watchers', 'License', 'Branch', 'Visibility', 'Language(s)', 'Open issues', 'Topics', 'Homepage', 'Clone URL', 'SSH URL', 'Is fork?', 'Is forkable?', 'Is private?', 'Is archived?', 'Is template?', 'Has wiki?', 'Has pages?', 'Has projects?', 'Has issues?', 'Has downloads?', 'Pushed at', 'Created at', 'Updated at']
repo_fork_row = [fork['full_name'], fork['id'], fork['description'], fork['forks'], fork['stargazers_count'], fork['watchers'], fork['license'], fork['default_branch'], fork['visibility'], fork['language'], fork['open_issues'], fork['topics'], fork['homepage'], fork['clone_url'], fork['ssh_url'], fork['fork'], fork['allow_forking'], fork['private'], fork['archived'], fork['is_template'], fork['has_wiki'], fork['has_pages'], fork['has_projects'], fork['has_issues'], fork['has_downloads'], fork['pushed_at'], fork['created_at'], fork['updated_at']]
prompt = input(f'\n{SignVar.prompt} {logRoller.askLogCsv}').lower()
if prompt == 'y':
with open(f"output/{fork['name']}_fork_{count}.csv", 'w') as file:
writecsv = csv.writer(file)
writecsv.writerow(repo_fork_fields)
writecsv.writerow(repo_fork_row)
logging.info(logRoller.loggedToCsv.format(file.name))
sys.stdout.write(f"{SignVar.positive} {logRoller.loggedToCsv.format(file.name)}\n")
else:
logging.info(logRoller.loggingSkipped)
sys.stdout.write(f"{SignVar.info} ({prompt}) {logRoller.loggingSkipped}\n")
# create .csv for repository issues
def logRepoIssues(issue, repo_name):
repo_issue_fields = ['Title', 'ID', 'Node ID', 'Number', 'State', 'Reactions', 'Comments', 'Milestone', 'Assignee', 'Assignees', 'Author association', 'Labels', 'Is locked?', 'Lock reason', 'Closed at', 'Created at', 'Updated at']
repo_issue_row = [issue['title'], issue['id'], issue['node_id'], issue['number'], issue['state'], issue['reactions'], issue['comments'], issue['milestone'], issue['assignee'], issue['assignees'], issue['author_association'], issue['labels'], issue['locked'], issue['active_lock_reason'], issue['closed_at'], issue['created_at'], issue['updated_at']]
prompt = input(f'\n{SignVar.prompt} {logRoller.askLogCsv}').lower()
if prompt == 'y':
with open(f"output/{repo_name}_issue_{issue['id']}.csv", 'w') as file:
writecsv = csv.writer(file)
writecsv.writerow(repo_issue_fields)
writecsv.writerow(repo_issue_row)
logging.info(logRoller.loggedToCsv.format(file.name))
sys.stdout.write(f"{SignVar.positive} {logRoller.loggedToCsv.format(file.name)}\n")
else:
logging.info(logRoller.loggingSkipped)
sys.stdout.write(f"{SignVar.info} ({prompt}) {logRoller.loggingSkipped}\n")
# create .csv for repository releases
def logRepoReleases(release, repo_name):
repo_release_fields = ['Name', 'ID', 'Node ID', 'Tag', 'Branch', 'Assets', 'Is draft?', 'Is prerelease?', 'Created at', 'Published at']
repo_release_row = [release['name'], release['id'], release['node_id'], release['tag_name'], release['target_commitish'], release['assets'], release['draft'], release['prerelease'], release['created_at'], release['published_at']]
prompt = input(f'\n{SignVar.prompt} {logRoller.askLogCsv}').lower()
if prompt == 'y':
with open(f"output/{repo_name}_release_{release['name']}.csv", 'w') as file:
writecsv = csv.writer(file)
writecsv.writerow(repo_release_fields)
writecsv.writerow(repo_release_row)
logging.info(logRoller.loggedToCsv.format(file.name))
sys.stdout.write(f"{SignVar.positive} {logRoller.loggedToCsv.format(file.name)}\n")
else:
logging.info(logRoller.loggingSkipped)
sys.stdout.write(f"{SignVar.info} ({prompt}) {logRoller.loggingSkipped}\n")
# Create .csv file for repository contributors
def logRepoContributors(contributor, repo_name):
repo_contributor_fields = ['Profile photo', 'Username', 'ID', 'Node ID', 'Gravatar ID', 'Account type', 'Is site admin?', 'URL']
repo_contributor_row = [contributor['avatar_url'], contributor['login'], contributor['id'], contributor['node_id'], contributor['gravatar_id'], contributor['type'], contributor['site_admin'], contributor['html_url']]
prompt = input(f'\n{SignVar.prompt} {logRoller.askLogCsv}').lower()
if prompt == 'y':
with open(f"output/{contributor['login']}_contributor_of_{repo_name}.csv", 'w') as file:
writecsv = csv.writer(file)
writecsv.writerow(repo_contributor_fields)
writecsv.writerow(repo_contributor_row)
logging.info(logRoller.loggedToCsv.format(file.name))
sys.stdout.write(f"{SignVar.positive} {logRoller.loggedToCsv.format(file.name)}\n")
else:
logging.info(logRoller.loggingSkipped)
print(f"{SignVar.info} {logRoller.loggingSkipped}\n")
# Create .csv for organization' events
def logOrgEvents(event, organization):
org_event_fields = ['ID', 'Type', 'Created at', 'Payload']
org_event_row = [event['id'], event['type'], event['created_at'], event['payload']]
prompt = input(f'\n{SignVar.prompt} {logRoller.askLogCsv}').lower()
if prompt == 'y':
with open(f"output/{organization}_event_{event['id']}.csv", 'w') as file:
writecsv = csv.writer(file)
writecsv.writerow(org_event_fields)
writecsv.writerow(org_event_row)
logging.info(logRoller.loggedToCsv.format(file.name))
sys.stdout.write(f"{SignVar.positive} {logRoller.loggedToCsv.format(file.name)}\n")
else:
logging.info(logRoller.loggingSkipped)
sys.stdout.write(f"{SignVar.info} ({prompt}) {logRoller.loggingSkipped}\n")
# Create .csv for organization' repositories
def logOrgRepos(repository, organization):
org_repo_fields = [ 'Name','ID', 'About', 'Forks', 'Stars', 'Watchers', 'License', 'Branch', 'Visibility', 'Language(s)', 'Open issues', 'Topics', 'Homepage', 'Clone URL', 'SSH URL', 'Is fork?', 'Is forkable?', 'Is private?', 'Is archived?', 'Is template?', 'Has wiki?', 'Has pages?', 'Has projects?', 'Has issues?', 'Has downloads?', 'Pushed at', 'Created at', 'Updated at']
org_repo_row = [repository['full_name'], repository['id'], repository['description'], repository['forks'], repository['stargazers_count'], repository['watchers'], repository['license'], repository['default_branch'], repository['visibility'], repository['language'], repository['open_issues'], repository['topics'], repository['homepage'], repository['clone_url'], repository['ssh_url'], repository['fork'], repository['allow_forking'], repository['private'], repository['archived'], repository['is_template'], repository['has_wiki'], repository['has_pages'], repository['has_projects'], repository['has_issues'], repository['has_downloads'], repository['pushed_at'], repository['created_at'], repository['updated_at']]
prompt = input(f'\n{SignVar.prompt} {logRoller.askLogCsv}').lower()
if prompt == 'y':
with open(f"output/{repository['name']}_repository_of_{organization}.csv", 'w') as file:
writecsv = csv.writer(file)
writecsv.writerow(org_repo_fields)
writecsv.writerow(org_repo_row)
logging.info(logRoller.loggedToCsv.format(file.name))
sys.stdout.write(f"{SignVar.positive} {logRoller.loggedToCsv.format(file.name)}\n")
else:
logging.info(logRoller.loggingSkipped)
sys.stdout.write(f"{SignVar.info} ({prompt}) {logRoller.loggingSkipped}\n")
# .csv for user' repositories
def logUserRepos(repository, username):
user_repo_fields = [ 'Name','ID', 'About', 'Forks', 'Stars', 'Watchers', 'License', 'Branch', 'Visibility', 'Language(s)', 'Open issues', 'Topics', 'Homepage', 'Clone URL', 'SSH URL', 'Is fork?', 'Is forkable?', 'Is private?', 'Is archived?', 'Is template?', 'Has wiki?', 'Has pages?', 'Has projects?', 'Has issues?', 'Has downloads?', 'Pushed at', 'Created at', 'Updated at']
user_repo_row = [repository['full_name'], repository['id'], repository['description'], repository['forks'], repository['stargazers_count'], repository['watchers'], repository['license'], repository['default_branch'], repository['visibility'], repository['language'], repository['open_issues'], repository['topics'], repository['homepage'], repository['clone_url'], repository['ssh_url'], repository['fork'], repository['allow_forking'], repository['private'], repository['archived'], repository['is_template'], repository['has_wiki'], repository['has_pages'], repository['has_projects'], repository['has_issues'], repository['has_downloads'], repository['pushed_at'], repository['created_at'], repository['updated_at']]
prompt = input(f'\n{SignVar.prompt} {logRoller.askLogCsv}').lower()
if prompt == 'y':
with open(f"output/{repository['name']}_{username}.csv", 'w') as file:
writecsv = csv.writer(file)
writecsv.writerow(user_repo_fields)
writecsv.writerow(user_repo_row)
logging.info(logRoller.loggedToCsv.format(file.name))
sys.stdout.write(f"{SignVar.positive} {logRoller.loggedToCsv.format(file.name)}\n")
else:
logging.info(logRoller.loggingSkipped)
sys.stdout.write(f"{SignVar.info} ({prompt}) {logRoller.loggingSkipped}\n")
# .csv for user events
def logUserEvents(event):
user_event_fields = ['Actor', 'Type', 'Repository', 'Created at', 'Payload']
user_event_row = [event['actor']['login'], event['type'], event['repo']['name'], event['created_at'], event['payload']]
prompt = input(f'\n{SignVar.prompt} {logRoller.askLogCsv}').lower()
if prompt == 'y':
with open(f"output/{event['actor']['login']}_event_{event['id']}.csv", 'w') as file:
writecsv = csv.writer(file)
writecsv.writerow(user_event_fields)
writecsv.writerow(user_event_row)
logging.info(logRoller.loggedToCsv.format(file.name))
sys.stdout.write(f"{SignVar.positive} {logRoller.loggedToCsv.format(file.name)}\n")
else:
logging.info(logRoller.loggingSkipped)
sys.stdout.write(f"{SignVar.info} ({prompt}) {logRoller.loggingSkipped}\n")
# .csv for user gists
def logUserGists(gist):
user_gist_fields = ['ID', 'Node ID', 'About', 'Comments', 'Files', 'Git Push URL', 'Is public?', 'Is truncated?', 'Updated at']
user_gist_row = [gist['id'], gist['node_id'], gist['description'], gist['comments'], gist['files'], gist['git_push_url'], gist['public'], gist['truncated'], gist['updated_at']]
prompt = input(f'\n{SignVar.prompt} {logRoller.askLogCsv}').lower()
if prompt == 'y':
with open(f"output/{gist['id']}_gists_{gist['owner']['login']}.csv", 'w') as file:
writecsv = csv.writer(file)
writecsv.writerow(user_gist_fields)
writecsv.writerow(user_gist_row)
logging.info(logRoller.loggedToCsv.format(file.name))
sys.stdout.write(f"{SignVar.positive} {logRoller.loggedToCsv.format(file.name)}\n")
else:
logging.info(logRoller.loggingSkipped)
sys.stdout.write(f"{SignVar.info} ({prompt}) {logRoller.loggingSkipped}\n")
# .csv for user followers
def logUserFollowers(follower, username):
user_follower_fields = ['Profile photo', 'Username', 'ID', 'Node ID', 'Gravatar ID', 'Account type', 'Is site admin?', 'URL']
user_follower_row = [follower['avatar_url'], follower['login'], follower['id'], follower['node_id'], follower['gravatar_id'], follower['type'], follower['site_admin'], follower['html_url']]
prompt = input(f'\n{SignVar.prompt} {logRoller.askLogCsv}').lower()
if prompt == 'y':
with open(f"output/{follower['login']}_follower_of_{username}.csv", 'w') as file:
writecsv = csv.writer(file)
writecsv.writerow(user_follower_fields)
writecsv.writerow(user_follower_row)
logging.info(logRoller.loggedToCsv.format(file.name))
sys.stdout.write(f"{SignVar.positive} {logRoller.loggedToCsv.format(file.name)}\n")
else:
logging.info(logRoller.loggingSkipped)
sys.stdout.write(f"{SignVar.info} ({prompt}) {logRoller.loggingSkipped}\n")
# .csv for user following
def logUserFollowing(user, username):
user_following_fields = ['Profile photo', 'Username', 'ID', 'Node ID', 'Gravatar ID', 'Account type', 'Is site admin?', 'URL']
user_following_row = [user['avatar_url'], user['login'], user['id'], user['node_id'], user['gravatar_id'], user['type'], user['site_admin'], user['html_url']]
prompt = input(f'\n{SignVar.prompt} {logRoller.askLogCsv}').lower()
if prompt == 'y':
with open(f"output/{user['login']}_followed_by_{username}.csv", 'w') as file:
writecsv = csv.writer(file)
writecsv.writerow(user_following_fields)
writecsv.writerow(user_following_row)
logging.info(logRoller.loggedToCsv.format(file.name))
sys.stdout.write(f"{SignVar.positive} {logRoller.loggedToCsv.format(file.name)}\n")
else:
logging.info(logRoller.loggingSkipped)
sys.stdout.write(f"{SignVar.info} ({prompt}) {logRoller.loggingSkipped}\n")
# .csv for user' subscriptions
def logUserSubscriptions(repository, username):
user_subscription_fields = [ 'Name','ID', 'About', 'Forks', 'Stars', 'Watchers', 'License', 'Branch', 'Visibility', 'Language(s)', 'Open issues', 'Topics', 'Homepage', 'Clone URL', 'SSH URL', 'Is fork?', 'Is forkable?', 'Is private?', 'Is archived?', 'Is template?', 'Has wiki?', 'Has pages?', 'Has projects?', 'Has issues?', 'Has downloads?', 'Pushed at', 'Created at', 'Updated at']
user_subscription_row = [repository['name'], repository['id'], repository['description'], repository['forks'], repository['stargazers_count'], repository['watchers'], repository['license'], repository['default_branch'], repository['visibility'], repository['language'], repository['open_issues'], repository['topics'], repository['homepage'], repository['clone_url'], repository['ssh_url'], repository['fork'], repository['allow_forking'], repository['private'], repository['archived'], repository['is_template'], repository['has_wiki'], repository['has_pages'], repository['has_projects'], repository['has_issues'], repository['has_downloads'], repository['pushed_at'], repository['created_at'], repository['updated_at']]
prompt = input(f'\n{SignVar.prompt} {logRoller.askLogCsv}').lower()
if prompt == 'y':
with open(f"output/{username}_subscriptions_{repository['name']}.csv", 'w') as file:
writecsv = csv.writer(file)
writecsv.writerow(user_subscription_fields)
writecsv.writerow(user_subscription_row)
logging.info(logRoller.loggedToCsv.format(file.name))
sys.stdout.write(f"{SignVar.positive} {logRoller.loggedToCsv.format(file.name)}\n")
else:
logging.info(logRoller.loggingSkipped)
sys.stdout.write(f"{SignVar.info} ({prompt}) {logRoller.loggingSkipped}\n")
# .csv for user organizations
def logUserOrgs(organization, username):
user_org_fields = ['Profile photo', 'Name', 'ID', 'Node ID', 'URL', 'About']
user_org_row = [organization['avatar_url'], organization['login'], organization['id'], organization['node_id'], organization['url'], organization['description']]
prompt = input(f'\n{SignVar.prompt} {logRoller.askLogCsv}').lower()
if prompt == 'y':
with open(f"output/{organization['login']}_{username}.csv", 'w') as file:
writecsv = csv.writer(file)
writecsv.writerow(user_org_fields)
writecsv.writerow(user_org_row)
logging.info(logRoller.loggedToCsv.format(file.name))
sys.stdout.write(f"{SignVar.positive} {logRoller.loggedToCsv.format(file.name)}\n")
else:
logging.info(logRoller.loggingSkipped)
sys.stdout.write(f"{SignVar.info} ({prompt}) {logRoller.loggingSkipped}\n")
# Create .csv for user search
def logUserSearch(user, query):
user_search_fields = ['Profile photo', 'Username', 'ID', 'Node ID', 'Gravatar ID', 'Account type', 'Is site admin?', 'URL']
user_search_row = [user['avatar_url'], user['login'], user['id'], user['node_id'], user['gravatar_id'], user['type'], user['site_admin'], user['html_url']]
prompt = input(f'\n{SignVar.prompt} {logRoller.askLogCsv}').lower()
if prompt == 'y':
with open(f"output/{user['login']}_user_search_result_for_{query}.csv", 'w') as file:
writecsv = csv.writer(file)
writecsv.writerow(user_search_fields)
writecsv.writerow(user_search_row)
logging.info(logRoller.loggedToCsv.format(file.name))
sys.stdout.write(f"{SignVar.positive} {logRoller.loggedToCsv.format(file.name)}\n")
else:
logging.info(logRoller.loggingSkipped)
sys.stdout.write(f"{SignVar.info} ({prompt}) {logRoller.loggingSkipped}\n")
# Create .csv for repository search
def logRepoSearch(repository, query):
repo_search_fields = [ 'Name','ID', 'About', 'Forks', 'Stars', 'Watchers', 'License', 'Branch', 'Visibility', 'Language(s)', 'Open issues', 'Topics', 'Homepage', 'Clone URL', 'SSH URL', 'Is fork?', 'Is forkable?', 'Is private?', 'Is archived?', 'Is template?', 'Has wiki?', 'Has pages?', 'Has projects?', 'Has issues?', 'Has downloads?', 'Pushed at', 'Created at', 'Updated at']
repo_search_row = [repository['full_name'], repository['id'], repository['description'], repository['forks'], repository['stargazers_count'], repository['watchers'], repository['license'], repository['default_branch'], repository['visibility'], repository['language'], repository['open_issues'], repository['topics'], repository['homepage'], repository['clone_url'], repository['ssh_url'], repository['fork'], repository['allow_forking'], repository['private'], repository['archived'], repository['is_template'], repository['has_wiki'], repository['has_pages'], repository['has_projects'], repository['has_issues'], repository['has_downloads'], repository['pushed_at'], repository['created_at'], repository['updated_at']]
prompt = input(f'\n{SignVar.prompt} {logRoller.askLogCsv}').lower()
if prompt == 'y':
with open(f"output/{repository['name']}_repository_search_result_for_{query}.csv", 'w') as file:
writecsv = csv.writer(file)
writecsv.writerow(repo_search_fields)
writecsv.writerow(repo_search_row)
logging.info(logRoller.loggedToCsv.format(file.name))
sys.stdout.write(f"{SignVar.positive} {logRoller.loggedToCsv.format(file.name)}\n")
else:
logging.info(logRoller.loggingSkipped)
sys.stdout.write(f"{SignVar.info} ({prompt}) {logRoller.loggingSkipped}\n")
# Create .csv for topic search
def logTopicSearch(topic, query):
topic_search_fields = ['Name', 'Score', 'Curated', 'Featured', 'Display name', 'Created by', 'Created at', 'Updated at']
topic_search_row = [topic['name'], topic['score'], topic['curated'], topic['featured'], topic['display_name'], topic['created_by'], topic['created_at'], topic['updated_at']]
prompt = input(f'\n{SignVar.prompt} {logRoller.askLogCsv}').lower()
if prompt == 'y':
with open(f"output/{topic['name']}_topic_search_result_for_{query}.csv", 'w') as file:
writecsv = csv.writer(file)
writecsv.writerow(topic_search_fields)
writecsv.writerow(topic_search_row)
logging.info(logRoller.loggedToCsv.format(file.name))
sys.stdout.write(f"{SignVar.positive} {logRoller.loggedToCsv.format(file.name)}\n")
else:
logging.info(logRoller.loggingSkipped)
sys.stdout.write(f"{SignVar.info} ({prompt}) {logRoller.loggingSkipped}\n")
# Create .csv for issues search
def logIssueSearch(issue, query):
issue_search_fields = ['Title', 'ID', 'Node ID', 'Number', 'State', 'Reactions', 'Comments', 'Milestone', 'Assignee', 'Assignees', 'Author association', 'Labels', 'Is locked?', 'Lock reason', 'Closed at', 'Created at', 'Updated at']
issue_search_row = [issue['title'], issue['id'], issue['node_id'], issue['number'], issue['state'], issue['reactions'], issue['comments'], issue['milestone'], issue['assignee'], issue['assignees'], issue['author_association'], issue['labels'], issue['locked'], issue['active_lock_reason'], issue['closed_at'], issue['created_at'], issue['updated_at']]
prompt = input(f'\n{SignVar.prompt} {logRoller.askLogCsv}').lower()
if prompt == 'y':
with open(f"output/{issue['id']}_issue_search_result_for_{query}.csv", 'w') as file:
writecsv = csv.writer(file)
writecsv.writerow(issue_search_fields)
writecsv.writerow(issue_search_row)
logging.info(logRoller.loggedToCsv.format(file.name))
sys.stdout.write(f"{SignVar.positive} {logRoller.loggedToCsv.format(file.name)}\n")
else:
logging.info(logRoller.loggingSkipped)
sys.stdout.write(f"{SignVar.info} ({prompt}) {logRoller.loggingSkipped}\n")
# Create .csv for commits search
def logCommitsSearch(commit, query):
commit_search_fields = ['SHA', 'Author', 'Username', 'Email', 'Committer', 'Repository', 'URL', 'Description']
commit_search_row = [commit['commit']['tree']['sha'], commit['commit']['author']['name'], commit['author']['login'], commit['commit']['author']['email'], commit['commit']['committer']['name'], commit['repository']['full_name'], commit['html_url'], commit['commit']['message']]
prompt = input(f'\n{SignVar.prompt} {logRoller.askLogCsv}').lower()
if prompt == 'y':
with open(f"output/{commit['commit']['tree']['sha']}_commit_search_result_for_{query}.csv", 'w') as file:
writecsv = csv.writer(file)
writecsv.writerow(commit_search_fields)
writecsv.writerow(commit_search_row)
logging.info(logRoller.loggedToCsv.format(file.name))
sys.stdout.write(f"{SignVar.positive} {logRoller.loggedToCsv.format(file.name)}\n")
else:
logging.info(logRoller.loggingSkipped)
sys.stdout.write(f"{SignVar.info} ({prompt}) {logRoller.loggingSkipped}\n")

231
octosuite/helper.py Normal file
View File

@@ -0,0 +1,231 @@
import sys
from octosuite.colors import Color
"""
Help
This class holds the help text for available.
Almost everything in the methods from this class is hard coded
"""
class Help:
usageText = 'Use {} to get started'
usageText1 = 'Use {} to view all available subcommands.'
def Org():
sys.stdout.write(f"""
{Color.white}Note:
-------
The '{Color.green}org{Color.white}' command works with subcommands.
{Help.usageText1.format('help:org')}{Color.reset}
""")
def Repo():
sys.stdout.write(f"""
{Color.white}Note:
-----
The '{Color.green}repo{Color.white}' command works with subcommands.
{Help.usageText1.format('help:repo')}{Color.reset}
""")
def User():
sys.stdout.write(f"""
{Color.white}Note:
-----
The '{Color.green}user{Color.white}' command works with subcommands.
{Help.usageText1.format('help:user')}{Color.reset}
""")
def Search():
sys.stdout.write(f"""
{Color.white}Note:
-----
The '{Color.green}search{Color.white}' command works with subcommands.
{Help.usageText1.format('help:search')}{Color.reset}
""")
def Source():
sys.stdout.write(f"""
{Color.white}Note:
-----
The '{Color.green}source{Color.white}' command works with subcommands.
{Help.usageText1.format('help:source')}{Color.reset}
""")
def Logs():
sys.stdout.write(f"""
{Color.white}Note:
-----
The '{Color.green}logs{Color.white}' command works with subcommands.
{Help.usageText1.format('help:logs')}{Color.reset}
""")
def Version():
sys.stdout.write(f"""
{Color.white}Note:
-----
The '{Color.green}version{Color.white}' command works with subcommands.
{Help.usageText1.format('help:version')}{Color.reset}
""")
def Csv():
sys.stdout.write(f"""
{Color.white}Note:
-----
The '{Color.green}csv{Color.white}' command works with subcommands.
{Help.usageText1.format('help:csv')}{Color.reset}
""")
def versionCommand():
sys.stdout.write(f"""
{Color.white}Version subcommands{Color.reset}
{'='*18}
{Help.usageText.format('version:<subcommand>')}
{Color.white}Command Description{Color.reset}
------- -----------
check Check for new release(s)
info Version info
""")
def sourceCommand():
sys.stdout.write(f"""
{Color.white}Source subcommands{Color.reset}
{'='*18}
{Help.usageText.format('source:<subcommand>')}
{Color.white}Command Description{Color.reset}
-------- -----------
zipball Download source code as zipball
tarball Download source code as tarball
""")
def searchCommand():
sys.stdout.write(f"""
{Color.white}Search subcommands{Color.reset}
{'='*18}
{Help.usageText.format('search:<subcommand>')}
{Color.white}Command Description{Color.reset}
------- -----------
users Search user(s)
repos Search repositor[yies]
topics Search topic(s)
issues Search issue(s)
commits Search commit(s)
""")
def userCommand():
sys.stdout.write(f"""
{Color.white}User subcommands{Color.reset}
{'='*17}
{Help.usageText.format('user:<subcommand>')}
{Color.white}Command Description{Color.reset}
------- -----------
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
follows Check if user(A) follows user(B)
followers Return a user's followers
following Return a list of users the target is following
subscriptions Return a user's subscriptions
""")
def orgCommand():
sys.stdout.write(f"""
{Color.white}Org subcommands{Color.reset}
{'='*16}
{Help.usageText.format('org:<subcommand>')}
{Color.white}Command Description{Color.reset}
------- -----------
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():
sys.stdout.write(f"""
{Color.white}Repo subcommands{Color.reset}
{'='*17}
{Help.usageText.format('repo:<subcommand>')}
{Color.white}Command Description{Color.reset}
------- -----------
profile Get a repository's info
issues Return a repository's issues
forks Return a repository's forks
releases Return a repository's releases
stargazers Return a repository's stargazers
pathcontents List contents in a path of a repository
""")
def logsCommand():
sys.stdout.write(f"""
{Color.white}Logs subcommands{Color.reset}
{'='*17}
{Help.usageText.format('logs:<subcommand>')}
{Color.white}Command Description{Color.reset}
------- -----------
view View logs
read Read log
delete Delete log
""")
def csvCommand():
sys.stdout.write(f"""
{Color.white}Csv subcommands{Color.reset}
{'='*17}
{Help.usageText.format('csv:<subcommand>')}
{Color.white}Command Description{Color.reset}
------- -----------
view View csv files
read Read csv
delete Delete csv
""")
def helpCommand():
sys.stdout.write(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' info
author Developer' info
{Color.white}Help subcommands{Color.reset}
{'='*16}
{Help.usageText.format('help:<subcommand>')}
{Color.white}Command Description{Color.reset}
------- -----------
csv (coming soon)
org List all organization investigation commands
logs List all logs management commands
repo List all repository investigation commands
user List all users investigation commands
search List all target discovery commands
source (beta) List all source code download commands (for developers)
version List all version management commands
""")

25
octosuite/log_roller.py Normal file
View File

@@ -0,0 +1,25 @@
'''
logRoller
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 logRoller:
Ctrl = 'Session terminated with {}.'
Error = 'An error occurred: {}'
sessionOpened = 'Opened new session on {}:{}'
sessionClosed = 'Session closed at {}.'
deletedLog = 'Deleted log: {}'
readingLog = 'Reading log: {}'
viewingLogs = 'Viewing logs...'
fileDownloading = 'Downloading:{}'
fileDownloaded = 'Downloaded:downloads/{}'
fileNotFound = 'File ({}) not found.'
infoNotFound = 'Information ({} - {} - {}) not found.'
repoNotFound = 'Repository ({}) not found.'
userNotFound = 'User (@{}) not found.'
orgNotFound = 'Organization (@{}) not found.'
repoOrUserNotFound = 'Repository or user not found ({} - @{}).'
askLogCsv = 'Do you wish to log this output to a .csv file? (Y/n) '
loggedToCsv = 'Output logged to {}'
loggingSkipped = '.csv logging skipped.'
limitInput = ' Limit {} output to how many? (1-100) '

941
octosuite/main.py Normal file
View File

@@ -0,0 +1,941 @@
#!usr/bin/python
import os
import csv
import sys
import json
import logging
import getpass
import requests
import platform
import subprocess
from pprint import pprint
from datetime import datetime
from octosuite.helper import Help
from octosuite.colors import Color
from octosuite.banner import Banner
from octosuite.sign_vars import SignVar
from octosuite.log_roller import logRoller
from octosuite.csv_loggers import csvLogger
global endpoint
global path_attrs
global path_attr_dict
global org_attrs
global org_attr_dict
global repo_attrs
global repo_attr_dict
global repo_releases_attrs
global repo_releases_attr_dict
global profile_attrs
global profile_attr_dict
global user_attrs
global user_attr_dict
global topic_attrs
global topic_attr_dict
global gists_attrs
global gists_attr_dict
global issue_attrs
global issue_attr_dict
global repo_issues_attrs
global repo_issues_attr_dict
global user_orgs_attrs
global user_orgs_attr_dict
global author_dict
# API endpoint
endpoint = 'https://api.github.com'
# Path attribute
path_attrs =['size','type','path','sha','html_url']
# Path attribute dictionary
path_attr_dict = {'size': 'Size (bytes)',
'type': 'Type',
'path': 'Path',
'sha': 'SHA',
'html_url': 'URL'}
# Organization attributes
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
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_repos': 'Repositories',
'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
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
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
repo_releases_attrs = ['id', 'node_id','tag_name','target_commitish','assets','draft','prerelease','created_at','published_at']
# Repo releases attribute dictionary
repo_releases_attr_dict = {'id': 'ID',
'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
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
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
user_attrs = ['avatar_url','id','node_id','gravatar_id','site_admin','type','html_url']
# User attribute dictionary
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
topic_attrs = ['score','curated','featured','display_name','created_by','created_at','updated_at']
# Topic attribute dictionary
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
gists_attrs = ['node_id','description','comments','files','git_push_url','public','truncated','updated_at']
# Gists attribute dictionary
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
issue_attrs = ['id','node_id','score','state','number','comments','milestone','assignee','assignees','labels','locked','draft','closed_at']
# Issue attribute dict
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'}
# Repo issues attributes
repo_issues_attrs = ['id','node_id','state', 'reactions','number','comments','milestone','assignee','active_lock_reason', 'author_association','assignees','labels','locked','closed_at','created_at','updated_at']
# Issue attribute dict
repo_issues_attr_dict = {'id': 'ID',
'node_id': 'Node ID',
'number': 'Number',
'state': 'State',
'reactions': 'Reactions',
'comments': 'Comments',
'milestone': 'Milestone',
'assignee': 'Assignee',
'assignees': 'Assignees',
'author_association': 'Author association',
'labels': 'Labels',
'locked': 'Is locked?',
'active_lock_reason': 'Lock reason',
'closed_at': 'Closed at',
'created_at': 'Created at',
'updated_at': 'Updated at'}
# User organizations attributes
user_orgs_attrs = ['avatar_url','id','node_id','url','description']
user_orgs_attr_dict = {'avatar_url': 'Profile Photo',
'id': 'ID',
'node_id': 'Node ID',
'url': 'URL',
'description': 'About'}
# Author dictionary
author_dict = {'Alias': 'rly0nheart',
'Country': 'Zambia, Africa',
'About.me': 'https://about.me/rly0nheart',
'BuyMeACoffee': 'https://buymeacoffee.com/189381184'}
'''
pathFinder()
This function is responsible for creating/checking the availability of the (.logs, output, downloads) folders,
enabling logging to automatically log network/user activity to a file,
and logging the start of a session.
'''
def pathFinder():
'''
Windows based machines
Here we check the existence of 3 directories
If the directories exist, we ignore them.
If not, we create them.
'''
if sys.platform.lower().startswith(('win', 'darwin')):
if os.path.exists('.logs'):
pass
else:
subprocess.run(['mkdir','.logs'])
if os.path.exists('output'):
pass
else:
subprocess.run(['mkdir','output'])
if os.path.exists('downloads'):
pass
else:
subprocess.run(['mkdir','.downloads'])
else:
'''
Here we do the same as above,
except we are not creating on windows based machines
'''
if os.path.exists('.logs'):
pass
else:
subprocess.run(['sudo','mkdir','.logs'], shell=False)
if os.path.exists('output'):
pass
else:
subprocess.run(['sudo','mkdir','output'], shell=False)
if os.path.exists('downloads'):
pass
else:
subprocess.run(['sudo', 'mkdir', 'downloads'], shell=False)
'''
Configure logging to log activities to a file, which will be named by the date and time a session was opened.
'''
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)
# Log the start of a session
logging.info(logRoller.sessionOpened.format(platform.node(), getpass.getuser()))
'''
onStart()
This is the main function, responsible for mapping commands, calling other functions, and catching exceptions
'''
def onStart():
pathFinder()
# A list of tuples mapping commands to their functions
command_map = [('org', Help.Org),
('org:events', orgEvents),
('org:profile', orgProfile),
('org:repos', orgRepos),
('org:member', orgMember),
('repo', Help.Repo),
('repo:pathcontents', pathContents),
('repo:profile', repoProfile),
('repo:contributors', repoContributors),
('repo:stargazers', repoStargazers),
('repo:forks', repoForks),
('repo:issues', repoIssues),
('repo:releases', repoReleases),
('user', Help.User),
('user:repos', userRepos),
('user:gists', userGists),
('user:orgs', userOrgs),
('user:profile', userProfile),
('user:events', userEvents),
('user:followers', userFollowers),
('user:follows', userFollows),
('user:following', userFollowing),
('user:subscriptions', userSubscriptions),
('search', Help.Search),
('search:users', userSearch),
('search:repos', repoSearch),
('search:topics', topicSearch),
('search:issues', issueSearch),
('search:commits', commitsSearch),
('source', Help.Source),
('source:tarball', downloadTarball),
('source:zipball', downloadZipball),
('logs', Help.Logs),
('logs:view',viewLogs),
('logs:read',readLog),
('logs:delete',deleteLog),
('help', Help.helpCommand),
('help:version', Help.versionCommand),
('help:source', Help.sourceCommand),
('help:search', Help.searchCommand),
('help:user', Help.userCommand),
('help:repo', Help.repoCommand),
('help:logs', Help.logsCommand),
('help:org', Help.orgCommand),
('author', author),
('about', about),
('clear',clearScreen),
('version', Help.Version),
('version:info', versionInfo),
('version:check', versionCheck),
('exit', exitSession)]
print(Banner.nameLogo)
'''
Main loop keeps octosuite running, this will break if Octosuite detects a KeyboardInterrupt (Ctrl+C)
or if the 'exit' command is entered.
'''
while True:
try:
command_input = input(f'''{Color.white}┌──({Color.red}{getpass.getuser()}{Color.white}@{Color.red}octosuite{Color.white})\n├──[{Color.green}~{os.getcwd()}{Color.white}]\n└╼{Color.reset} ''').lower()
print('\n')
'''
Iterating over the command_map and check if the user input matches any command in it [command_map],
if there's a match, we return its function. If no match is found, we ignore it.
'''
for command, function in command_map:
if command_input == command:
function()
print('\n')
else:
pass
# This catches the KeyboardInterrupt exception (Ctrl+C)
except KeyboardInterrupt:
logging.warning(logRoller.Ctrl.format('Ctrl+C'))
sys.stdout.write(f"{SignVar.warning} {logRoller.Ctrl}".format(f"{Color.red}Ctrl{Color.reset}+{Color.red}C{Color.reset}")+'\n');break
# This initially catches all exceptions (except the KeyboardInterrupt)
except Exception as e:
logging.error(logRoller.Error.format(e))
sys.stderr.write(f"{SignVar.error} {logRoller.Error}".format(f'{Color.red}{e}{Color.reset}')+'\n')
# Fetching organization info
def orgProfile():
organization = input(f'{Color.white}--> @{Color.green}Organization{Color.white} (username){Color.reset} ')
response = requests.get(f'{endpoint}/orgs/{organization}')
if response.status_code == 404:
sys.stdout.write(f"{SignVar.negative} {logRoller.orgNotFound.format(organization)}\n")
elif response.status_code == 200:
print(f"\n{Color.white}{response.json()['name']}{Color.reset}")
for attr in org_attrs:
print(f'{Color.white}├─ {org_attr_dict[attr]}: {Color.green}{response.json()[attr]}{Color.reset}')
csvLogger.logOrgProfile(response)
else:
pprint(response.json())
# Fetching user information
def userProfile():
username = input(f'{Color.white}--> @{Color.green}Username{Color.reset} ')
response = requests.get(f'{endpoint}/users/{username}')
if response.status_code == 404:
sys.stdout.write(f"{SignVar.negative} {logRoller.userNotFound.format(username)}\n")
elif response.status_code == 200:
print(f"\n{Color.white}{response.json()['name']}{Color.reset}")
for attr in profile_attrs:
print(f'{Color.white}├─ {profile_attr_dict[attr]}: {Color.green}{response.json()[attr]}{Color.reset}')
csvLogger.logUserProfile(response)
else:
pprint(response.json())
# Fetching repository information
def repoProfile():
repo_name = input(f'{Color.white}--> %{Color.green}Repository{Color.reset} ')
username = input(f'{Color.white}--> @{Color.green}Owner{Color.white} (username){Color.reset} ')
response = requests.get(f'{endpoint}/repos/{username}/{repo_name}')
if response.status_code == 404:
sys.stdout.write(f"{SignVar.negative} {logRoller.repoOrUserNotFound.format(repo_name, username)}\n")
elif response.status_code == 200:
print(f"\n{Color.white}{response.json()['full_name']}{Color.reset}")
for attr in repo_attrs:
print(f"{Color.white}├─ {repo_attr_dict[attr]}: {Color.green}{response.json()[attr]}{Color.reset}")
csvLogger.logRepoProfile(response)
else:
pprint(response.json())
# Get path contents
def pathContents():
repo_name = input(f'{Color.white}--> %{Color.green}Repository{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'{endpoint}/repos/{username}/{repo_name}/contents/{path_name}')
if response.status_code == 404:
sys.stdout.write(f"{SignVar.negative} {logRoller.infoNotFound.format(repo_name, username, path_name)}\n")
elif response.status_code == 200:
for content in response.json():
print(f"\n{Color.white}{content['name']}{Color.reset}")
for attr in path_attrs:
print(f'{Color.white}├─ {path_attr_dict[attr]}: {Color.green}{content[attr]}{Color.reset}')
csvLogger.logRepoPathContents(content, repo_name)
else:
pprint(response.json())
# repo contributors
def repoContributors():
repo_name = input(f'{Color.white}--> %{Color.green}Repository{Color.reset} ')
username = input(f'{Color.white}--> @{Color.green}Owner{Color.white} (username){Color.reset} ')
limit = input(SignVar.prompt+logRoller.limitInput.format('contributors'))
response = requests.get(f'{endpoint}/repos/{username}/{repo_name}/contributors?per_page={limit}')
if response.status_code == 404:
sys.stdout.write(f"{SignVar.negative} {logRoller.repoOrUserNotFound.format(repo_name, username)}\n")
elif response.status_code == 200:
for contributor in response.json():
print(f"\n{Color.white}{contributor['login']}{Color.reset}")
for attr in user_attrs:
print(f'{Color.white}├─ {user_attr_dict[attr]}: {Color.green}{contributor[attr]}{Color.reset}')
csvLogger.logRepoContributors(contributor, repo_name)
else:
pprint(response.json())
# repo stargazers
def repoStargazers():
repo_name = input(f'{Color.white}--> %{Color.green}Repository{Color.reset} ')
username = input(f'{Color.white}--> @{Color.green}Owner{Color.white} (username){Color.reset} ')
limit = input(SignVar.prompt+logRoller.limitInput.format('repository stargazers'))
response = requests.get(f'{endpoint}/repos/{username}/{repo_name}/stargazers?per_page={limit}')
if response.status_code == 404:
sys.stdout.write(f"{SignVar.negative} {logRoller.repoOrUserNotFound.format(repo_name, username)}\n")
elif response.json() == {}:
sys.stdout.write(f'{SignVar.negative} Repository ({repo_name}) does not have any stargazers.{Color.reset}\n')
elif response.status_code == 200:
for stargazer in response.json():
print(f"\n{Color.white}{stargazer['login']}{Color.reset}")
for attr in user_attrs:
print(f'{Color.white}├─ {user_attr_dict[attr]}: {Color.green}{stargazer[attr]}{Color.reset}')
csvLogger.logRepoStargazers(stargazer, repo_name)
else:
pprint(response.json())
# repo forks
def repoForks():
repo_name = input(f'{Color.white}--> %{Color.green}Repository{Color.reset} ')
username = input(f'{Color.white}--> @{Color.green}Owner{Color.white} (username){Color.reset} ')
limit = input(SignVar.prompt+logRoller.limitInput.format('repository forks'))
response = requests.get(f'{endpoint}/repos/{username}/{repo_name}/forks?per_page={limit}')
if response.status_code == 404:
sys.stdout.write(f"{SignVar.negative} {logRoller.repoOrUserNotFound.format(repo_name, username)}\n")
elif response.json() == {}:
sys.stdout.write(f'{SignVar.negative} Repository ({repo_name}) does not have forks.{Color.reset}\n')
elif response.status_code == 200:
count = 0
for fork in response.json():
count += 1
print(f"\n{Color.white}{fork['full_name']}{Color.reset}")
for attr in repo_attrs:
print(f'{Color.white}├─ {repo_attr_dict[attr]}: {Color.green}{fork[attr]}{Color.reset}')
csvLogger.logRepoForks(fork, count)
else:
pprint(response.json())
# Repo issues
def repoIssues():
repo_name = input(f'{Color.white}--> %{Color.green}Repository{Color.reset} ')
username = input(f'{Color.white}--> @{Color.green}Owner{Color.white} (username){Color.reset} ')
limit = input(SignVar.prompt+logRoller.limitInput.format('repository issues'))
response = requests.get(f'{endpoint}/repos/{username}/{repo_name}/issues?per_page={limit}')
if response.status_code == 404:
sys.stdout.write(f"{SignVar.negative} {logRoller.repoOrUserNotFound.format(repo_name, username)}\n")
elif response.json() == []:
sys.stdout.write(f'{SignVar.negative} Repository ({repo_name}) does not have open issues.{Color.reset}\n')
elif response.status_code == 200:
for issue in response.json():
print(f"\n{Color.white}{issue['title']}{Color.reset}")
for attr in repo_issues_attrs:
print(f'{Color.white}├─ {repo_issues_attr_dict[attr]}: {Color.green}{issue[attr]}{Color.reset}')
print(issue['body'])
csvLogger.logRepoIssues(issue, repo_name)
else:
pprint(response.json())
# Repo releases
def repoReleases():
repo_name = input(f'{Color.white}--> %{Color.green}Repository{Color.reset} ')
username = input(f'{Color.white}--> @{Color.green}Owner{Color.white} (username){Color.reset} ')
limit = input(SignVar.prompt+logRoller.limitInput.format('repository releases'))
response = requests.get(f'{endpoint}/repos/{username}/{repo_name}/releases?per_page={limit}')
if response.status_code == 404:
sys.stdout.write(f"{SignVar.negative} {logRoller.repoOrUserNotFound.format(repo_name, username)}\n")
elif response.json() == []:
sys.stdout.write(f"{SignVar.negative} Repository ({repo_name}) does not have releases.{Color.reset}\n")
elif response.status_code == 200:
for release in response.json():
print(f"\n{Color.white}{release['name']}{Color.reset}")
for attr in repo_releases_attrs:
print(f'{Color.white}├─ {repo_releases_attr_dict[attr]}: {Color.green}{release[attr]}{Color.reset}')
print(release['body'])
csvLogger.logRepoReleases(release, repo_name)
else:
pprint(response.json())
# Fetching organization repositories
def orgRepos():
organization = input(f'{Color.white}--> @{Color.green}Organization{Color.white} (username){Color.reset} ')
limit = input(SignVar.prompt+logRoller.limitInput.format('organization repositories'))
response = requests.get(f'{endpoint}/orgs/{organization}/repos?per_page={limit}')
if response.status_code == 404:
sys.stdout.write(f"{SignVar.negative} {logRoller.orgNotFound.format(organization)}\n")
elif response.status_code == 200:
for repository in response.json():
print(f"\n{Color.white}{repository['full_name']}{Color.reset}")
for attr in repo_attrs:
print(f"{Color.white}├─ {repo_attr_dict[attr]}: {Color.green}{repository[attr]}{Color.reset}")
csvLogger.logOrgRepos(repository, organization)
else:
pprint(response.json())
# organization events
def orgEvents():
organization = input(f"{Color.white}--> @{Color.green}Organization{Color.white} (username){Color.reset} ")
limit = input(SignVar.prompt+logRoller.limitInput.format('organization events'))
response = requests.get(f'{endpoint}/orgs/{organization}/events?per_page={limit}')
if response.status_code == 404:
sys.stdout.write(f"{SignVar.negative} {logRoller.orgNotFound.format(organization)}\n")
elif response.status_code == 200:
for event in response.json():
print(f"\n{Color.white}{event['id']}{Color.reset}")
print(f"{Color.white}├─ Type: {Color.green}{event['type']}{Color.reset}\n{Color.white}├─ Created at: {Color.green}{event['created_at']}{Color.reset}")
pprint(event['payload'])
csvLogger.logOrgEvents(event, organization)
else:
pprint(response.json())
# organization member
def orgMember():
organization = input(f"{Color.white}--> @{Color.green}Organization{Color.white} (username){Color.reset} ")
username = input(f'{Color.white}--> @{Color.green}Username{Color.reset} ')
response = requests.get(f'{endpoint}/orgs/{organization}/public_members/{username}')
if response.status_code == 204:
sys.stdout.write(f"{SignVar.positive} User ({username}) is a public member of the organization ({organization}){Color.reset}\n")
else:
sys.stdout.write(f"{SignVar.negative} {response.json()['message']}{Color.reset}\n")
# Fetching user repositories
def userRepos():
username = input(f'{Color.white}--> @{Color.green}Username{Color.reset} ')
limit = input(SignVar.prompt+logRoller.limitInput.format('repositories'))
response = requests.get(f'{endpoint}/users/{username}/repos?per_page={limit}')
if response.status_code == 404:
sys.stdout.write(f"{SignVar.negative} {logRoller.userNotFound.format(username)}\n")
elif response.status_code == 200:
for repository in response.json():
print(f"\n{Color.white}{repository['full_name']}{Color.reset}")
for attr in repo_attrs:
print(f"{Color.white}├─ {repo_attr_dict[attr]}: {Color.green}{repository[attr]}{Color.reset}")
csvLogger.logUserRepos(repository, username)
else:
pprint(response.json())
# Fetching user's gists
def userGists():
username = input(f'{Color.white}--> @{Color.green}Username{Color.reset} ')
limit = input(f'{SignVar.prompt+logRoller.limitInput.format("gists")}')
response = requests.get(f'{endpoint}/users/{username}/gists?per_page={limit}')
#pprint(response.json())
if response.json() == []:
sys.stdout.write(f'{SignVar.negative} User does not have gists.{Color.reset}\n')
elif response.status_code == 404:
sys.stdout.write(f"{SignVar.negative} {logRoller.userNotFound.format(username)}\n")
elif response.status_code == 200:
for gist in response.json():
print(f"\n{Color.white}{gist['id']}{Color.reset}")
for attr in gists_attrs:
print(f"{Color.white}├─ {gists_attr_dict[attr]}: {Color.green}{gist[attr]}{Color.reset}")
csvLogger.logUserGists(gist)
else:
pprint(response.json())
# Fetching a list of organizations that a user owns or belongs to
def userOrgs():
username = input(f'{Color.white}--> @{Color.green}Username{Color.reset} ')
limit = input(SignVar.prompt+logRoller.limitInput.format('user organizations'))
response = requests.get(f'{endpoint}/users/{username}/orgs?per_page={limit}')
if response.json() == []:
sys.stdout.write(f'{SignVar.negative} User ({username}) does not (belong to/own) any organizations.{Color.reset}\n')
elif response.status_code == 404:
sys.stdout.write(f"{SignVar.negative} {logRoller.userNotFound.format(username)}\n")
elif response.status_code == 200:
for organization in response.json():
print(f'\n{Color.white}{organization["login"]}{Color.reset}')
for attr in user_orgs_attrs:
print(f'{Color.white}├─ {user_orgs_attr_dict[attr]}: {Color.green}{organization[attr]}{Color.reset}')
csvLogger.logUserOrgs(organization, username)
else:
pprint(response.json())
# Fetching a users events
def userEvents():
username = input(f'{Color.white}--> @{Color.green}Username{Color.reset} ')
limit = input(SignVar.prompt+logRoller.limitInput.format('events'))
response = requests.get(f'{endpoint}/users/{username}/events/public?per_page={limit}')
if response.status_code == 404:
sys.stdout.write(f"{SignVar.negative} {logRoller.userNotFound.format(username)}\n")
elif response.status_code == 200:
for event in response.json():
print(f"\n{Color.white}{event['id']}{Color.reset}")
print(f"{Color.white}├─ Actor: {Color.green}{event['actor']['login']}{Color.reset}")
print(f"{Color.white}├─ Type: {Color.green}{event['type']}{Color.green}")
print(f"{Color.white}├─ Repository: {Color.green}{event['repo']['name']}{Color.reset}")
print(f"{Color.white}├─ Created at: {Color.green}{event['created_at']}{Color.reset}")
pprint(event['payload'])
csvLogger.logUserEvents(event)
else:
pprint(response.json())
# Fetching a target user's subscriptions
def userSubscriptions():
username = input(f'{Color.white}--> @{Color.green}Username{Color.reset} ')
limit = input(SignVar.prompt+logRoller.limitInput.format('user subscriptions'))
response = requests.get(f'{endpoint}/users/{username}/subscriptions?per_page={limit}')
if response.json() == []:
print(f"{SignVar.negative} User does not have any subscriptions.{Color.reset}\n")
elif response.status_code == 404:
sys.stdout.write(f"{SignVar.negative} {logRoller.userNotFound.format(username)}\n")
elif response.status_code == 200:
for repository in response.json():
print(f"\n{Color.white}{repository['full_name']}{Color.reset}")
for attr in repo_attrs:
print(f"{Color.white}├─ {repo_attr_dict[attr]}: {Color.green}{repository[attr]}{Color.reset}")
csvLogger.logUserSubscriptions(repository, username)
else:
pprint(response.json())
# Fetching a list of users the target follows
def userFollowing():
username = input(f'{Color.white}--> @{Color.green}Username{Color.reset} ')
limit = input(SignVar.prompt+logRoller.limitInput.format('user\' following'))
response = requests.get(f'{endpoint}/users/{username}/following?per_page={limit}')
if response.json() == []:
sys.stdout.write(f'{SignVar.negative} User ({username})does not follow anyone.{Color.reset}')
elif response.status_code == 404:
sys.stdout.write(f"{SignVar.negative} {logRoller.userNotFound.format(username)}\n")
elif response.status_code == 200:
for user in response.json():
print(f"\n{Color.white}@{user['login']}{Color.reset}")
for attr in user_attrs:
print(f"{Color.white}├─ {user_attr_dict[attr]}: {Color.green}{user[attr]}{Color.reset}")
csvLogger.logUserFollowing(user, username)
else:
pprint(response.json())
# Fetching user's followera'
def userFollowers():
username = input(f'{Color.white}--> @{Color.green}Username{Color.reset} ')
limit = input(SignVar.prompt+logRoller.limitInput.format('user followers'))
response = requests.get(f'{endpoint}/users/{username}/followers?per_page={limit}')
if response.json() == []:
sys.stdout.write(f'{SignVar.negative} User ({username})does not have followers.{Color.reset}')
elif response.status_code == 404:
sys.stdout.write(f"{SignVar.negative} {logRoller.userNotFound.format(username)}\n")
elif response.status_code == 200:
for follower in response.json():
print(f"\n{Color.white}@{follower['login']}{Color.reset}")
for attr in user_attrs:
print(f"{Color.white}├─ {user_attr_dict[attr]}: {Color.green}{follower[attr]}{Color.reset}")
csvLogger.logUserFollowers(follower, username)
else:
pprint(response.json())
# Checking whether or not user[A] follows user[B]
def userFollows():
user_a = input(f'{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'{endpoint}/users/{user_a}/following/{user_b}')
if response.status_code == 204:
sys.stdout.write(f'{SignVar.positive} @{user_a} FOLLOWS @{user_b}{Color.reset}\n')
else:
sys.stdout.write(f'{SignVar.negative} @{user_a} DOES NOT FOLLOW @{user_b}{Color.reset}\n')
# User search
def userSearch():
query = input(f'{Color.white}--> @{Color.green}Query{Color.white} (eg. john){Color.reset} ')
limit = input(SignVar.prompt+logRoller.limitInput.format('user search'))
response = requests.get(f'{endpoint}/search/users?q={query}&per_page={limit}').json()
for user in response['items']:
print(f"\n{Color.white}@{user['login']}{Color.reset}")
for attr in user_attrs:
print(f"{Color.white}├─ {user_attr_dict[attr]}: {Color.green}{user[attr]}{Color.reset}")
csvLogger.logUserSearch(user, query)
# Repository search
def repoSearch():
query = input(f'{Color.white}--> %{Color.green}Query{Color.white} (eg. git){Color.reset} ')
limit = input(SignVar.prompt+logRoller.limitInput.format('repositor[y][ies] search'))
response = requests.get(f'{endpoint}/search/repositories?q={query}&per_page={limit}').json()
for repository in response['items']:
print(f"\n{Color.white}{repository['full_name']}{Color.reset}")
for attr in repo_attrs:
print(f"{Color.white}├─ {repo_attr_dict[attr]}: {Color.green}{repository[attr]}{Color.reset}")
csvLogger.logRepoSearch(repository, query)
# Topics search
def topicSearch():
query = input(f'{Color.white}--> #{Color.green}Query{Color.white} (eg. osint){Color.reset} ')
limit = input(SignVar.prompt+logRoller.limitInput.format('topic(s) search'))
response = requests.get(f'{endpoint}/search/topics?q={query}&per_page={limit}').json()
for topic in response['items']:
print(f"\n{Color.white}{topic['name']}{Color.reset}")
for attr in topic_attrs:
print(f"{Color.white}├─ {topic_attr_dict[attr]}: {Color.green}{topic[attr]}{Color.reset}")
csvLogger.logTopicSearch(topic, query)
# Issue search
def issueSearch():
query = input(f'{Color.white}--> !{Color.green}Query{Color.white} (eg. error){Color.reset} ')
limit = input(SignVar.prompt+logRoller.limitInput.format('issue(s) search'))
response = requests.get(f'{endpoint}/search/issues?q={query}&per_page={limit}').json()
for issue in response['items']:
print(f"\n\n{Color.white}{issue['title']}{Color.reset}")
for attr in repo_issues_attrs:
print(f"{Color.white}├─ {repo_issues_attr_dict[attr]}: {Color.green}{issue[attr]}{Color.reset}")
print(issue['body'])
csvLogger.logIssueSearch(issue, query)
# Commits search
def commitsSearch():
query = input(f'{Color.white}--> :{Color.green}Query{Color.white} (eg. filename:index.php){Color.reset} ')
limit = input(SignVar.prompt+logRoller.limitInput.format('commit(s) search'))
response = requests.get(f'{endpoint}/search/commits?q={query}&per_page={limit}').json()
for commit in response['items']:
print(f"\n{Color.white}{commit['commit']['tree']['sha']}{Color.reset}")
print(f"{Color.white}├─ Author: {commit['commit']['author']['name']}{Color.reset}")
print(f"{Color.white}├─ Username: {Color.green}{commit['author']['login']}{Color.reset}")
print(f"{Color.white}├─ Email: {Color.green}{commit['commit']['author']['email']}{Color.reset}")
print(f"{Color.white}├─ Commiter: {Color.green}{commit['commit']['committer']['name']}{Color.reset}")
print(f"{Color.white}├─ Repository: {Color.green}{commit['repository']['full_name']}{Color.reset}")
print(f"{Color.white}├─ URL: {Color.green}{commit['html_url']}{Color.reset}")
pprint(commit['commit']['message'])
csvLogger.logCommitsSearch(commit, query)
# View octosuite log files
def viewLogs():
logging.info(logRoller.viewingLogs)
logs = os.listdir('.logs')
print(f'''{Color.white}
Log Size{Color.reset}
--- ---------''')
for log in logs:
print(f"{log}\t ",os.path.getsize(".logs/"+log),"bytes")
# Read a specified log file
def readLog():
log_file = input(f"{Color.white}--> .log date (eg. 2022-04-27 10:09:36AM){Color.reset} ")
with open(f'.logs/{log_file}.log', 'r') as log:
logging.info(logRoller.readingLog.format(log_file))
print("\n"+log.read())
# Delete a specified log file
def deleteLog():
log_file = input(f"{Color.white}--> .log date (eg. 2022-04-27 10:09:36AM){Color.reset} ")
if sys.platform.lower().startswith(('win','darwin')):
subprocess.run(['del',f'.logs\{log_file}.log'])
else:
subprocess.run(['sudo','rm',f'.logs/{log_file}.log'],shell=False)
logging.info(logRoller.deletedLog.format(log_file))
sys.stdout.write(f"{SignVar.positive} {logRoller.deletedLog.format(log_file)}\n")
# Downloading release tarball
def downloadTarball():
logging.info(logRoller.fileDownloading.format(f'octosuite.v{Banner.versionTag}.tar'))
sys.stdout.write(SignVar.info+' '+logRoller.fileDownloading.format(f'octosuite.v{Banner.versionTag}.tar')+'...\n')
data = requests.get(f'{endpoint}/repos/rly0nheart/octosuite/tarball/{Banner.versionTag}')
if data.status_code == 404:
logging.info(logRoller.tagNotFound.format(Banner.versionTag))
sys.stdout.write(f'{SignVar.negative} {logRoller.tagNotFound.format(Banner.versionTag)}\n')
else:
with open(f'downloads/octosuite.v{Banner.versionTag}.tar', 'wb') as file:
file.write(data.content)
file.close()
logging.info(logRoller.fileDownloaded.format(f'octosuite.v{Banner.versionTag}.tar'))
sys.stdout.write(SignVar.positive+' '+logRoller.fileDownloaded.format(f'octosuite.v{Banner.versionTag}.tar'))
# Downloading release zipball
def downloadZipball():
logging.info(logRoller.fileDownloading.format(f'octosuite.v{Banner.versionTag}.zip'))
sys.stdout.write(SignVar.info+' '+logRoller.fileDownloading.format(f'octosuite.v{Banner.versionTag}.zip')+'...\n')
data = requests.get(f'{endpoint}/repos/rly0nheart/octosuite/zipball/{Banner.versionTag}')
if data.status_code == 404:
logging.info(logRoller.tagNotFound.format(Banner.versionTag))
sys.sdtout.write(f'{SignVar.negative} {logRoller.tagNotFound.format(Banner.versionTag)}\n')
else:
with open(f'downloads/octosuite.v{Banner.versionTag}.zip', 'wb') as file:
file.write(data.content)
file.close()
logging.info(logRoller.fileDownloaded.format(f'octosuite.v{Banner.versionTag}.zip'))
sys.stdout.write(SignVar.positive+' '+logRoller.fileDownloaded.format(f'octosuite.v{Banner.versionTag}.zip'))
def versionCheck():
response = requests.get(f"{endpoint}/repos/rly0nheart/octosuite/releases/latest")
if response.json()['tag_name'] == Banner.versionTag:
sys.stdout.write(f"{SignVar.positive} Octosuite is up to date. Check again soon :)\n")
else:
sys.stdout.write(f"{SignVar.info} A new release is available (octosuite.v{response.json()['tag_name']}). Exit Octosuite and run '{Color.green}pip install --upgrade octosuite{Color.white}' to download and install the update.{Color.reset}\n")
# Author info
def author():
print(f'{Color.white}Richard Mwewa (Ritchie){Color.reset}')
for key,value in author_dict.items():
print(f'{Color.white}├─ {key}: {Color.green}{value}{Color.reset}')
def about():
sys.stdout.write('''
OCTOSUITE © 2022 Richard Mwewa
An advanced and lightning fast framework for gathering open-source intelligence on GitHub users and organizations.
Read the wiki: https://github.com/rly0nheart/octosuite/wiki
GitHub REST API documentation: https://docs.github.com/rest
''')
# Close session
def exitSession():
prompt = input(f'{SignVar.prompt} This will close the current session, continue? (Y/n) ').lower()
if prompt == 'y':
logging.info(logRoller.sessionClosed.format('exit'))
sys.stdout.write(f"{SignVar.info} {logRoller.sessionClosed.format(datetime.now())}\n");exit()
else:
pass
# Clear screen
def clearScreen():
'''
We use 'cls' on Windows machines to clear the screen,
otherwise, we use 'clear'
'''
if sys.platform.lower().startswith(('win','darwin')):
subprocess.run(['cls'])
else:
subprocess.run(['clear'],shell=False)
# Show version information
def versionInfo():
'''
Yes... the changelog is hard coded
It's actually frustrating having to change this everytime I publish a new release lol
'''
sys.stdout.write(f'''
OCTOSUITE.v{Banner.versionTag}
What's changed?
{'='*15}
[fix] error in source commands (source:tarball, source:zipball)
''')

15
octosuite/sign_vars.py Normal file
View File

@@ -0,0 +1,15 @@
from octosuite.colors import Color
'''
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 SignVar:
prompt = f'{Color.white}[{Color.green} ? {Color.white}]{Color.reset}'
warning = f'{Color.white}[{Color.red} ! {Color.white}]{Color.reset}'
error = f'{Color.white}[{Color.red} x {Color.white}]{Color.reset}'
positive = f'{Color.white}[{Color.green} + {Color.white}]{Color.reset}'
negative = f'{Color.white}[{Color.red} - {Color.white}]{Color.reset}'
info = f'{Color.white}[{Color.green} * {Color.white}]{Color.reset}'

View File

@@ -1,2 +0,0 @@
tqdm
requests

31
setup.py Normal file
View File

@@ -0,0 +1,31 @@
import setuptools
with open("README.md", "r", encoding="utf-8") as file:
long_description = file.read()
setuptools.setup(
name="octosuite",
version="2.1.1",
author="Richard Mwewa",
author_email="richardmwewa@duck.com",
packages=["octosuite"],
description="Advanced Github OSINT Framework",
long_description=long_description,
long_description_content_type="text/markdown",
url="https://github.com/rly0nheart/octosuite",
license="GNU General Public License v3 (GPLv3)",
install_requires=["requests"],
classifiers=[
'Development Status :: 5 - Production/Stable',
'Intended Audience :: Information Technology',
'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
'Operating System :: OS Independent',
'Natural Language :: English',
'Programming Language :: Python :: 3'
],
entry_points={
"console_scripts": [
"octosuite=octosuite.main:onStart",
]
},
)

View File

@@ -1,118 +0,0 @@
from utilities.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''')

View File

@@ -1,20 +0,0 @@
import getpass
from utilities.colors import Color
class Banner:
versionTag = '2.0.1-alpha'
nameLogo = f'''{Color.white}
_______ __ _______ __ __
| |.----.| |_.-----.| __|.--.--.|__| |_.-----.
| - || __|| _| _ ||__ || | || | _| -__|
|_______||____||____|_____||_______||_____||__|____|_____|
v{versionTag}
{Color.white}— Advanced Github {Color.red}OSINT{Color.white} Framework{Color.reset}
.:{Color.white}{Color.green}{getpass.getuser()}{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}
'''