Compare commits
184 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8eea84e546 | ||
|
|
acd65e7919 | ||
|
|
4a7b199929 | ||
|
|
2723e75735 | ||
|
|
0365616478 | ||
|
|
befd61eea0 | ||
|
|
daa426d73b | ||
|
|
22df1eae73 | ||
|
|
51c1efbcec | ||
|
|
d091125c15 | ||
|
|
e2c94fcc89 | ||
|
|
16f9a80818 | ||
|
|
66d7a2c42c | ||
|
|
456d46fdb1 | ||
|
|
539c540a72 | ||
|
|
db413767cd | ||
|
|
6a87cb51de | ||
|
|
f98bfb34f3 | ||
|
|
43637193de | ||
|
|
33d9c7f5f2 | ||
|
|
835a78284b | ||
|
|
4dc2dced32 | ||
|
|
4b18c0307e | ||
|
|
0ec3100347 | ||
|
|
bb287acfe2 | ||
|
|
7a6015b8e4 | ||
|
|
d0f5c660b5 | ||
|
|
2e65cfcb79 | ||
|
|
41f392f5a0 | ||
|
|
18bcc06070 | ||
|
|
c9cf000b23 | ||
|
|
11e55f7804 | ||
|
|
96c62c7097 | ||
|
|
587641cd18 | ||
|
|
d1ad3d14b9 | ||
|
|
0e3568f967 | ||
|
|
51d27c4996 | ||
|
|
ae9d9f4af5 | ||
|
|
482f2ee55a | ||
|
|
9894b15209 | ||
|
|
4d563683d2 | ||
|
|
614a24d69c | ||
|
|
2c18f116e9 | ||
|
|
f67e876773 | ||
|
|
cf7ab5eef1 | ||
|
|
bdb88135fe | ||
|
|
e062065c75 | ||
|
|
b2557fd714 | ||
|
|
2a025190bd | ||
|
|
3b4f38e76a | ||
|
|
5bffb8a4b5 | ||
|
|
1e95f70e3e | ||
|
|
88cc04a104 | ||
|
|
3339614984 | ||
|
|
c9cb27f3fd | ||
|
|
f58736f27d | ||
|
|
822be0d088 | ||
|
|
9fe13482ff | ||
|
|
c4b8e70762 | ||
|
|
557e64c22f | ||
|
|
ba6d6da734 | ||
|
|
94a22c6de1 | ||
|
|
0ff5765dc5 | ||
|
|
bb4596c294 | ||
|
|
8967e9ae7a | ||
|
|
ccb40ec942 | ||
|
|
4e2291eba3 | ||
|
|
a9787efbe1 | ||
|
|
efd7b44131 | ||
|
|
2f40b22ab5 | ||
|
|
17075b8a4b | ||
|
|
5d7f8cfbd7 | ||
|
|
daffefe24e | ||
|
|
887957b73c | ||
|
|
2ab70c0737 | ||
|
|
0bca1d6e42 | ||
|
|
8f426eae26 | ||
|
|
d350a37cb6 | ||
|
|
151e8ba3dd | ||
|
|
23f3b43362 | ||
|
|
985325550b | ||
|
|
be3e14889d | ||
|
|
62a9556432 | ||
|
|
705966135d | ||
|
|
85e5c28a33 | ||
|
|
19165e1d48 | ||
|
|
c669aa87af | ||
|
|
7d0c462842 | ||
|
|
10e2d2dae7 | ||
|
|
d0f9ff2dd0 | ||
|
|
c88ec5f5ed | ||
|
|
161da5f5be | ||
|
|
5dd0ce300d | ||
|
|
89b98b7281 | ||
|
|
3e750bced7 | ||
|
|
8de7b0d939 | ||
|
|
6afc37d9a4 | ||
|
|
b030d48152 | ||
|
|
508df0ea77 | ||
|
|
fb7ad2cda8 | ||
|
|
b16eb762e0 | ||
|
|
8b7ac9e428 | ||
|
|
66e0da38b6 | ||
|
|
fbf7e40404 | ||
|
|
73bcb68012 | ||
|
|
850d9bb5ab | ||
|
|
17cfb5920c | ||
|
|
5ffb7758a9 | ||
|
|
2d6a808100 | ||
|
|
02feed27a8 | ||
|
|
77ddfa4b02 | ||
|
|
66192c2a63 | ||
|
|
f591764343 | ||
|
|
f6dbd8ec49 | ||
|
|
99deaf04f3 | ||
|
|
56eead2f26 | ||
|
|
73e7c339e6 | ||
|
|
3cd6442d91 | ||
|
|
7ebb90790a | ||
|
|
ca0d2dc268 | ||
|
|
51babb3c45 | ||
|
|
12e9dbcae9 | ||
|
|
be4e88d99b | ||
|
|
f7ac0fb8d5 | ||
|
|
fc7a65c593 | ||
|
|
51f2a46c65 | ||
|
|
4afbc5adaa | ||
|
|
d7980c0726 | ||
|
|
f8221d8f58 | ||
|
|
9d86c4cc7b | ||
|
|
67db855322 | ||
|
|
1b5cfa2ebf | ||
|
|
0d2a939388 | ||
|
|
52c572f255 | ||
|
|
eeda47723e | ||
|
|
1a6e3bb3c9 | ||
|
|
82b6a3fd3c | ||
|
|
c994678fa1 | ||
|
|
9ef14f3e4f | ||
|
|
23270e8da9 | ||
|
|
619070348b | ||
|
|
5718c5f229 | ||
|
|
b4f009c7d4 | ||
|
|
82a95d84b0 | ||
|
|
425525bf94 | ||
|
|
24c6b54d19 | ||
|
|
6d1e163d9a | ||
|
|
a8730864e9 | ||
|
|
315cb7ec6f | ||
|
|
2afac6c0d4 | ||
|
|
b226f1b53b | ||
|
|
bb38d95e57 | ||
|
|
eae41866ec | ||
|
|
1e71c2d3bd | ||
|
|
d91f88d31b | ||
|
|
d5119a87b0 | ||
|
|
c4bd431620 | ||
|
|
fe16b23fc7 | ||
|
|
8156af38d5 | ||
|
|
6fb6a0ec98 | ||
|
|
dc2a22aae0 | ||
|
|
a2aac88da3 | ||
|
|
3dbfc0f18e | ||
|
|
392b3022d7 | ||
|
|
162d36d741 | ||
|
|
49490ed95b | ||
|
|
78966551ee | ||
|
|
a939b89d23 | ||
|
|
f79ce44bc9 | ||
|
|
7e45fa5669 | ||
|
|
f55e1b3895 | ||
|
|
1ea78c2e0b | ||
|
|
2272e7ff81 | ||
|
|
44ce7799f4 | ||
|
|
6c6fc477f8 | ||
|
|
d7060e33dd | ||
|
|
b47b073af4 | ||
|
|
4f196660bc | ||
|
|
e073d63845 | ||
|
|
89a53d519f | ||
|
|
794bafc90f | ||
|
|
689934144c | ||
|
|
f034fac654 | ||
|
|
84bb25fb0f |
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal 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
@@ -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.
|
||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal 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.
|
||||
13
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
# Enable version updates for pip
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/" # Location of package manifests
|
||||
# Check the tqdm registry for updates every day (weekdays)
|
||||
schedule:
|
||||
interval: "daily"
|
||||
72
.github/workflows/codeql.yml
vendored
Normal 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
@@ -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
@@ -0,0 +1 @@
|
||||
__pycache__/
|
||||
5
LICENSE
@@ -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
|
||||
|
||||
117
README.md
@@ -1,72 +1,63 @@
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
[](https://github.com/rly0nheart/octosuite/actions/workflows/python-publish.yml)
|
||||
[](https://github.com/rly0nheart/octosuite/actions/workflows/codeql.yml)
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
> *Simply gather OSINT on Github users and organizations like a god🔥*
|
||||
|
||||

|
||||
> *Simply gather OSINT on Github users & organizations like a God🔥*
|
||||

|
||||

|
||||
|
||||
|
||||
# INSTALLATION
|
||||
**clone project**:
|
||||
## Wiki
|
||||
[Refer to the Wiki](https://github.com/rly0nheart/octosuite/wiki) for installation instructions, in addition to all other documentation.
|
||||
|
||||
```
|
||||
$ git clone https://github.com/rly0nheart/octosuite.git
|
||||
```
|
||||
## 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] Automatically logs network activity (.logs folder)
|
||||
- [x] User can view, read and delete logs
|
||||
- [x] ...And more
|
||||
|
||||
```
|
||||
$ cd octosuite
|
||||
```
|
||||
## 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✌🏾🙂
|
||||
|
||||
# USAGE
|
||||
**Linux**:
|
||||
```
|
||||
$ sudo chmod +x octosuite
|
||||
```
|
||||
|
||||
```
|
||||
$ ./octosuite
|
||||
```
|
||||
|
||||
**Windows**:
|
||||
```
|
||||
$ python3 octosuite
|
||||
```
|
||||
|
||||
# AVAILABLE COMMANDS
|
||||
| Command | Usage|
|
||||
| ------------- |:---------:|
|
||||
| <code>orginfo</code> | *get organization info* |
|
||||
| <code>userinfo</code> | *get user profile info* |
|
||||
| <code>repoinfo</code> | *get repository info* |
|
||||
| <code>pathcontents</code> | *get contents of a path from a specified repository* |
|
||||
| <code>orgrepos</code> | *get a list of repositories owned by a specified organization* |
|
||||
| <code>userrepos</code> | *get a list of repositories owned by a specified user* |
|
||||
| <code>usergists</code> | *get a list of gists owned by a specified user* |
|
||||
| <code>userfollowers</code> | *get a list of a user's followers* |
|
||||
| <code>userfollowing</code> | *check whether user A follows user B* |
|
||||
| <code>usersearch</code> | *search user(s)* |
|
||||
| <code>reposearch</code> | *search repositor(y)(ies)* |
|
||||
| <code>topicsearch</code> | *search topics(s)* |
|
||||
| <code>issuesearch</code> | *search issue(s)* |
|
||||
| <code>commitsearch</code> | *search commit(s)* |
|
||||
| <code>usersearch</code> | *search user(s)* |
|
||||
| <code>update</code> | *check for/download updates* |
|
||||
| <code>author</code> | *show author info* |
|
||||
| <code>help</code> | *show usage/help* |
|
||||
| <code>exit</code> | *exit session* |
|
||||
|
||||
|
||||
# NOTE
|
||||
* *octosuite automatically logs network and minor user activity. The logs are saved by date and time in .logs folder*
|
||||
* *octosuite has only been tested on **Linux** (for now), please be sure to let me know how it works on **Windows***
|
||||
|
||||
# LICENSE
|
||||
## License
|
||||

|
||||
|
||||
# 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!☕👌🏾😊
|
||||
|
||||
1
images/.images
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
BIN
images/logo.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
images/octosuite_app.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
images/octosuite_app1.png
Normal file
|
After Width: | Height: | Size: 1.0 MiB |
BIN
images/octosuite_exe.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
images/octosuite_gui_exe (1).png
Normal file
|
After Width: | Height: | Size: 248 KiB |
BIN
images/octosuite_gui_exe (2).png
Normal file
|
After Width: | Height: | Size: 264 KiB |
BIN
images/octosuite_gui_exe (3).png
Normal file
|
After Width: | Height: | Size: 257 KiB |
BIN
images/octosuite_gui_exe (4).png
Normal file
|
After Width: | Height: | Size: 234 KiB |
BIN
images/octosuite_gui_exe.png
Normal file
|
After Width: | Height: | Size: 207 KiB |
@@ -1,19 +0,0 @@
|
||||
from lib.colors import red,white,green,reset
|
||||
banner = f'''{red}
|
||||
▒█████ ▄████▄ ▄▄▄█████▓ ▒█████ ██████ █ ██ ██▓▄▄▄█████▓▓█████
|
||||
▒██▒ ██▒▒██▀ ▀█ ▓ ██▒ ▓▒▒██▒ ██▒▒██ ▒ ██ ▓██▒▓██▒▓ ██▒ ▓▒▓█ ▀
|
||||
▒██░ ██▒▒▓█ ▄ ▒ ▓██░ ▒░▒██░ ██▒░ ▓██▄ ▓██ ▒██░▒██▒▒ ▓██░ ▒░▒███
|
||||
▒██ ██░▒▓▓▄ ▄██▒░ ▓██▓ ░ ▒██ ██░ ▒ ██▒▓▓█ ░██░░██░░ ▓██▓ ░ ▒▓█ ▄
|
||||
░ ████▓▒░▒ ▓███▀ ░ ▒██▒ ░ ░ ████▓▒░▒██████▒▒▒█████▓ ░██░ ▒██▒ ░ ░▒████▒
|
||||
░ ▒░▒░▒░ ░ ░▒ ▒ ░ ▒ ░░ ░ ▒░▒░▒░ ▒ ▒▓▒ ▒ ░░▒▓▒ ▒ ▒ ░▓ ▒ ░░ ░░ ▒░ ░
|
||||
░ ▒ ▒░ ░ ▒ ░ ░ ▒ ▒░ ░ ░▒ ░ ░░░▒░ ░ ░ ▒ ░ ░ ░ ░ ░
|
||||
░ ░ ░ ▒ ░ ░ ░ ░ ░ ▒ ░ ░ ░ ░░░ ░ ░ ▒ v1.1.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}
|
||||
'''
|
||||
@@ -1,15 +0,0 @@
|
||||
import sys
|
||||
|
||||
colors = True
|
||||
machine = sys.platform # Detecting the os
|
||||
if machine.lower().startswith(("os", "win", "darwin")):
|
||||
colors = False # Colors will not be displayed
|
||||
|
||||
if not colors:
|
||||
reset = red = white = green = ""
|
||||
|
||||
else:
|
||||
white = "\033[97m"
|
||||
red = "\033[91m"
|
||||
reset = "\033[0m"
|
||||
green = "\033[92m"
|
||||
20
octosuite
@@ -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
@@ -0,0 +1 @@
|
||||
|
||||
22
octosuite/banner.py
Normal 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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}"
|
||||
31
setup.py
Normal 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",
|
||||
]
|
||||
},
|
||||
)
|
||||
466
src/main.py
@@ -1,466 +0,0 @@
|
||||
import os
|
||||
import logging
|
||||
import platform
|
||||
import subprocess
|
||||
import urllib.request
|
||||
from pprint import pprint
|
||||
from lib import colors,banner
|
||||
from datetime import datetime
|
||||
try:
|
||||
import requests
|
||||
except ImportError:
|
||||
print(f'{colors.white}[{colors.green}*{colors.white}] Installing requirement(s). Please wait...{colors.reset}')
|
||||
subprocess.run(['pip', 'install', 'requests'],shell=False)
|
||||
exit(f'{colors.white}[{colors.green}+{colors.white}] Installation complete. Re-run octosuite.{colors.reset}')
|
||||
|
||||
|
||||
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',
|
||||
'Github': 'https://github.com/rly0nheart',
|
||||
'Twitter': 'https://twitter.com/rly0nheart',
|
||||
'Facebook': 'https://fb.me/rly0nheart',
|
||||
'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 == '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('Checking for update(s)...')
|
||||
files_to_update = ['src/main.py','lib/banner.py','lib/colors.py','octosuite','LICENSE','README.md']
|
||||
print(f'\n{colors.white}[{colors.green}*{colors.white}] Fetching update(s). Please wait...{colors.reset}',end='')
|
||||
for file in files_to_update:
|
||||
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'\n{colors.white}[{colors.green}+{colors.white}] Update complete. Re-run octosuite.{colors.reset}')
|
||||
|
||||
|
||||
# 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 = '''
|
||||
|
||||
usage:
|
||||
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 --> Check for/download update(s)
|
||||
author --> Show author info
|
||||
help --> Show usage/help
|
||||
exit --> Exit session
|
||||
'''
|
||||
return help
|
||||
|
||||
|
||||
file_exists = os.path.exists('.logs')
|
||||
if file_exists:
|
||||
pass
|
||||
else:
|
||||
os.mkdir('.logs')
|
||||
|
||||
# Set to automatically monitor and log network and user activity to .log folder
|
||||
logging.basicConfig(filename=f'.logs/{datetime.now()}.log',format='[%(asctime)s] %(message)s',level=logging.DEBUG)
|
||||