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