Compare commits

...

157 Commits
1.4.0 ... 2.2.2

Author SHA1 Message Date
Richard Mwewa
8eea84e546 Update README.md 2022-08-26 13:04:12 +02:00
Richard Mwewa
acd65e7919 Update README.md 2022-08-26 13:03:26 +02:00
Richard Mwewa
4a7b199929 Add files via upload 2022-08-26 12:40:37 +02:00
Richard Mwewa
2723e75735 Update banner.py 2022-08-26 12:36:29 +02:00
Richard Mwewa
0365616478 Update main.py 2022-08-26 12:35:49 +02:00
Richard Mwewa
befd61eea0 Update setup.py 2022-08-26 12:30:18 +02:00
Richard Mwewa
daa426d73b Add files via upload 2022-08-20 16:35:06 +02:00
Richard Mwewa
22df1eae73 Add files via upload 2022-08-20 15:26:35 +02:00
Richard Mwewa
51c1efbcec Update README.md 2022-08-17 11:20:09 +02:00
Richard Mwewa
d091125c15 Update setup.py 2022-07-08 12:01:27 +02:00
Richard Mwewa
e2c94fcc89 Update banner.py 2022-07-08 12:00:15 +02:00
Richard Mwewa
16f9a80818 Update main.py 2022-07-08 11:58:49 +02:00
Richard Mwewa
66d7a2c42c Update README.md 2022-07-08 11:52:42 +02:00
Richard Mwewa
456d46fdb1 Create README.md 2022-07-02 22:10:33 +02:00
Richard Mwewa
539c540a72 Update README.md 2022-07-02 22:09:46 +02:00
Richard Mwewa
db413767cd Update setup.py 2022-07-02 22:05:53 +02:00
Richard Mwewa
6a87cb51de Update sign_vars.py 2022-07-02 22:02:58 +02:00
Richard Mwewa
f98bfb34f3 Update log_roller.py 2022-07-02 21:57:53 +02:00
Richard Mwewa
43637193de Update helper.py 2022-07-02 21:56:16 +02:00
Richard Mwewa
33d9c7f5f2 Update csv_loggers.py 2022-07-02 21:53:25 +02:00
Richard Mwewa
835a78284b Update colors.py 2022-07-02 21:48:30 +02:00
Richard Mwewa
4dc2dced32 Update banner.py 2022-07-02 21:45:54 +02:00
Richard Mwewa
4b18c0307e Update main.py 2022-07-02 21:42:24 +02:00
Richard Mwewa
0ec3100347 Update README.md 2022-06-28 13:51:59 +02:00
Richard Mwewa
bb287acfe2 Update README.md 2022-06-28 13:51:01 +02:00
Richard Mwewa
7a6015b8e4 Update README.md 2022-06-28 13:40:51 +02:00
Richard Mwewa
d0f5c660b5 Update README.md 2022-06-28 01:03:06 +02:00
Richard Mwewa
2e65cfcb79 Update README.md 2022-06-28 01:00:15 +02:00
Richard Mwewa
41f392f5a0 Update README.md 2022-06-28 00:53:36 +02:00
Richard Mwewa
18bcc06070 Create codeql.yml 2022-06-28 00:34:21 +02:00
Richard Mwewa
c9cf000b23 Create .gitignore 2022-06-26 21:46:54 +02:00
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
Richard Mwewa
62a9556432 Update misc.py 2022-05-25 11:17:52 +02:00
Richard Mwewa
705966135d Update README.md 2022-05-25 11:10:27 +02:00
Richard Mwewa
85e5c28a33 Add files via upload 2022-05-25 11:09:51 +02:00
Richard Mwewa
19165e1d48 Create .images 2022-05-25 11:09:15 +02:00
Richard Mwewa
c669aa87af Delete img directory 2022-05-25 11:07:57 +02:00
Richard Mwewa
7d0c462842 Update main.py 2022-05-25 11:06:40 +02:00
Richard Mwewa
10e2d2dae7 Update octosuite 2022-05-25 11:01:00 +02:00
Richard Mwewa
d0f9ff2dd0 Delete utils directory 2022-05-25 10:59:40 +02:00
Richard Mwewa
c88ec5f5ed Create misc.py 2022-05-25 10:58:47 +02:00
Richard Mwewa
161da5f5be Create helper.py 2022-05-25 10:55:29 +02:00
Richard Mwewa
5dd0ce300d Update and rename utils/colors.py to utilities/colors.py 2022-05-25 10:52:43 +02:00
Richard Mwewa
89b98b7281 b 2022-05-24 10:19:09 +02:00
Richard Mwewa
3e750bced7 Update README.md 2022-05-20 17:30:44 +02:00
Richard Mwewa
8de7b0d939 Update misc.py 2022-05-20 17:00:28 +02:00
Richard Mwewa
6afc37d9a4 Update octosuite 2022-05-20 16:52:52 +02:00
Richard Mwewa
b030d48152 Create helper.py 2022-05-20 16:51:07 +02:00
Richard Mwewa
508df0ea77 Create colors.py 2022-05-20 16:48:52 +02:00
Richard Mwewa
fb7ad2cda8 Delete colors.py 2022-05-20 16:47:57 +02:00
Richard Mwewa
b16eb762e0 Update and rename lib/banner.py to utils/misc.py 2022-05-20 16:46:34 +02:00
Richard Mwewa
8b7ac9e428 Update and rename src/main.py to core/main.py 2022-05-20 16:43:49 +02:00
Richard Mwewa
66e0da38b6 Update README.md 2022-05-20 16:39:46 +02:00
Richard Mwewa
fbf7e40404 Update README.md 2022-05-19 21:59:51 +02:00
Richard Mwewa
73bcb68012 Delete .img 2022-05-19 21:56:59 +02:00
Richard Mwewa
850d9bb5ab Add files via upload 2022-05-19 21:52:40 +02:00
Richard Mwewa
17cfb5920c Create .img 2022-05-19 21:51:44 +02:00
Richard Mwewa
5ffb7758a9 Update README.md 2022-05-15 21:20:47 +02:00
Richard Mwewa
2d6a808100 Update README.md 2022-05-06 14:14:25 +02:00
Richard Mwewa
02feed27a8 Update README.md 2022-05-04 17:25:01 +02:00
Richard Mwewa
77ddfa4b02 Update README.md 2022-04-30 10:32:31 +02:00
Richard Mwewa
66192c2a63 Create .log 2022-04-30 10:30:49 +02:00
Richard Mwewa
f591764343 Update banner.py 2022-04-30 10:28:50 +02:00
Richard Mwewa
f6dbd8ec49 Update main.py 2022-04-30 10:27:11 +02:00
Richard Mwewa
99deaf04f3 Update main.py 2022-04-29 23:53:10 +02:00
Richard Mwewa
56eead2f26 Update README.md 2022-04-29 23:46:12 +02:00
Richard Mwewa
73e7c339e6 Update README.md 2022-04-29 23:26:32 +02:00
Richard Mwewa
3cd6442d91 Update README.md 2022-04-28 01:33:21 +02:00
Richard Mwewa
7ebb90790a Update banner.py 2022-04-28 01:10:39 +02:00
Richard Mwewa
ca0d2dc268 Update main.py 2022-04-28 01:08:26 +02:00
Richard Mwewa
51babb3c45 Update README.md 2022-04-27 17:09:33 +02:00
Richard Mwewa
12e9dbcae9 Update main.py 2022-04-27 15:31:31 +02:00
Richard Mwewa
be4e88d99b Update banner.py 2022-04-27 15:29:53 +02:00
Richard Mwewa
f7ac0fb8d5 Update colors.py 2022-04-27 15:28:07 +02:00
Richard Mwewa
fc7a65c593 Update octosuite 2022-04-27 15:25:15 +02:00
Richard Mwewa
51f2a46c65 Delete tmp 2022-04-23 14:10:18 +02:00
Richard Mwewa
4afbc5adaa Create tmp 2022-04-23 14:09:35 +02:00
Richard Mwewa
d7980c0726 Delete tmp 2022-04-23 13:33:01 +02:00
Richard Mwewa
f8221d8f58 Update main.py 2022-04-23 13:32:25 +02:00
Richard Mwewa
9d86c4cc7b Update README.md 2022-04-23 13:25:30 +02:00
Richard Mwewa
67db855322 Update banner.py 2022-04-23 13:18:28 +02:00
Richard Mwewa
1b5cfa2ebf Update colors.py 2022-04-23 13:17:37 +02:00
Richard Mwewa
0d2a939388 Update main.py 2022-04-23 13:16:04 +02:00
Richard Mwewa
52c572f255 Update README.md 2022-04-20 00:40:38 +02:00
Richard Mwewa
eeda47723e Update main.py 2022-04-20 00:32:49 +02:00
Richard Mwewa
1a6e3bb3c9 Update banner.py 2022-04-20 00:28:49 +02:00
Richard Mwewa
82b6a3fd3c Update colors.py 2022-04-20 00:27:55 +02:00
Richard Mwewa
c994678fa1 Update README.md 2022-04-18 14:19:37 +02:00
Richard Mwewa
9ef14f3e4f Update octosuite 2022-04-14 11:19:34 +02:00
Richard Mwewa
23270e8da9 Update octosuite 2022-04-14 11:15:18 +02:00
Richard Mwewa
619070348b Update banner.py 2022-04-14 11:13:01 +02:00
Richard Mwewa
5718c5f229 Update colors.py 2022-04-14 11:11:41 +02:00
Richard Mwewa
b4f009c7d4 Update main.py 2022-04-14 11:09:43 +02:00
Richard Mwewa
82a95d84b0 Update README.md 2022-04-14 11:07:42 +02:00
Richard Mwewa
425525bf94 Update README.md 2022-04-14 00:57:55 +02:00
Richard Mwewa
24c6b54d19 Update main.py 2022-04-11 13:13:57 +02:00
Richard Mwewa
6d1e163d9a Update LICENSE 2022-04-11 13:03:07 +02:00
Richard Mwewa
a8730864e9 Update README.md 2022-04-11 13:01:03 +02:00
Richard Mwewa
315cb7ec6f Update colors.py 2022-04-11 12:59:10 +02:00
Richard Mwewa
2afac6c0d4 Update banner.py 2022-04-11 12:58:23 +02:00
Richard Mwewa
b226f1b53b Update main.py 2022-04-11 12:56:19 +02:00
Richard Mwewa
bb38d95e57 Update README.md 2022-04-09 21:18:56 +02:00
Richard Mwewa
eae41866ec Create config.yml 2022-04-09 20:57:07 +02:00
Richard Mwewa
1e71c2d3bd Create feature_request.md 2022-04-09 20:42:46 +02:00
Richard Mwewa
d91f88d31b Create bug_report.md 2022-04-09 20:40:44 +02:00
Richard Mwewa
d5119a87b0 Update banner.py 2022-04-01 02:26:53 +02:00
Richard Mwewa
c4bd431620 Update main.py 2022-04-01 02:26:06 +02:00
32 changed files with 1918 additions and 612 deletions

38
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,38 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.

8
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: Richard Mwewa (Telegram)
url: https://t.me/rly0nheart/
about: Please ask and answer questions here.
- name: Richard Mwewa (Twitter)
url: https://m.twitter.com/rly0nheart/
about: Please report security vulnerabilities or bugs here.

View File

@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

72
.github/workflows/codeql.yml vendored Normal file
View File

@@ -0,0 +1,72 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ "master" ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ "master" ]
schedule:
- cron: '18 7 * * 3'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'python' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
steps:
- name: Checkout repository
uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
# - run: |
# echo "Run, Build Application using script"
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2

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 }}

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
__pycache__/

View File

@@ -1,3 +1,4 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
@@ -631,8 +632,8 @@ to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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

135
README.md
View File

@@ -1,98 +1,63 @@
![Screenshot_2022-03-17_10-12-53](https://user-images.githubusercontent.com/74001397/158868105-b5aba7e8-7342-4268-bd7a-6d6ae0bdae5a.png)
![logo](https://user-images.githubusercontent.com/74001397/175805580-fffc96d4-e0ef-48bb-a55c-80b2da3e714d.png)
[![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)
[![CodeQL](https://github.com/rly0nheart/octosuite/actions/workflows/codeql.yml/badge.svg)](https://github.com/rly0nheart/octosuite/actions/workflows/codeql.yml)
![GitHub](https://img.shields.io/github/license/rly0nheart/octosuite?style=flat)
![PyPI](https://img.shields.io/pypi/v/octosuite?style=flat&logo=pypi)
![PyPI - Downloads](https://img.shields.io/pypi/dw/octosuite?style=flat&logo=pypi)
![PyPI - Status](https://img.shields.io/pypi/status/octosuite?style=flat&logo=pypi)
![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🔥*
![octosuite_gui_exe (2)](https://user-images.githubusercontent.com/74001397/186889610-4530ee26-d3c6-46fc-8c92-8709f89617fd.png "Octosuite' about window")
![octosuite_gui_exe (4)](https://user-images.githubusercontent.com/74001397/186889897-c1c17fac-fddc-4967-9084-39cfe2d1307f.png "Octosuite user profile window")
![GitHub tag (latest by date)](https://img.shields.io/github/v/tag/rly0nheart/octosuite?style=for-the-badge&logo=github)
![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-blue?style=for-the-badge&logo=apple)
![GitHub](https://img.shields.io/github/license/rly0nheart/octosuite?style=for-the-badge&logo=github)
![GitHub repo size](https://img.shields.io/github/repo-size/rly0nheart/octosuite?style=for-the-badge&logo=github)
## Wiki
[Refer to the Wiki](https://github.com/rly0nheart/octosuite/wiki) for installation instructions, in addition to all other documentation.
> *Simply gather OSINT on Github users and organizations like a god🔥*
# FEATURES
- [x] Fetches organization info
- [x] Fetches user info
- [x] Fetches repository info
- [x] Returns contents of a path from a repository
- [x] Returns a list of repos owned by an organization
- [x] Returns a list of repos owned by a user
- [x] Returns a list of gists owned by a user
- [x] Returns a list of a user's followers
- [x] Checks whether user A follows user B
## Features
- [x] Fetches an organization's profile information
- [x] Fetches an oganization's events
- [x] Returns an organization's repositories
- [x] Returns an organization's public members
- [x] Fetches a repository's information
- [x] Returns a repository's contributors
- [x] Returns a repository's languages
- [x] Fetches a repository's stargazers
- [x] Fetches a repository's forks
- [x] Fetches a repository's releases
- [x] Returns a list of files in a specified path of a repository
- [x] Fetches a user's profile information
- [x] Returns a user's gists
- [x] Returns organizations that a user owns/belongs to
- [x] Fetches a user's events
- [x] Fetches a 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
- [x] Returns a user's subscriptions
- [x] Gets a user's subscriptions
- [x] Gets a user's events
- [x] Searches users
- [x] Searches repositories
- [x] Searches topics
- [x] Searches issues
- [x] Searches commits
- [x] Easily updates with the 'update' command
- [x] Automatically logs network activity (.logs folder)
- [x] User can view, read and delete logs
- [x] ...And more
# INSTALLATION
**clone project**:
## Note
> octosuite automatically logs network and minor user activity of each session. The logs are saved by date and time in the .logs folder
>> If you believe octosuite can be better, feel free to open a pull request with your improvements✌🏾🙂
```
git clone https://github.com/rly0nheart/octosuite.git
```
```
cd octosuite
```
```
pip install -r requirements.txt
```
# USAGE
**Linux**:
```
sudo chmod +x octosuite
```
```
sudo ./octosuite
```
**Windows**:
```
python3 octosuite
```
**Mac**:
```
python3 octosuite
```
# AVAILABLE COMMANDS
| Command | Usage|
| ------------- |:---------:|
| ``orginfo`` | *get organization info* |
| ``userinfo`` | *get user profile info* |
| ``repoinfo`` | *get repository info* |
| ``pathcontents`` | *get contents of a path from a specified repository* |
| ``orgrepos`` | *get a list of repositories owned by a specified organization* |
| ``userrepos`` | *get a list of repositories owned by a specified user* |
| ``usergists`` | *get a list of gists owned by a specified user* |
| ``userfollowers`` | *get a list of a user's followers* |
| ``userfollowing`` | *check whether user A follows user B* |
| ``usersearch`` | *search user(s)* |
| ``reposearch`` | *search repositor(y)(ies)* |
| ``topicsearch`` | *search topics(s)* |
| ``issuesearch`` | *search issue(s)* |
| ``commitsearch`` | *search commit(s)* |
| ``update`` | *update octosuite* |
| ``changelog`` | *show changelog* |
| ``author`` | *show author info* |
| ``help`` | *show usage/help* |
| ``exit`` | *exit session* |
# NOTE
* *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***
# LICENSE
## License
![license](https://user-images.githubusercontent.com/74001397/137917929-2f2cdb0c-4d1d-4e4b-9f0d-e01589e027b5.png)
# ABOUT AUTHOR
[About.me](https://about.me/rly0nheart)
## Donations
Love octosuite and would like to donate? You can buy me a coffee using the button below.
<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>
Your support is much appreciated!☕👌🏾😊

BIN
images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
images/octosuite_app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

BIN
images/octosuite_app1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

BIN
images/octosuite_exe.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 KiB

View File

@@ -1,19 +0,0 @@
from lib.colors import red,white,green,reset
banner = f'''{red}
▒█████ ▄████▄ ▄▄▄█████▓ ▒█████ ██████ █ ██ ██▓▄▄▄█████▓▓█████
▒██▒ ██▒▒██▀ ▀█ ▓ ██▒ ▓▒▒██▒ ██▒▒██ ▒ ██ ▓██▒▓██▒▓ ██▒ ▓▒▓█ ▀
▒██░ ██▒▒▓█ ▄ ▒ ▓██░ ▒░▒██░ ██▒░ ▓██▄ ▓██ ▒██░▒██▒▒ ▓██░ ▒░▒███
▒██ ██░▒▓▓▄ ▄██▒░ ▓██▓ ░ ▒██ ██░ ▒ ██▒▓▓█ ░██░░██░░ ▓██▓ ░ ▒▓█ ▄
░ ████▓▒░▒ ▓███▀ ░ ▒██▒ ░ ░ ████▓▒░▒██████▒▒▒█████▓ ░██░ ▒██▒ ░ ░▒████▒
░ ▒░▒░▒░ ░ ░▒ ▒ ░ ▒ ░░ ░ ▒░▒░▒░ ▒ ▒▓▒ ▒ ░░▒▓▒ ▒ ▒ ░▓ ▒ ░░ ░░ ▒░ ░
░ ▒ ▒░ ░ ▒ ░ ░ ▒ ▒░ ░ ░▒ ░ ░░░▒░ ░ ░ ▒ ░ ░ ░ ░ ░
░ ░ ░ ▒ ░ ░ ░ ░ ░ ▒ ░ ░ ░ ░░░ ░ ░ ▒ v1.4.0
░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░
{white}— Advanced Github {red}OSINT{white} Framework{reset}
> {white}use '{green}help{white}' command for usage{reset}
> {white}commands are case sensitive
{'-'*30}
{reset}
'''

View File

@@ -1,16 +0,0 @@
import sys
# Colors will be unavailable on non-linux machines
colors = True
machine = sys.platform
if machine.lower().startswith(("os", "win", "darwin")):
colors = False
if not colors:
reset = red = white = green = ""
else:
white = "\033[97m"
red = "\033[91m"
reset = "\033[0m"
green = "\033[92m"

View File

@@ -1,20 +0,0 @@
#!/usr/bin/env python3
import logging
from src.main import *
from lib.colors import red,white,green,reset
if __name__ == '__main__':
while True:
try:
octosuite().on_start()
except KeyboardInterrupt:
logging.info('Process interrupted with Ctrl+C')
exit(f'\n{white}[{red}x{white}] Process interrupted with {red}Ctrl{white}+{red}C{reset}')
except Exception as e:
logging.error(f'An error occured: {e}')
print(f'{white}[{red}!{white}] An error occured: {red}{e}{reset}')
input(f'\n{white}^ Press any key to continue{reset} ')

1
octosuite/__init__.py Normal file
View File

@@ -0,0 +1 @@

22
octosuite/banner.py Normal file
View File

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

43
octosuite/colors.py Normal file
View File

@@ -0,0 +1,43 @@
import platform
from datetime import datetime
# This file is responsible for enabling/disabling colors in OctoSuite
# This file gets called first at start up before any other file gets called
# colors.py is the reason why users get to choose whether to enable/disable colors
system_info = [("Processor",platform.processor),
("Node", platform.node),
("Release", platform.release),
("Architecture", platform.architecture),
("Version", platform.version)]
banner = f"""
OCTOSUITE © 2022 Richard Mwewa
{datetime.now().strftime('%A %d %B %Y, %H:%M:%S%p')}
"""
print(banner)
print(f"\t{platform.system()}")
for key, value in system_info:
print(f"\t├─ {key}: {value()}")
print("\n")
while True:
try:
color_chooser = input(f"[ ? ] Welcome, would you like to enable colors for this session? (Y/n) ").lower()
if color_chooser == "y":
header_title = "bold white"
red = "[red]"
white = "[white]"
green = "[green]"
red_bold = "[white bold]"
white_bold = "[white bold]"
green_bold = "[green bold]"
reset = "[/]"
break
elif color_chooser == "n":
header_title = red = white = green = red_bold = white_bold = green_bold = reset = ""
break
else:
print(f"\n[ ! ] Your response '{color_chooser}' is invalid (expected y or n)")
except KeyboardInterrupt:
exit(f"[ ! ] Process interrupted with [Ctrl+C].")

447
octosuite/csv_loggers.py Normal file
View File

@@ -0,0 +1,447 @@
import csv
import logging
from rich import print as xprint
from octosuite.sign_vars import SignVar
from octosuite.log_roller import logRoller
from octosuite.colors import red, white, green, reset
'''
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']]
xprint(f"\n{SignVar.prompt} {logRoller.askLogCsv}", end="");prompt = input().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))
xprint(f"{SignVar.positive} {logRoller.loggedToCsv.format(file.name)}")
else:
logging.info(logRoller.loggingSkipped.format(prompt))
xprint(f"{SignVar.info} {logRoller.loggingSkipped.format(prompt)}")
# 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']]
xprint(f"\n{SignVar.prompt} {logRoller.askLogCsv}", end="");prompt = input().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))
xprint(f"{SignVar.positive} {logRoller.loggedToCsv.format(file.name)}")
else:
logging.info(logRoller.loggingSkipped.format(prompt))
xprint(f"{SignVar.info} {logRoller.loggingSkipped.format(prompt)}")
# 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']]
xprint(f"\n{SignVar.prompt} {logRoller.askLogCsv}", end="");prompt = input().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))
xprint(f"{SignVar.positive} {logRoller.loggedToCsv.format(file.name)}")
else:
logging.info(logRoller.loggingSkipped.format(prompt))
xprint(f"{SignVar.info} {logRoller.loggingSkipped.format(prompt)}")
# 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']]
xprint(f"\n{SignVar.prompt} {logRoller.askLogCsv}", end="");prompt = input().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))
xprint(f"{SignVar.positive} {logRoller.loggedToCsv.format(file.name)}")
else:
logging.info(logRoller.loggingSkipped.format(prompt))
xprint(f"{SignVar.info} {logRoller.loggingSkipped.format(prompt)}")
# 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']]
xprint(f"\n{SignVar.prompt} {logRoller.askLogCsv}", end="");prompt = input().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))
xprint(f"{SignVar.positive} {logRoller.loggedToCsv.format(file.name)}")
else:
logging.info(logRoller.loggingSkipped.format(prompt))
xprint(f"{SignVar.info} {logRoller.loggingSkipped.format(prompt)}")
# 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']]
xprint(f"\n{SignVar.prompt} {logRoller.askLogCsv}", end="");prompt = input().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))
xprint(f"{SignVar.positive} {logRoller.loggedToCsv.format(file.name)}")
else:
logging.info(logRoller.loggingSkipped.format(prompt))
xprint(f"{SignVar.info} {logRoller.loggingSkipped.format(prompt)}")
# 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']]
xprint(f"\n{SignVar.prompt} {logRoller.askLogCsv}", end="");prompt = input().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))
xprint(f"{SignVar.positive} {logRoller.loggedToCsv.format(file.name)}")
else:
logging.info(logRoller.loggingSkipped.format(prompt))
xprint(f"{SignVar.info} {logRoller.loggingSkipped.format(prompt)}")
# 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']]
xprint(f"\n{SignVar.prompt} {logRoller.askLogCsv}", end="");prompt = input().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))
xprint(f"{SignVar.positive} {logRoller.loggedToCsv.format(file.name)}")
else:
logging.info(logRoller.loggingSkipped.format(prompt))
xprint(f"{SignVar.info} {logRoller.loggingSkipped.format(prompt)}")
# 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']]
xprint(f"\n{SignVar.prompt} {logRoller.askLogCsv}", end="");prompt = input().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))
xprint(f"{SignVar.positive} {logRoller.loggedToCsv.format(file.name)}")
else:
logging.info(logRoller.loggingSkipped.format(prompt))
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']]
xprint(f"\n{SignVar.prompt} {logRoller.askLogCsv}", end="");prompt = input().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))
xprint(f"{SignVar.positive} {logRoller.loggedToCsv.format(file.name)}")
else:
logging.info(logRoller.loggingSkipped.format(prompt))
xprint(f"{SignVar.info} {logRoller.loggingSkipped.format(prompt)}")
# 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']]
xprint(f"\n{SignVar.prompt} {logRoller.askLogCsv}", end="");prompt = input().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))
xprint(f"{SignVar.positive} {logRoller.loggedToCsv.format(file.name)}")
else:
logging.info(logRoller.loggingSkipped.format(prompt))
xprint(f"{SignVar.info} {logRoller.loggingSkipped.format(prompt)}")
# .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']]
xprint(f"\n{SignVar.prompt} {logRoller.askLogCsv}", end="");prompt = input().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))
xprint(f"{SignVar.positive} {logRoller.loggedToCsv.format(file.name)}")
else:
logging.info(logRoller.loggingSkipped.format(prompt))
xprint(f"{SignVar.info} {logRoller.loggingSkipped.format(prompt)}")
# .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']]
xprint(f"\n{SignVar.prompt} {logRoller.askLogCsv}", end="");prompt = input().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))
xprint(f"{SignVar.positive} {logRoller.loggedToCsv.format(file.name)}")
else:
logging.info(logRoller.loggingSkipped.format(prompt))
xprint(f"{SignVar.info} {logRoller.loggingSkipped.format(prompt)}")
# .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']]
xprint(f"\n{SignVar.prompt} {logRoller.askLogCsv}", end="");prompt = input().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))
xprint(f"{SignVar.positive} {logRoller.loggedToCsv.format(file.name)}")
else:
logging.info(logRoller.loggingSkipped.format(prompt))
xprint(f"{SignVar.info} {logRoller.loggingSkipped.format(prompt)}")
# .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']]
xprint(f"\n{SignVar.prompt} {logRoller.askLogCsv}", end="");prompt = input().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))
xprint(f"{SignVar.positive} {logRoller.loggedToCsv.format(file.name)}")
else:
logging.info(logRoller.loggingSkipped.format(prompt))
xprint(f"{SignVar.info} {logRoller.loggingSkipped.format(prompt)}")
# .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']]
xprint(f"\n{SignVar.prompt} {logRoller.askLogCsv}", end="");prompt = input().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))
xprint(f"{SignVar.positive} {logRoller.loggedToCsv.format(file.name)}")
else:
logging.info(logRoller.loggingSkipped.format(prompt))
xprint(f"{SignVar.info} {logRoller.loggingSkipped.format(prompt)}")
# .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']]
xprint(f"\n{SignVar.prompt} {logRoller.askLogCsv}", end="");prompt = input().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))
xprint(f"{SignVar.positive} {logRoller.loggedToCsv.format(file.name)}")
else:
logging.info(logRoller.loggingSkipped.format(prompt))
xprint(f"{SignVar.info} {logRoller.loggingSkipped.format(prompt)}")
# .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']]
xprint(f"\n{SignVar.prompt} {logRoller.askLogCsv}", end="");prompt = input().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))
xprint(f"{SignVar.positive} {logRoller.loggedToCsv.format(file.name)}")
else:
logging.info(logRoller.loggingSkipped.format(prompt))
xprint(f"{SignVar.info} {logRoller.loggingSkipped.format(prompt)}")
# 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']]
xprint(f"\n{SignVar.prompt} {logRoller.askLogCsv}", end="");prompt = input().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))
xprint(f"{SignVar.positive} {logRoller.loggedToCsv.format(file.name)}")
else:
logging.info(logRoller.loggingSkipped.format(prompt))
xprint(f"{SignVar.info} {logRoller.loggingSkipped.format(prompt)}")
# 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']]
xprint(f"\n{SignVar.prompt} {logRoller.askLogCsv}", end="");prompt = input().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))
xprint(f"{SignVar.positive} {logRoller.loggedToCsv.format(file.name)}")
else:
logging.info(logRoller.loggingSkipped.format(prompt))
xprint(f"{SignVar.info} {logRoller.loggingSkipped.format(prompt)}")
# 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']]
xprint(f"\n{SignVar.prompt} {logRoller.askLogCsv}", end="");prompt = input().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))
xprint(f"{SignVar.positive} {logRoller.loggedToCsv.format(file.name)}")
else:
logging.info(logRoller.loggingSkipped.format(prompt))
xprint(f"{SignVar.info} {logRoller.loggingSkipped.format(prompt)}")
# 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']]
xprint(f"\n{SignVar.prompt} {logRoller.askLogCsv}", end="");prompt = input().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))
xprint(f"{SignVar.positive} {logRoller.loggedToCsv.format(file.name)}")
else:
logging.info(logRoller.loggingSkipped.format(prompt))
xprint(f"{SignVar.info} {logRoller.loggingSkipped.format(prompt)}")
# 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']]
xprint(f"\n{SignVar.prompt} {logRoller.askLogCsv}", end="");prompt = input().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))
xprint(f"{SignVar.positive} {logRoller.loggedToCsv.format(file.name)}")
else:
logging.info(logRoller.loggingSkipped.format(prompt))
xprint(f"{SignVar.info} {logRoller.loggingSkipped.format(prompt)}")

182
octosuite/helper.py Normal file
View File

@@ -0,0 +1,182 @@
from rich.table import Table
from rich import print as xprint
from octosuite.colors import white, green, white_bold, green_bold, header_title, reset
"""
Help
This class holds the help text for available commands.
"""
class Help:
usageText = 'Use syntax {} to get started with %s{}%s.' % (green_bold, reset)
usageText1 = '%sUse {} to view all available subcommands.%s' % (white, reset)
usageText2 = "%sThe {} command works with subcommands. %s" % (white, reset)
def Org():
xprint(Help.usageText2.format(f"{green_bold}org{reset}") + Help.usageText1.format(f"{green_bold}help:org{reset}"))
def Repo():
xprint(Help.usageText2.format(f"{green_bold}repo{reset}") + Help.usageText1.format(f"{green_bold}help:repo{reset}"))
def User():
xprint(Help.usageText2.format(f"{green_bold}user{reset}") + Help.usageText1.format(f"{green_bold}help:user{reset}"))
def Search():
xprint(Help.usageText2.format(f"{green_bold}search{reset}") + Help.usageText1.format(f"{green_bold}help:search{reset}"))
def Source():
xprint(Help.usageText2.format(f"{green_bold}source{reset}") + Help.usageText1.format(f"{green_bold}help:source{reset}"))
def Logs():
xprint(Help.usageText2.format(f"{green_bold}logs{reset}") + Help.usageText1.format(f"{green_bold}help:logs{reset}"))
def Version():
xprint(Help.usageText2.format(f"{green_bold}version{reset}") + Help.usageText1.format(f"{green_bold}help:version{reset}"))
def Csv():
xprint(Help.usageText2.format(f"{green_bold}csv{reset}") + Help.usageText1.format(f"{green_bold}help:csv{reset}"))
def versionCommand():
version_cmd_table =Table(show_header=True, header_style=header_title)
version_cmd_table.add_column("Command", style="dim", width=12)
version_cmd_table.add_column("Description")
version_cmd_table.add_row("check", "Check for new release(s)")
version_cmd_table.add_row("info", "Version information")
syntax = f"{green}version:<command>{reset}"
xprint(f"{Help.usageText.format(syntax, 'version management')}")
xprint(version_cmd_table)
def sourceCommand():
source_cmd_table =Table(show_header=True, header_style=header_title)
source_cmd_table.add_column("Command", style="dim", width=12)
source_cmd_table.add_column("Description")
source_cmd_table.add_row("zipball", "Download source code Zipball")
source_cmd_table.add_row("tarball", "Download source code Tarball")
syntax = f"{green}source:<command>{reset}"
xprint(f"{Help.usageText.format(syntax, 'source code downloads')}")
xprint(source_cmd_table)
def searchCommand():
search_cmd_table =Table(show_header=True, header_style=header_title)
search_cmd_table.add_column("Command", style="dim", width=12)
search_cmd_table.add_column("Description")
search_cmd_table.add_row("users", "Search user(s)")
search_cmd_table.add_row("repos", "Search repositor[y][ies]")
search_cmd_table.add_row("topics", "Search topic(s)")
search_cmd_table.add_row("issues", "Search issue(s)")
search_cmd_table.add_row("commits", "Search commit(s)")
syntax = f"{green}search:<command>{reset}"
xprint(f"{Help.usageText.format(syntax, 'target discovery')}")
xprint(search_cmd_table)
def userCommand():
user_cmd_table =Table(show_header=True, header_style=header_title)
user_cmd_table.add_column("Command", style="dim", width=12)
user_cmd_table.add_column("Description")
user_cmd_table.add_row("profile", "Get a target's profile info")
user_cmd_table.add_row("gists", "Return a users's gists")
user_cmd_table.add_row("org", "Return organizations that a target belongs to/owns")
user_cmd_table.add_row("repos", "Return a target's repositories")
user_cmd_table.add_row("events", "Return a target's events")
user_cmd_table.add_row("follows", "Check if user(A) follows user(B)")
user_cmd_table.add_row("followers", "Return a target's followers")
user_cmd_table.add_row("following", "Return a list of users the target is following")
user_cmd_table.add_row("subscriptions", "Return a target's subscriptions")
syntax = f"{green}user:<command>{reset}"
xprint(f"{Help.usageText.format(syntax, 'user investigation(s)')}")
xprint(user_cmd_table)
def orgCommand():
org_cmd_table =Table(show_header=True, header_style=header_title)
org_cmd_table.add_column("Command", style="dim", width=12)
org_cmd_table.add_column("Description")
org_cmd_table.add_row("profile", "Get a target organization' profile info")
org_cmd_table.add_row("repos", "Return a target organization' repositories")
org_cmd_table.add_row("events", "Return a target organization' events")
org_cmd_table.add_row("member", "Check if a specified user is a public member of the target organization")
syntax = f"{green}org:<command>{reset}"
xprint(f"{Help.usageText.format(syntax, 'organization investigation(s)')}")
xprint(org_cmd_table)
def repoCommand():
repo_cmd_table =Table(show_header=True, header_style=header_title)
repo_cmd_table.add_column("Command", style="dim", width=12)
repo_cmd_table.add_column("Description")
repo_cmd_table.add_row("profile", "Get a repository's info")
repo_cmd_table.add_row("issues", "Return a repository's issues")
repo_cmd_table.add_row("forks", "Return a repository's forks")
repo_cmd_table.add_row("releases", "Return a repository's releases")
repo_cmd_table.add_row("stargazers", "Return a repository's stargazers")
repo_cmd_table.add_row("path_contents", "List contents in a path of a repository")
syntax = f"{green}repo:<command>{reset}"
xprint(f"{Help.usageText.format(syntax, 'repository investigation(s)')}")
xprint(repo_cmd_table)
def logsCommand():
logs_cmd_table =Table(show_header=True, header_style=header_title)
logs_cmd_table.add_column("Command", style="dim", width=12)
logs_cmd_table.add_column("Description")
logs_cmd_table.add_row("view", "View logs")
logs_cmd_table.add_row("read", "Read log")
logs_cmd_table.add_row("delete", "Delete log")
syntax = f"{green}logs:<command>{reset}"
xprint(f"{Help.usageText.format(syntax, 'log(s) management')}")
xprint(logs_cmd_table)
def csvCommand():
csv_cmd_table =Table(show_header=True, header_style=header_title)
csv_cmd_table.add_column("Command", style="dim", width=12)
csv_cmd_table.add_column("Description")
csv_cmd_table.add_row("view", "View csv files")
csv_cmd_table.add_row("read", "Read csv")
csv_cmd_table.add_row("delete", "Delete csv")
syntax = f"{green}csv:<command>{reset}"
xprint(f"{Help.usageText.format(syntax, 'csv management')}")
xprint(csv_cmd_table)
def helpCommand():
core_cmd_table =Table(show_header=True, header_style=header_title)
core_cmd_table.add_column("Command", style="dim", width=12)
core_cmd_table.add_column("Description")
core_cmd_table.add_row("help", "Help menu")
core_cmd_table.add_row("exit", "Close session")
core_cmd_table.add_row("clear", "Clear screen")
core_cmd_table.add_row("about", "Program's info")
core_cmd_table.add_row("author", "Developer's info")
help_sub_cmd_table =Table(show_header=True, header_style=header_title)
help_sub_cmd_table.add_column("Command", style="dim", width=12)
help_sub_cmd_table.add_column("Description")
help_sub_cmd_table.add_row("csv", "List all csv management commands")
help_sub_cmd_table.add_row("logs", "List all logs management commands")
help_sub_cmd_table.add_row("org", "List all organization investigation commands")
help_sub_cmd_table.add_row("user", "List all users investigation commands")
help_sub_cmd_table.add_row("repo", "List all repository investigation commands")
help_sub_cmd_table.add_row("search", "List all target discovery commands")
help_sub_cmd_table.add_row("source", "List all source code download commands (for developers)")
help_sub_cmd_table.add_row("version", "List all version management commands")
syntax = f"{green}help:<command>{reset}"
xprint(core_cmd_table)
xprint(f"\n\n{Help.usageText.format(syntax, 'octosuite')}")
xprint(help_sub_cmd_table)

27
octosuite/log_roller.py Normal file
View File

@@ -0,0 +1,27 @@
"""
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 {}."
viewingLogs = "Viewing logs..."
viewingCsv = "Viewing CSV file(s)..."
deletedLog = "Deleted log -> {}"
readingLog = "Reading log -> {}"
readingCsv = 'Reading csv -> {}'
deletedCsv = 'Deleted csv -> {}'
fileDownloading = "Downloading -> {}..."
fileDownloaded = "Downloaded -> downloads/{}"
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 -> ({})"
loggingSkipped = "Logging skipped -> ({})"
limitInput = "Limit '{}' output to how many? (1-100) "

919
octosuite/main.py Normal file
View File

@@ -0,0 +1,919 @@
#!usr/bin/python
import os
import csv
import sys
import json
import logging
import getpass
import requests
import platform
import subprocess
from rich.table import Table
from datetime import datetime
from rich import print as xprint
from octosuite.helper import Help
from octosuite.sign_vars import SignVar
from octosuite.log_roller import logRoller
from octosuite.csv_loggers import csvLogger
from octosuite.banner import name_logo, version_tag
from octosuite.colors import red, white, green, red_bold, white_bold, green_bold, header_title, reset
# 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: Zambia, Africa',
'About.me': 'https://about.me/rly0nheart',
'Buy Me A Coffee': '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():
'''
Here we create/check 3 directories (.logs, output, downloads) on startup
If they exists, we ignore, otherwise, we create them
'''
directory_list = ['.logs', 'output', 'downloads']
for directory in directory_list:
os.makedirs(directory, exist_ok=True)
'''
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 = [("exit", exitSession),
("clear",clearScreen),
("about", about),
("author", author),
("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:csv", Help.csvCommand),
("help:org", Help.orgCommand),
("version", Help.Version),
("version:info", versionInfo),
("version:check", versionCheck),
("source", Help.Source),
("source:tarball", downloadTarball),
("source:zipball", downloadZipball),
("org", Help.Org),
("org:events", orgEvents),
("org:profile", orgProfile),
("org:repos", orgRepos),
("org:member", orgMember),
("repo", Help.Repo),
("repo:path_contents", 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),
("logs", Help.Logs),
("logs:view",viewLogs),
("logs:read",readLog),
("logs:delete",deleteLog),
("csv", Help.Csv),
("csv:view", viewCsv),
("csv:read", readCsv),
("csv:delete", deleteCsv)]
xprint(name_logo)
'''
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:
xprint(f"{white}┌──({red}{getpass.getuser()}{white}@{red}octosuite{white})\n├──[~{green}{os.getcwd()}{white}]\n└╼ {reset}", end="");command_input = input().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"))
xprint(f"{SignVar.warning} {logRoller.Ctrl.format('Ctrl+C')}")
break
# This initially catches all exceptions (except the KeyboardInterrupt)
except Exception as e:
logging.error(logRoller.Error.format(e))
xprint(f"{SignVar.error} {logRoller.Error.format(e)}")
# Fetching organization info
def orgProfile():
xprint(f"{white}>> @{green}Organization {white}(username){reset} ", end="");organization = input()
response = requests.get(f"{endpoint}/orgs/{organization}")
if response.status_code == 404:
xprint(f"{SignVar.negative} {logRoller.orgNotFound.format(organization)}")
elif response.status_code == 200:
xprint(f"\n{white}{response.json()['name']}{reset}")
for attr in org_attrs:
xprint(f"{white}├─ {org_attr_dict[attr]}:{reset} {response.json()[attr]}")
csvLogger.logOrgProfile(response)
else:
xprint(response.json())
# Fetching user information
def userProfile():
xprint(f"{white}>> @{green}Username{reset} ", end="");username = input()
response = requests.get(f"{endpoint}/users/{username}")
if response.status_code == 404:
xprint(f"{SignVar.negative} {logRoller.userNotFound.format(username)}")
elif response.status_code == 200:
xprint(f"\n{white}{response.json()['name']}{reset}")
for attr in profile_attrs:
xprint(f"{white}├─ {profile_attr_dict[attr]}:{reset} {response.json()[attr]}")
csvLogger.logUserProfile(response)
else:
xprint(response.json())
# Fetching repository information
def repoProfile():
xprint(f"{white}>> %{green}Repository{reset} ", end="");repo_name = input()
xprint(f"{white}>> @{green}Owner{white} (username) ", end="");username = input()
response = requests.get(f"{endpoint}/repos/{username}/{repo_name}")
if response.status_code == 404:
xprint(f"{SignVar.negative} {logRoller.repoOrUserNotFound.format(repo_name, username)}")
elif response.status_code == 200:
xprint(f"\n{white}{response.json()['full_name']}{reset}")
for attr in repo_attrs:
xprint(f"{white}├─ {repo_attr_dict[attr]}:{reset} {response.json()[attr]}")
csvLogger.logRepoProfile(response)
else:
xprint(response.json())
# Get path contents
def pathContents():
xprint(f"{white}>> %{green}Repository{reset} ", end="");repo_name = input()
xprint(f"{white}>> @{green}Owner{white} (username) ", end="");username = input()
xprint(f"{white}>> ~{green}/path/name{reset} ", end="");path_name = input()
response = requests.get(f"{endpoint}/repos/{username}/{repo_name}/contents/{path_name}")
if response.status_code == 404:
xprint(f"{SignVar.negative} {logRoller.infoNotFound.format(repo_name, username, path_name)}")
elif response.status_code == 200:
content_count = 0
for content in response.json():
content_count += 1
xprint(f"\n{white}{content['name']}{reset}")
for attr in path_attrs:
xprint(f"{white}├─ {path_attr_dict[attr]}:{reset} {content[attr]}")
csvLogger.logRepoPathContents(content, repo_name)
xprint(SignVar.info, f"Found {content_count} file(s) in {repo_name}/{path_name}.")
else:
xprint(response.json())
# repo contributors
def repoContributors():
xprint(f"{white}>> %{green}Repository{reset} ", end="");repo_name = input()
xprint(f"{white}>> @{green}Owner{white} (username) ", end="");username = input()
xprint(SignVar.prompt, logRoller.limitInput.format("contributors"), end="");limit = int(input())
response = requests.get(f"{endpoint}/repos/{username}/{repo_name}/contributors?per_page={limit}")
if response.status_code == 404:
xprint(f"{SignVar.negative} {logRoller.repoOrUserNotFound.format(repo_name, username)}")
elif response.status_code == 200:
for contributor in response.json():
xprint(f"\n{white}{contributor['login']}{reset}")
for attr in user_attrs:
xprint(f"{white}├─ {user_attr_dict[attr]}:{reset} {contributor[attr]}")
csvLogger.logRepoContributors(contributor, repo_name)
else:
xprint(response.json())
# repo stargazers
def repoStargazers():
xprint(f"{white}>> %{green}Repository{reset} ");repo_name = input()
xprint(f"{white}>> @{green}Owner{white} (username){reset} ", end="");username = input()
xprint(SignVar.prompt, logRoller.limitInput.format("repository stargazers"), end="");limit = int(input())
response = requests.get(f"{endpoint}/repos/{username}/{repo_name}/stargazers?per_page={limit}")
if response.status_code == 404:
xprint(f"{SignVar.negative} {logRoller.repoOrUserNotFound.format(repo_name, username)}")
elif response.json() == {}:
xprint(f"{SignVar.negative} Repository does not have any stargazers -> ({repo_name})")
elif response.status_code == 200:
for stargazer in response.json():
xprint(f"\n{white}{stargazer['login']}{reset}")
for attr in user_attrs:
xprint(f"{white}├─ {user_attr_dict[attr]}:{reset} {stargazer[attr]}")
csvLogger.logRepoStargazers(stargazer, repo_name)
else:
xprint(response.json())
# repo forks
def repoForks():
xprint(f"{white}>> %{green}Repository{reset} ", end="");repo_name = input()
xprint(f"{white}>> @{green}Owner{white} (username){reset} ", end="");username = input()
xprint(SignVar.prompt, logRoller.limitInput.format("repository forks"), end="");limit = int(input())
response = requests.get(f"{endpoint}/repos/{username}/{repo_name}/forks?per_page={limit}")
if response.status_code == 404:
xprint(f"{SignVar.negative} {logRoller.repoOrUserNotFound.format(repo_name, username)}")
elif response.json() == {}:
xprint(f"{SignVar.negative} Repository does not have forks -> ({repo_name})")
elif response.status_code == 200:
count = 0
for fork in response.json():
count += 1
xprint(f"\n{white}{fork['full_name']}{reset}")
for attr in repo_attrs:
xprint(f"{white}├─ {repo_attr_dict[attr]}:{reset} {fork[attr]}")
csvLogger.logRepoForks(fork, count)
else:
xprint(response.json())
# Repo issues
def repoIssues():
xprint(f"{white}>> %{green}Repository{reset} ", end="");repo_name = input()
xprint(f"{white}>> @{green}Owner{white} (username){reset} ", end="");username = input()
xprint(SignVar.prompt, logRoller.limitInput.format("repository issues"), end="");limit = int(input())
response = requests.get(f"{endpoint}/repos/{username}/{repo_name}/issues?per_page={limit}")
if response.status_code == 404:
xprint(f"{SignVar.negative} {logRoller.repoOrUserNotFound.format(repo_name, username)}")
elif response.json() == []:
xprint(f"{SignVar.negative} Repository does not have open issues -> ({repo_name})")
elif response.status_code == 200:
for issue in response.json():
xprint(f"\n{white}{issue['title']}{reset}")
for attr in repo_issues_attrs:
xprint(f"{white}├─ {repo_issues_attr_dict[attr]}:{reset} {issue[attr]}")
xprint(issue['body'])
csvLogger.logRepoIssues(issue, repo_name)
else:
xprint(response.json())
# Repo releases
def repoReleases():
xprint(f"{white}>> %{green}Repository{reset} ", end="");repo_name = input()
xprint(f"{white}>> @{green}Owner{white} (username){reset} ", end="");username = input()
xprint(SignVar.prompt, logRoller.limitInput.format("repository releases"), end="");limit = int(input())
response = requests.get(f"{endpoint}/repos/{username}/{repo_name}/releases?per_page={limit}")
if response.status_code == 404:
xprint(f"{SignVar.negative} {logRoller.repoOrUserNotFound.format(repo_name, username)}")
elif response.json() == []:
xprint(f"{SignVar.negative} Repository does not have releases -> ({repo_name})")
elif response.status_code == 200:
for release in response.json():
xprint(f"\n{white}{release['name']}{reset}")
for attr in repo_releases_attrs:
xprint(f"{white}├─ {repo_releases_attr_dict[attr]}:{reset} {release[attr]}")
xprint(release['body'])
csvLogger.logRepoReleases(release, repo_name)
else:
xprint(response.json())
# Fetching organization repositories
def orgRepos():
xprint(f"{white}>> @{green}Organization{white} (username){reset} ", end="");organization = input()
xprint(SignVar.prompt, logRoller.limitInput.format("organization repositories"), end="");limit = int(input())
response = requests.get(f"{endpoint}/orgs/{organization}/repos?per_page={limit}")
if response.status_code == 404:
xprint(f"{SignVar.negative} {logRoller.orgNotFound.format(organization)}")
elif response.status_code == 200:
for repository in response.json():
xprint(f"\n{white}{repository['full_name']}{reset}")
for attr in repo_attrs:
xprint(f"{white}├─ {repo_attr_dict[attr]}:{reset} {repository[attr]}")
csvLogger.logOrgRepos(repository, organization)
else:
xprint(response.json())
# organization events
def orgEvents():
xprint(f"{white}>> @{green}Organization{white} (username){reset} ", end="");organization = input()
xprint(SignVar.prompt, logRoller.limitInput.format("organization repositories"), end="");limit = int(input())
response = requests.get(f"{endpoint}/orgs/{organization}/events?per_page={limit}")
if response.status_code == 404:
xprint(f"{SignVar.negative} {logRoller.orgNotFound.format(organization)}")
elif response.status_code == 200:
for event in response.json():
xprint(f"\n{white}{event['id']}{reset}")
xprint(f"{white}├─ Type:{reset} {event['type']}\n{white}├─ Created at:{reset} {event['created_at']}")
xprint(event['payload'])
csvLogger.logOrgEvents(event, organization)
else:
xprint(response.json())
# organization member
def orgMember():
xprint(f"{white}>> @{green}Organization{white} (username){reset} ", end="");organization = input()
xprint(f"{white}>> @{green}Username{reset} ", end="");username = input()
response = requests.get(f"{endpoint}/orgs/{organization}/public_members/{username}")
if response.status_code == 204:
xprint(f"{SignVar.positive} User ({username}) is a public member of the organization -> ({organization})")
else:
xprint(f"{SignVar.negative} {response.json()['message']}")
# Fetching user repositories
def userRepos():
xprint(f"{white}>> @{green}Username{reset} ", end="");username = input()
xprint(SignVar.prompt, logRoller.limitInput.format("repositories"), end="");limit = int(input())
response = requests.get(f"{endpoint}/users/{username}/repos?per_page={limit}")
if response.status_code == 404:
xprint(f"{SignVar.negative} {logRoller.userNotFound.format(username)}")
elif response.status_code == 200:
for repository in response.json():
xprint(f"\n{white}{repository['full_name']}{reset}")
for attr in repo_attrs:
xprint(f"{white}├─ {repo_attr_dict[attr]}:{reset} {repository[attr]}")
csvLogger.logUserRepos(repository, username)
else:
xprint(response.json())
# Fetching user's gists
def userGists():
xprint(f"{white}>> @{green}Username{reset} ", end="");username = input()
xprint(SignVar.prompt, logRoller.limitInput.format('gists'), end="");limit = int(input())
response = requests.get(f"{endpoint}/users/{username}/gists?per_page={limit}")
#xprint(response.json())
if response.json() == []:
xprint(f"{SignVar.negative} User does not have gists.")
elif response.status_code == 404:
xprint(f"{SignVar.negative} {logRoller.userNotFound.format(username)}")
elif response.status_code == 200:
for gist in response.json():
xprint(f"\n{white}{gist['id']}{reset}")
for attr in gists_attrs:
xprint(f"{white}├─ {gists_attr_dict[attr]}:{reset} {gist[attr]}")
csvLogger.logUserGists(gist)
else:
xprint(response.json())
# Fetching a list of organizations that a user owns or belongs to
def userOrgs():
xprint(f"{white}>> @{green}Username{reset} ", end="");username = input()
xprint(SignVar.prompt, logRoller.limitInput.format("user organizations"), end="");limit = int(input())
response = requests.get(f"{endpoint}/users/{username}/orgs?per_page={limit}")
if response.json() == []:
xprint(f"{SignVar.negative} User ({username}) does not (belong to/own) any organizations.")
elif response.status_code == 404:
xprint(f"{SignVar.negative} {logRoller.userNotFound.format(username)}")
elif response.status_code == 200:
for organization in response.json():
print(f"\n{white}{organization['login']}{reset}")
for attr in user_orgs_attrs:
xprint(f"{white}├─ {user_orgs_attr_dict[attr]}:{reset} {organization[attr]}")
csvLogger.logUserOrgs(organization, username)
else:
xprint(response.json())
# Fetching a users events
def userEvents():
xprint(f"{white}>> @{green}Username{reset} ", end="");username = input()
xprint(SignVar.prompt, logRoller.limitInput.format("events"), end="");limit = int(input())
response = requests.get(f"{endpoint}/users/{username}/events/public?per_page={limit}")
if response.status_code == 404:
xprint(f"{SignVar.negative} {logRoller.userNotFound.format(username)}")
elif response.status_code == 200:
for event in response.json():
xprint(f"\n{white}{event['id']}{reset}")
xprint(f"{white}├─ Actor:{reset} {event['actor']['login']}")
xprint(f"{white}├─ Type:{reset} {event['type']}")
xprint(f"{white}├─ Repository:{reset} {event['repo']['name']}")
xprint(f"{white}├─ Created at:{reset} {event['created_at']}")
xprint(event['payload'])
csvLogger.logUserEvents(event)
else:
xprint(response.json())
# Fetching a target user's subscriptions
def userSubscriptions():
xprint(f"{white}>> @{green}Username{reset} ", end="");username = input().lower()
xprint(SignVar.prompt, logRoller.limitInput.format("user subscriptions"), end="");limit = int(input())
response = requests.get(f"{endpoint}/users/{username}/subscriptions?per_page={limit}")
if response.json() == []:
xprint(f"{SignVar.negative} User does not have any subscriptions.")
elif response.status_code == 404:
xprint(f"{SignVar.negative} {logRoller.userNotFound.format(username)}")
elif response.status_code == 200:
for repository in response.json():
xprint(f"\n{white}{repository['full_name']}{reset}")
for attr in repo_attrs:
xprint(f"{white}├─ {repo_attr_dict[attr]}:{reset} {repository[attr]}")
csvLogger.logUserSubscriptions(repository, username)
else:
xprint(response.json())
# Fetching a list of users the target follows
def userFollowing():
xprint(f"{white}>> @{green}Username{reset} ", end="");username = input().lower()
xprint(SignVar.prompt, logRoller.limitInput.format("user' following"), end="");limit = int(input())
response = requests.get(f"{endpoint}/users/{username}/following?per_page={limit}")
if response.json() == []:
xprint(f"{SignVar.negative} User ({username})does not follow anyone.")
elif response.status_code == 404:
xprint(f"{SignVar.negative} {logRoller.userNotFound.format(username)}")
elif response.status_code == 200:
for user in response.json():
xprint(f"\n{white}@{user['login']}{reset}")
for attr in user_attrs:
xprint(f"{white}├─ {user_attr_dict[attr]}:{reset} {user[attr]}")
csvLogger.logUserFollowing(user, username)
else:
xprint(response.json())
# Fetching user's followera'
def userFollowers():
xprint(f"{white}>> @{green}Username{reset} ", end="");username = input().lower()
xprint(SignVar.prompt, logRoller.limitInput.format("user followers"),end="");limit = int(input())
response = requests.get(f"{endpoint}/users/{username}/followers?per_page={limit}")
if response.json() == []:
xprint(f"{SignVar.negative} User ({username})does not have followers.")
elif response.status_code == 404:
xprint(f"{SignVar.negative} {logRoller.userNotFound.format(username)}")
elif response.status_code == 200:
for follower in response.json():
xprint(f"\n{white}@{follower['login']}{reset}")
for attr in user_attrs:
xprint(f"{white}├─ {user_attr_dict[attr]}:{reset} {follower[attr]}")
csvLogger.logUserFollowers(follower, username)
else:
xprint(response.json())
# Checking whether or not user[A] follows user[B]
def userFollows():
xprint(f"{white}>> @{green}user{white}(A) (username){reset} ", end="");user_a = input()
xprint(f"{white}>> @{green}user{white}(B) (username){reset} ", end="");user_b = input()
response = requests.get(f"{endpoint}/users/{user_a}/following/{user_b}")
if response.status_code == 204:
xprint(f"{SignVar.positive} @{user_a} FOLLOWS @{user_b}")
else:
xprint(f"{SignVar.negative} @{user_a} DOES NOT FOLLOW @{user_b}")
# User search
def userSearch():
xprint(f"{white}>> @{green}Query{white} (eg. john){reset} ", end="");query = input()
xprint(SignVar.prompt, logRoller.limitInput.format("user search"), end="");limit = int(input())
response = requests.get(f"{endpoint}/search/users?q={query}&per_page={limit}").json()
for user in response['items']:
xprint(f"\n{white}@{user['login']}{reset}")
for attr in user_attrs:
xprint(f"{white}├─ {user_attr_dict[attr]}:{reset} {user[attr]}")
csvLogger.logUserSearch(user, query)
# Repository search
def repoSearch():
xprint(f"{white}>> %{green}Query{white} (eg. git){reset} ", end="");query = input()
xprint(SignVar.prompt, logRoller.limitInput.format("repositor[y][ies] search"), end="");limit = int(input())
response = requests.get(f"{endpoint}/search/repositories?q={query}&per_page={limit}").json()
for repository in response['items']:
xprint(f"\n{white}{repository['full_name']}{reset}")
for attr in repo_attrs:
xprint(f"{white}├─ {repo_attr_dict[attr]}:{reset} {repository[attr]}")
csvLogger.logRepoSearch(repository, query)
# Topics search
def topicSearch():
xprint(f"{white}>> #{green}Query{white} (eg. osint){reset} ", end="");query = input()
xprint(SignVar.prompt, logRoller.limitInput.format("topic(s) search"),end="");limit = int(input())
response = requests.get(f"{endpoint}/search/topics?q={query}&per_page={limit}").json()
for topic in response['items']:
xprint(f"\n{white}{topic['name']}{reset}")
for attr in topic_attrs:
xprint(f"{white}├─ {topic_attr_dict[attr]}:{reset} {topic[attr]}")
csvLogger.logTopicSearch(topic, query)
# Issue search
def issueSearch():
xprint(f"{white}>> !{green}Query{white} (eg. error){reset} ", end="");query = input()
xprint(SignVar.prompt, logRoller.limitInput.format("issue(s) search"), end="");limit = int(input())
response = requests.get(f"{endpoint}/search/issues?q={query}&per_page={limit}").json()
for issue in response['items']:
xprint(f"\n\n{white}{issue['title']}{reset}")
for attr in repo_issues_attrs:
xprint(f"{white}├─ {repo_issues_attr_dict[attr]}:{reset} {issue[attr]}")
xprint(issue['body'])
csvLogger.logIssueSearch(issue, query)
# Commits search
def commitsSearch():
xprint(f"{white}>> :{green}Query{white} (eg. filename:index.php){reset} ", end="");query = input()
xprint(SignVar.prompt, logRoller.limitInput.format("commit(s) search"), end="");limit = int(input())
response = requests.get(f"{endpoint}/search/commits?q={query}&per_page={limit}").json()
for commit in response['items']:
xprint(f"\n{white}{commit['commit']['tree']['sha']}{reset}")
xprint(f"{white}├─ Author:{reset} {commit['commit']['author']['name']}")
xprint(f"{white}├─ Username:{reset} {commit['author']['login']}")
xprint(f"{white}├─ Email:{reset} {commit['commit']['author']['email']}")
xprint(f"{white}├─ Commiter:{reset} {commit['commit']['committer']['name']}")
xprint(f"{white}├─ Repository:{reset} {commit['repository']['full_name']}")
xprint(f"{white}├─ URL:{reset} {commit['html_url']}")
xprint(commit['commit']['message'])
csvLogger.logCommitsSearch(commit, query)
# View csv files
def viewCsv():
logging.info(logRoller.viewingCsv)
csv_files = os.listdir("output")
csv_table =Table(show_header=True, header_style=header_title)
csv_table.add_column("CSV", style="dim", width=12)
csv_table.add_column("Size (bytes)")
for csv_file in csv_files:
csv_table.add_row(str(csv_file), str(os.path.getsize("output/"+csv_file)))
xprint(csv_table)
# Read a specified csv file
def readCsv():
xprint(f"{white}>> {green}.csv {reset}(filename) ", end="");csv_file = input()
with open(f"output/{csv_file}.csv", "r") as file:
logging.info(logRoller.readingCsv.format(csv_file))
xprint("\n"+file.read())
# Delete a specified csv file
def deleteCsv():
xprint(f"{white}>> {green}.csv {reset}filename{reset} ", end="");csv_file = input()
if sys.platform.lower().startswith(("win", "darwin")):
subprocess.run(['del',f'.output\{csv_file}.csv'])
else:
subprocess.run(['sudo','rm',f'output/{csv_file}.csv'],shell=False)
logging.info(logRoller.deletedCsv.format(csv_file))
xprint(f"{SignVar.positive} {logRoller.deletedCsv.format(csv_file)}")
# View octosuite log files
def viewLogs():
logging.info(logRoller.viewingLogs)
logs = os.listdir(".logs")
logs_table =Table(show_header=True, header_style=header_title)
logs_table.add_column("Log", style="dim", width=12)
logs_table.add_column("Size (bytes)")
for log in logs:
logs_table.add_row(str(log), str(os.path.getsize(".logs/"+log)))
xprint(logs_table)
# Read a specified log file
def readLog():
xprint(f"{white}>> {green}.log date{reset} (eg. 2022-04-27 10:09:36AM) ", end="");log_file = input()
with open(f".logs/{log_file}.log", "r") as log:
logging.info(logRoller.readingLog.format(log_file))
xprint("\n"+log.read())
# Delete a specified log file
def deleteLog():
xprint(f"{white}>> {green}.log date{reset} (eg. 2022-04-27 10:09:36AM) ", end="");log_file = input()
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))
xprint(f"{SignVar.positive} {logRoller.deletedLog.format(log_file)}")
# Downloading release tarball
def downloadTarball():
logging.info(logRoller.fileDownloading.format(f"octosuite.v{version_tag}.tar"))
xprint(SignVar.info, logRoller.fileDownloading.format(f"octosuite.v{version_tag}.tar"))
data = requests.get(f"{endpoint}/repos/rly0nheart/octosuite/tarballball/{version_tag}")
with open(f"downloads/octosuite.v{version_tag}.tar", "wb") as file:
file.write(data.content)
file.close()
logging.info(logRoller.fileDownloaded.format(f"octosuite.v{version_tag}.tar"))
xprint(SignVar.positive, logRoller.fileDownloaded.format(f"octosuite.v{version_tag}.tar"))
# Downloading release zipball
def downloadZipball():
logging.info(logRoller.fileDownloading.format(f"octosuite.v{version_tag}.zip"))
xprint(SignVar.info, logRoller.fileDownloading.format(f"octosuite.v{version_tag}.zip"))
data = requests.get(f"{endpoint}/repos/rly0nheart/octosuite/zipball/{version_tag}")
with open(f"downloads/octosuite.v{version_tag}.zip", "wb") as file:
file.write(data.content)
file.close()
logging.info(logRoller.fileDownloaded.format(f"octosuite.v{version_tag}.zip"))
xprint(SignVar.positive, logRoller.fileDownloaded.format(f"octosuite.v{version_tag}.zip"))
def versionCheck():
response = requests.get(f"{endpoint}/repos/rly0nheart/octosuite/releases/latest")
if response.json()['tag_name'] == version_tag:
xprint(f"{SignVar.positive} Octosuite is up to date. Check again soon! :)")
else:
xprint(f"{SignVar.info} A new release is available (octosuite.v{response.json()['tag_name']}). Exit Octosuite and run '{green_bold}pip install --upgrade octosuite{white}' to download and install the update.")
# Author info
def author():
xprint(f"{white}Richard Mwewa (Ritchie){reset}")
for key,value in author_dict.items():
xprint(f"{white}├─ {key}:{reset} {value}")
# About program
def about():
xprint(f"""
{white_bold}OCTOSUITE © 2022 Richard Mwewa{reset}
An advanced and lightning fast framework for gathering open-source intelligence on GitHub users and organizations.
With over 20+ features, Octosuite only runs on 2 external dependencies, and returns the gathered intelligence in a highly readable format.
'_This is how you gather GitHub OSINT like a god:fire:_'
{white_bold}Read the wiki:{reset} https://github.com/rly0nheart/octosuite/wiki
{white_bold}GitHub REST API documentation:{reset} https://docs.github.com/rest
""")
# Close session
def exitSession():
xprint(f"{SignVar.prompt} This will close the current session, continue? (Y/n) ", end="");prompt = input().lower()
if prompt == 'y':
logging.info(logRoller.sessionClosed.format(datetime.now()))
xprint(f"{SignVar.info} {logRoller.sessionClosed.format(datetime.now())}")
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 lol
'''
xprint(f"""
{white_bold}Whats new in v{version_tag}?{reset}
[ {green}improvement{reset} ] The Octosuite GUI (.exe/.app) is now available on GitHub
""")

15
octosuite/sign_vars.py Normal file
View File

@@ -0,0 +1,15 @@
from octosuite.colors import red, white, green, reset
'''
SignVar
*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"{white}[{green} ? {white}]{reset}"
warning = f"{white}[{red} ! {white}]{reset}"
error = f"{white}[{red} x {white}]{reset}"
positive = f"{white}[{green} + {white}]{reset}"
negative = f"{white}[{red} - {white}]{reset}"
info = f"{white}[{green} * {white}]{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.2.2",
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", "rich"],
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,468 +0,0 @@
import os
import logging
import requests
import platform
import subprocess
import urllib.request
from tqdm import tqdm
from pprint import pprint
from lib import colors,banner
from datetime import datetime
class octosuite:
def __init__(self):
# Path attribute
self.path_attrs =['size','type','path','sha','html_url']
# Path attribute dictionary
self.path_attr_dict = {'size': 'Size (bytes)',
'type': 'Type',
'path': 'Path',
'sha': 'SHA',
'html_url': 'URL'}
# Organization attributes
self.org_attrs = ['avatar_url','login','id','node_id','email','description','blog','location','followers','following','twitter_username','public_gists','public_repos','type','is_verified','has_organization_projects','has_repository_projects','created_at','updated_at']
# Organization attribute dictionary
self.org_attr_dict = {'avatar_url': 'Profile Photo',
'login': 'Username',
'id': 'ID#',
'node_id': 'Node ID',
'email': 'Email',
'description': 'About',
'location': 'Location',
'blog': 'Blog',
'followers': 'Followers',
'following': 'Following',
'twitter_username': 'Twitter Handle',
'public_gists': 'Gists (public)',
'public_repos': 'Repositories (public)',
'type': 'Account type',
'is_verified': 'Is verified?',
'has_organization_projects': 'Has organization projects?',
'has_repository_projects': 'Has repository projects?',
'created_at': 'Created at',
'updated_at': 'Updated at'}
# Repository attributes
self.repo_attrs = ['id','description','forks','allow_forking','fork','stargazers_count','watchers','license','default_branch','visibility','language','open_issues','topics','homepage','clone_url','ssh_url','private','archived','has_downloads','has_issues','has_pages','has_projects','has_wiki','pushed_at','created_at','updated_at']
# Repository attribute dictionary
self.repo_attr_dict = {'id': 'ID#',
'description': 'About',
'forks': 'Forks',
'allow_forking': 'Is forkable?',
'fork': 'Is fork?',
'stargazers_count': 'Stars',
'watchers': 'Watchers',
'license': 'License',
'default_branch': 'Branch',
'visibility': 'Visibility',
'language': 'Language(s)',
'open_issues': 'Open issues',
'topics': 'Topics',
'homepage': 'Homepage',
'clone_url': 'Clone URL',
'ssh_url': 'SSH URL',
'private': 'Is private?',
'archived': 'Is archived?',
'is_template': 'Is template?',
'has_wiki': 'Has wiki?',
'has_pages': 'Has pages?',
'has_projects': 'Has projects?',
'has_issues': 'Has issues?',
'has_downloads': 'Has downloads?',
'pushed_at': 'Pushed at',
'created_at': 'Created at',
'updated_at': 'Updated at'}
# Profile attributes
self.profile_attrs = ['avatar_url','login','id','node_id','bio','blog','location','followers','following','twitter_username','public_gists','public_repos','company','hireable','site_admin','created_at','updated_at']
# Profile attribute dictionary
self.profile_attr_dict = {'avatar_url': 'Profile Photo',
'login': 'Username',
'id': 'ID#',
'node_id': 'Node ID',
'bio': 'Bio',
'blog': 'Blog',
'location': 'Location',
'followers': 'Followers',
'following': 'Following',
'twitter_username': 'Twitter Handle',
'public_gists': 'Gists (public)',
'public_repos': 'Repositories (public)',
'company': 'Organization',
'hireable': 'Is hireable?',
'site_admin': 'Is site admin?',
'created_at': 'Joined at',
'updated_at': 'Updated at'}
# User attributes
self.user_attrs = ['avatar_url','id','node_id','gravatar_id','site_admin','type','html_url']
# User attribute dictionary
self.user_attr_dict = {'avatar_url': 'Profile Photo',
'id': 'ID#',
'node_id': 'Node ID',
'gravatar_id': 'Gravatar ID',
'site_admin': 'Is site admin?',
'type': 'Account type',
'html_url': 'URL'}
# Topic atrributes
self.topic_attrs = ['score','curated','featured','display_name','created_by','created_at','updated_at']
# Topic attribute dictionary
self.topic_attr_dict = {'score': 'Score',
'curated': 'Curated',
'featured': 'Featured',
'display_name': 'Display Name',
'created_by': 'Created by',
'created_at': 'Created at',
'updated_at': 'Updated at'}
# Gists attributes
self.gists_attrs = ['node_id','description','comments','files','git_push_url','public','truncated','updated_at']
# Gists attribute dictionary
self.gists_attr_dict = {'node_id': 'Node ID',
'description': 'About',
'comments': 'Comments',
'files': 'Files',
'git_push_url': 'Git Push URL',
'public': 'Is public?',
'truncated': 'Is truncated?',
'updated_at': 'Updated at'}
# Issue attributes
self.issue_attrs = ['id','node_id','score','state','number','comments','milestone','assignee','assignees','labels','locked','draft','closed_at','body']
# Issue attribute dict
self.issue_attr_dict = {'id': 'ID#',
'node_id': 'Node ID',
'score': 'Score',
'state': 'State',
'closed_at': 'Closed at',
'number': 'Number',
'comments': 'Comments',
'milestone': 'Milestone',
'assignee': 'Assignee',
'assignees': 'Assignees',
'labels': 'Labels',
'draft': 'Is draft?',
'locked': 'Is locked?',
'created_at': 'Created at',
'body': 'Body'}
# Author dictionary
self.author_dict = {'Alias': 'rly0nheart',
'Country': 'Zambia, Africa',
'About.me': 'https://about.me/rly0nheart'}
def on_start(self):
logging.info(f'Started new session on {platform.node()}')
while True:
if platform.system() == 'Windows':
subprocess.run(['cls'])
else:
subprocess.run(['clear'],shell=False)
print(banner.banner)
command = input(f'''{colors.white}┌─({colors.red}{platform.node()}{colors.white}@{colors.red}octosuite{colors.white})-[{colors.green}{os.getcwd()}{colors.white}]\n└─╼[{colors.green}:~{colors.white}]{colors.reset} ''')
if command == 'orginfo':
self.org_info()
elif command == 'userinfo':
self.user_profile()
elif command == 'repoinfo':
self.repo_info()
elif command == 'pathcontents':
self.path_contents()
elif command == 'orgrepos':
self.org_repos()
elif command == 'userrepos':
self.user_repos()
elif command == 'usergists':
self.user_gists()
elif command == 'userfollowers':
self.followers()
elif command == 'userfollowing':
self.following()
elif command == 'usersearch':
self.user_search()
elif command == 'reposearch':
self.repo_search()
elif command == 'topicsearch':
self.topic_search()
elif command == 'issuesearch':
self.issue_search()
elif command == 'commitsearch':
self.commits_search()
elif command == 'update':
self.update()
elif command == 'changelog':
print(self.changelog())
elif command == 'author':
self.author()
elif command == 'help':
print(self.help())
elif command == 'exit':
logging.info('Session terminated.')
exit(f'\n{colors.white}[{colors.red}-{colors.white}] Session terminated.{colors.reset}')
else:
print(f'\n{colors.white}[{colors.red}!{colors.white}] Unknown command: {command}{colors.reset}')
logging.warning(f'Unknown command: {command}')
input(f'\n{colors.white}^ Press any key to continue{colors.reset} ')
def org_info(self):
organization = input(f'{colors.white}@{colors.green}Organization{colors.white} >> {colors.reset}')
api = f'https://api.github.com/orgs/{organization}'
response = requests.get(api)
if response.status_code != 200:
print(f'\n{colors.white}[{colors.red}-{colors.white}] Organization @{organization} {colors.red}Not Found{colors.reset}')
else:
response = response.json()
print(f"\n{colors.white}{response['name']}{colors.reset}")
for attr in self.org_attrs:
print(f'{colors.white}├─ {self.org_attr_dict[attr]}: {colors.green}{response[attr]}{colors.reset}')
# Fetching user information
def user_profile(self):
username = input(f'{colors.white}@{colors.green}Username{colors.white} >> {colors.reset}')
api = f'https://api.github.com/users/{username}'
response = requests.get(api)
if response.status_code != 200:
print(f'\n{colors.white}[{colors.red}-{colors.white}] User @{username} {colors.red}Not Found{colors.reset}')
else:
response = response.json()
print(f"\n{colors.white}{response['name']}{colors.reset}")
for attr in self.profile_attrs:
print(f'{colors.white}├─ {self.profile_attr_dict[attr]}: {colors.green}{response[attr]}{colors.reset}')
# Fetching repository information
def repo_info(self):
username = input(f'{colors.white}@{colors.green}Owner-username{colors.white} >> {colors.reset}')
repo_name = input(f'{colors.white}%{colors.green}reponame{colors.white} >> {colors.reset}')
api = f'https://api.github.com/repos/{username}/{repo_name}'
response = requests.get(api)
if response.status_code != 200:
print(f'\n{colors.white}[{colors.red}-{colors.white}] Repository %{repo_name} {colors.red}Not Found{colors.reset}')
else:
response = response.json()
print(f"\n{colors.white}{response['full_name']}{colors.reset}")
for attr in self.repo_attrs:
print(f"{colors.white}├─ {self.repo_attr_dict[attr]}: {colors.green}{response[attr]}{colors.reset}")
# Get path contents
def path_contents(self):
username = input(f'{colors.white}@{colors.green}Owner-username{colors.white} >> {colors.reset}')
repo_name = input(f'{colors.white}%{colors.green}reponame{colors.white} >> {colors.reset}')
path_name = input(f'{colors.white}/path/name >>{colors.reset} ')
api = f'https://api.github.com/repos/{username}/{repo_name}/contents/{path_name}'
response = requests.get(api)
if response.status_code != 200:
print(f'\n{colors.white}[{colors.red}-{colors.white}] Information {colors.red}Not Found{colors.reset}')
else:
response = response.json()
for item in response:
print(f"\n{colors.white}{item['name']}{colors.reset}")
for attr in self.path_attrs:
print(f'{colors.white}├─ {self.path_attr_dict[attr]}: {colors.green}{item[attr]}{colors.reset}')
# Fetching organozation repositories
def org_repos(self):
organization = input(f'{colors.white}@{colors.green}Organization{colors.white} >> {colors.reset}')
api = f'https://api.github.com/orgs/{organization}/repos?per_page=100'
response = requests.get(api)
if response.status_code != 200:
print(f'\n{colors.white}[{colors.red}-{colors.white}] Organization @{organization} {colors.red}Not Found{colors.reset}')
else:
response = response.json()
for repo in response:
print(f"\n{colors.white}{repo['full_name']}{colors.reset}")
for attr in self.repo_attrs:
print(f"{colors.white}├─ {self.repo_attr_dict[attr]}: {colors.green}{repo[attr]}{colors.reset}")
print('\n')
# Fetching user repositories
def user_repos(self):
username = input(f'{colors.white}@{colors.green}Username{colors.white} >> {colors.reset}')
api = f'https://api.github.com/users/{username}/repos?per_page=100'
response = requests.get(api)
if response.status_code != 200:
print(f'\n{colors.white}[{colors.red}-{colors.white}] User @{username} {colors.red}Not Found{colors.reset}')
else:
response = response.json()
for repo in response:
print(f"\n{colors.white}{repo['full_name']}{colors.reset}")
for attr in self.repo_attrs:
print(f"{colors.white}├─ {self.repo_attr_dict[attr]}: {colors.green}{repo[attr]}{colors.reset}")
print('\n')
# Fetching user's gists
def user_gists(self):
username = input(f'{colors.white}@{colors.green}Username{colors.white} >> {colors.reset}')
api = f'https://api.github.com/users/{username}/gists'
response = requests.get(api).json()
if response == []:
print(f'{colors.white}[{colors.red}-{colors.white}]User @{username} does not have any active gists.{colors.reset}')
else:
for item in response:
print(f"\n{colors.white}{item['id']}{colors.reset}")
for attr in self.gists_attrs:
print(f"{colors.white}├─ {self.gists_attr_dict[attr]}: {colors.green}{item[attr]}{colors.reset}")
print('\n')
# Fetching user's followera'
def followers(self):
username = input(f'{colors.white}@{colors.green}Username{colors.white} >> {colors.reset}')
api = f'https://api.github.com/users/{username}/followers?per_page=100'
response = requests.get(api).json()
if response == []:
print(f'\n{colors.white}[{colors.red}-{colors.white}]User @{username} does not have followers.{colors.reset}')
else:
for item in response:
print(f"\n{colors.white}@{item['login']}{colors.reset}")
for attr in self.user_attrs:
print(f"{colors.white}├─ {self.user_attr_dict[attr]}: {colors.green}{item[attr]}{colors.reset}")
print('\n')
# Checking whether or not user[A] follows user[B]
def following(self):
user_a = input(f'{colors.white}@{colors.green}User[A]{colors.white} >> {colors.reset}')
user_b = input(f'{colors.white}@{colors.green}User[B]{colors.white} >> {colors.reset}')
api = f'https://api.github.com/users/{user_a}/following/{user_b}'
response = requests.get(api)
if response.status_code == 204:
print(f'{colors.white}[{colors.green}+{colors.white}] @{user_a} follows @{user_b}.{colors.reset}')
else:
print(f'{colors.white}[{colors.red}-{colors.white}] @{user_a} does not follow @{user_b}.{colors.reset}')
# User search
def user_search(self):
query = input(f'{colors.white}#{colors.green}Query{colors.white} >> {colors.reset}')
api = f'https://api.github.com/search/users?q={query}&per_page=100'
response = requests.get(api).json()
for item in response['items']:
print(f"\n{colors.white}@{item['login']}{colors.reset}")
for attr in self.user_attrs:
print(f"{colors.white}├─ {self.user_attr_dict[attr]}: {colors.green}{item[attr]}{colors.reset}")
print('\n')
# Repository search
def repo_search(self):
query = input(f'{colors.white}#{colors.green}Query{colors.white} >> {colors.reset}')
api = f'https://api.github.com/search/repositories?q={query}&per_page=100'
response = requests.get(api).json()
for item in response['items']:
print(f"\n{colors.white}{item['full_name']}{colors.reset}")
for attr in self.repo_attrs:
print(f"{colors.white}├─ {self.repo_attr_dict[attr]}: {colors.green}{item[attr]}{colors.reset}")
print('\n')
# Topics search
def topic_search(self):
query = input(f'{colors.white}#{colors.green}Query{colors.white} >> {colors.reset}')
api = f'https://api.github.com/search/topics?q={query}&per_page=100'
response = requests.get(api).json()
for item in response['items']:
print(f"\n{colors.white}{item['name']}{colors.reset}")
for attr in self.topic_attrs:
print(f"{colors.white}├─ {self.topic_attr_dict[attr]}: {colors.green}{item[attr]}{colors.reset}")
print('\n')
# Issue search
def issue_search(self):
query = input(f'{colors.white}#{colors.green}Query{colors.white} >> {colors.reset}')
api = f'https://api.github.com/search/issues?q={query}&per_page=100'
response = requests.get(api).json()
for item in response['items']:
print(f"\n{colors.white}{item['title']}{colors.reset}")
for attr in self.issue_attrs:
print(f"{colors.white}├─ {self.issue_attr_dict[attr]}: {colors.green}{item[attr]}{colors.reset}")
print('\n')
# Commits search
def commits_search(self):
query = input(f'{colors.white}#{colors.green}Query{colors.white} >> {colors.reset}')
api = f'https://api.github.com/search/commits?q={query}&per_page=100'
response = requests.get(api).json()
n=0
for item in response['items']:
n+=1
print(f'{colors.white}{n}.{colors.reset}')
pprint(item['commit'])
print('\n')
# Update program
def update(self):
logging.info('Fetching updates...')
files_to_update = ['src/main.py','lib/banner.py','lib/colors.py','octosuite','.github/dependabot.yml','LICENSE','README.md','requirements.txt']
for file in tqdm(files_to_update,desc=f'{colors.white}[{colors.green}*{colors.white}] Fetching updates...{colors.reset}'):
data = urllib.request.urlopen(f'https://raw.githubusercontent.com/rly0nheart/octosuite/master/{file}').read()
with open(file, 'wb') as code:
code.write(data)
code.close()
logging.info('Update complete.')
exit(f'{colors.white}[{colors.green}+{colors.white}] Update complete. Re-run octosuite.{colors.reset}')
# Show changelog
def changelog(self):
# lol yes the changelog is hard coded
changelog_text = '''
v1.4.0 Changelog:
• Minor changes to the interface
'''
return changelog_text
# Author info
def author(self):
print(f'\n{colors.white}Richard Mwewa (Ritchie){colors.reset}')
for key,value in self.author_dict.items():
print(f'{colors.white}├─ {key}: {colors.green}{value}{colors.reset}')
def help(self):
help = '''
help:
Command Descritption
------------ ---------------------------------------------------------
orginfo --> Get target organization info
userinfo --> Get target user profile info
repoinfo --> Get target repository info
pathcontents --> Get contents of a specified path from a target repository
orgrepos --> Get a list of repositories owned by a target organization
userrepos --> Get a list of repositories owned by a target user
usergists --> Get a list of gists owned by a target user
userfollowers --> Get a list of the target's followers
userfollowing --> Check whether or not User[A] follows User[B]
usersearch --> Search user(s)
reposearch --> Search repositor[y][ies]
topicsearch --> Search topic(s)
issuesearch --> Search issue(s)
commitsearch --> Search commit(s)
update --> Update octosuite
changelog --> Show changelog
author --> Show author info
help --> Show usage/help
exit --> Exit session
'''
return help
# Set to automatically monitor and log network and user activity into the .logs folder
logging.basicConfig(filename=f'.logs/{datetime.now()}.log',format='[%(asctime)s] %(message)s',level=logging.DEBUG)