Compare commits
351 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
79a97a7883 | ||
|
|
ac6ebc6d72 | ||
|
|
a12210cfa2 | ||
|
|
47e0b4c64a | ||
|
|
c4847773a0 | ||
|
|
832e887302 | ||
|
|
8487898f93 | ||
|
|
4d8b7394f0 | ||
|
|
e925c9cb8a | ||
|
|
dd2edfd204 | ||
|
|
13efce4f9c | ||
|
|
928ba6580f | ||
|
|
457c2a5472 | ||
|
|
c6c037247f | ||
|
|
f84fcf0d35 | ||
|
|
8424c94bec | ||
|
|
a14b356b18 | ||
|
|
f60f088f05 | ||
|
|
c2a5dc89ea | ||
|
|
6a7b61b1f2 | ||
|
|
4427f30f77 | ||
|
|
9982727de1 | ||
|
|
866621db18 | ||
|
|
48b86cb2d0 | ||
|
|
96b10f0bf9 | ||
|
|
53ccc9899b | ||
|
|
bb82427abe | ||
|
|
ee4af7d266 | ||
|
|
3bd6af4f28 | ||
|
|
7531aa4f02 | ||
|
|
f9d56c51a0 | ||
|
|
5bff3ccc12 | ||
|
|
272fcc36ed | ||
|
|
64c1063ea5 | ||
|
|
4c7109f4f0 | ||
|
|
d907167713 | ||
|
|
9eec2e323d | ||
|
|
ead76074f5 | ||
|
|
e87560939f | ||
|
|
3f1191758b | ||
|
|
34c7cd3918 | ||
|
|
52c4101a7a | ||
|
|
0677b901f7 | ||
|
|
49215fd03e | ||
|
|
8fc14d3ec4 | ||
|
|
3da2b91fb2 | ||
|
|
b6908a19f4 | ||
|
|
4db8300e2c | ||
|
|
07ca8c486c | ||
|
|
9473b0278d | ||
|
|
4113c62d09 | ||
|
|
f235bc1186 | ||
|
|
4d0e7beea7 | ||
|
|
7b1fd3addc | ||
|
|
3968f53e98 | ||
|
|
bd9c1f210f | ||
|
|
0518f33571 | ||
|
|
c783a717a0 | ||
|
|
ea3b8bcd71 | ||
|
|
cdf04fe443 | ||
|
|
f3b90f2e6d | ||
|
|
bf77bc4dfe | ||
|
|
157cb68142 | ||
|
|
53433c119b | ||
|
|
5d41e07bc2 | ||
|
|
331c81ac46 | ||
|
|
e3c151e2d3 | ||
|
|
a89596efc2 | ||
|
|
283a92fa84 | ||
|
|
beb8d561f8 | ||
|
|
b68842df1c | ||
|
|
dd7fcc455d | ||
|
|
b3ae15a062 | ||
|
|
4c8d3626b3 | ||
|
|
1f86ad4034 | ||
|
|
e151f81d76 | ||
|
|
a69fbf33d9 | ||
|
|
227fcd0482 | ||
|
|
8d04112d32 | ||
|
|
66373fd96e | ||
|
|
c4f1940bb6 | ||
|
|
f79813ed45 | ||
|
|
ffd162d881 | ||
|
|
558715fdab | ||
|
|
9984c71a57 | ||
|
|
bb2efb903b | ||
|
|
defe1b1159 | ||
|
|
347acea797 | ||
|
|
46937abb7e | ||
|
|
2963db3608 | ||
|
|
92d4f3f9f2 | ||
|
|
02df05df39 | ||
|
|
549f1940f9 | ||
|
|
e936525f81 | ||
|
|
29eb2bde95 | ||
|
|
d27d9b9168 | ||
|
|
a6ca1e3c0c | ||
|
|
5bf7ef3f39 | ||
|
|
ae73fdffde | ||
|
|
7c8fea02e6 | ||
|
|
40fffcab73 | ||
|
|
9a6127d6bc | ||
|
|
eb724f629f | ||
|
|
6d04b990db | ||
|
|
2537bcbab8 | ||
|
|
bb6c5a09e9 | ||
|
|
083d3e9f71 | ||
|
|
497c855648 | ||
|
|
c2641b3134 | ||
|
|
a108b4784e | ||
|
|
597ad07490 | ||
|
|
94c1e0a737 | ||
|
|
6e83bcf367 | ||
|
|
a57f5d47e2 | ||
|
|
3fa3e7a182 | ||
|
|
d42b7af59f | ||
|
|
8d337c621c | ||
|
|
5842a13975 | ||
|
|
e739f78eb9 | ||
|
|
26cf25bf91 | ||
|
|
ad6959d1b9 | ||
|
|
d7af8602fe | ||
|
|
29d40ec27e | ||
|
|
6a0953401b | ||
|
|
e87e5c64c8 | ||
|
|
c53ab2a00b | ||
|
|
b30ad16719 | ||
|
|
cfa032f7cb | ||
|
|
87f287bfb9 | ||
|
|
ab70a10d19 | ||
|
|
02e19baa93 | ||
|
|
0b76846968 | ||
|
|
9614785455 | ||
|
|
6ae34437a4 | ||
|
|
5a1b5378ee | ||
|
|
64d1b6bf38 | ||
|
|
09fffdf192 | ||
|
|
8e4520b6d2 | ||
|
|
420186bec4 | ||
|
|
758676ac88 | ||
|
|
310b1fb702 | ||
|
|
6822806a77 | ||
|
|
4eddd20480 | ||
|
|
95dcfbea6e | ||
|
|
258be0a2d7 | ||
|
|
3b329d039a | ||
|
|
fec88ac343 | ||
|
|
272cd49d0e | ||
|
|
cea07f187a | ||
|
|
0f96785123 | ||
|
|
000eb80fbc | ||
|
|
e4f1e2928a | ||
|
|
73fa406e82 | ||
|
|
e99e0c317e | ||
|
|
7d2eada3f9 | ||
|
|
f706a08859 | ||
|
|
b3ccb713e1 | ||
|
|
0bb330d771 | ||
|
|
df339d33df | ||
|
|
c296f09e0e | ||
|
|
04ea880276 | ||
|
|
eef44716d5 | ||
|
|
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 | ||
|
|
3cbdd23056 | ||
|
|
4f267986d3 | ||
|
|
b5474bb6e2 | ||
|
|
132ef4f2ac | ||
|
|
e450c43f19 |
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__/
|
||||
11
Dockerfile
Normal file
@@ -0,0 +1,11 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
FROM python:latest
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN pip install --upgrade pip && pip install build && python -m build && pip install dist/*.whl
|
||||
|
||||
ENTRYPOINT ["octosuite"]
|
||||
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
|
||||
|
||||
97
README.md
@@ -1,56 +1,67 @@
|
||||

|
||||

|
||||
|
||||
Advanced Github OSINT Framework
|
||||
A framework for gathering open-source intelligence on GitHub users, repositories and organizations
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||

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

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
|
||||
# Installation
|
||||
**clone project**:
|
||||

|
||||
|
||||
```
|
||||
$ git clone https://github.com/rly0nheart/octosuite.git
|
||||
```
|
||||
|
||||
```
|
||||
$ cd octosuite
|
||||
```
|
||||
|
||||
# Usage
|
||||
**Linux**:
|
||||
```
|
||||
$ sudo chmod +x octosuite
|
||||
```
|
||||
|
||||
```
|
||||
$ ./octosuite
|
||||
```
|
||||
|
||||
**Windows**:
|
||||
```
|
||||
$ python3 octosuite
|
||||
```
|
||||

|
||||
|
||||
|
||||
# Note
|
||||
* Requirement(s) [requests] will be installed on first run
|
||||
* Enter *help* command to show usage
|
||||
* octosuite automatically logs network and minor user activity. The logs are saved by date and time in .logs folder
|
||||
# Wiki
|
||||
[Refer to the Wiki](https://github.com/bellingcat/octosuite/wiki) for installation instructions, in addition to all other documentation.
|
||||
|
||||
* octosuite has only been tested on *Linux* for now, please be sure to let me know how it works on *Windows*
|
||||
# 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] All the above can be used with command-line arguments (PyPI Package only)
|
||||
- [x] ...And more
|
||||
|
||||
* Feel free to open pull requests if there are any notable bugs found
|
||||
## Note
|
||||
> Octosuite automatically logs network and user activity of each session, the logs are saved by date and time in the .logs folder
|
||||
|
||||
|
||||
# LICENSE
|
||||
# License
|
||||

|
||||
|
||||
# About me
|
||||
* [About.me](https://about.me/rly0nheart)
|
||||
# Donations
|
||||
If you like OctoSuite and would like to show support, you can Buy A Coffee for the developer 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 will be 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.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 @@
|
||||
|
||||
23
octosuite/banner.py
Normal file
@@ -0,0 +1,23 @@
|
||||
import getpass
|
||||
from octosuite.config import red, white, green, reset, Tree
|
||||
|
||||
|
||||
# banner.py
|
||||
# This file holds the program's banner and version tag
|
||||
version_tag = "3.0.1"
|
||||
|
||||
|
||||
def banner():
|
||||
banner_tree = Tree(getpass.getuser())
|
||||
banner_tree.add(f"use ‘{green}help{reset}’ command for usage")
|
||||
banner_tree.add(f"commands are case insensitive\n")
|
||||
return f"""
|
||||
_______ __ _______ __ __
|
||||
| |.----.| |_.-----.| __|.--.--.|__| |_.-----.
|
||||
| - || __|| _| _ ||__ || | || | _| -__|
|
||||
|_______||____||____|_____||_______||_____||__|____|_____|
|
||||
v{version_tag}#dev
|
||||
{white}— Advanced Github {red}OSINT{white} Framework
|
||||
|
||||
|
||||
""", banner_tree
|
||||
200
octosuite/config.py
Normal file
@@ -0,0 +1,200 @@
|
||||
import psutil
|
||||
import platform
|
||||
import argparse
|
||||
from rich.tree import Tree
|
||||
from rich.text import Text
|
||||
from rich.table import Table
|
||||
from datetime import datetime
|
||||
from rich import print as xprint
|
||||
from rich.prompt import Prompt, Confirm
|
||||
|
||||
|
||||
def usage():
|
||||
return """
|
||||
Basic Usage
|
||||
===========
|
||||
|
||||
Get User Profile Info
|
||||
---------------------
|
||||
octosuite --method user_profile --username <username>
|
||||
|
||||
|
||||
Get User Repos
|
||||
--------------
|
||||
octosuite --method user_repos --username <username>
|
||||
|
||||
|
||||
Get Organi[sz]ation Profile Info
|
||||
-----------------------------
|
||||
octosuite --method org_profile --organization <organization_name>
|
||||
|
||||
|
||||
Get Organi[sz]ation Repos
|
||||
-----------------------------
|
||||
octosuite --method org_repos --organization <organization_name>
|
||||
|
||||
|
||||
Get Repo Profile Info
|
||||
---------------------
|
||||
octosuite --method repo_profile --username <username> --repository <repo_name>
|
||||
|
||||
|
||||
Get Repo Forks
|
||||
--------------
|
||||
octosuite --method repo_forks --username <username> --repository <repo_name>
|
||||
|
||||
|
||||
|
||||
Searching
|
||||
=========
|
||||
|
||||
Search Users
|
||||
------------
|
||||
octosuite --method users_search --query <query>
|
||||
|
||||
|
||||
Search Issues
|
||||
-------------
|
||||
octosuite --method issues_search --query <query>
|
||||
|
||||
|
||||
Search Commits
|
||||
--------------
|
||||
octosuite --method commits_search --query <query>
|
||||
|
||||
|
||||
Search Topics
|
||||
-------------
|
||||
octosuite --method topics_search --query <query>
|
||||
|
||||
|
||||
Search Repositories
|
||||
-------------------
|
||||
octosuite --method repos_search --query <query>
|
||||
|
||||
|
||||
|
||||
Log Management
|
||||
==============
|
||||
|
||||
View logs
|
||||
---------
|
||||
octosuite --method view_logs
|
||||
|
||||
|
||||
Read log
|
||||
--------
|
||||
octosuite --method read_log --log-file <log_file>
|
||||
|
||||
|
||||
Delete log
|
||||
----------
|
||||
octosuite --method delete_log --log-file <log_file>
|
||||
|
||||
|
||||
Clear logs
|
||||
----------
|
||||
octosuite --method clear_logs
|
||||
|
||||
|
||||
|
||||
CSV Management
|
||||
==============
|
||||
|
||||
View CSV
|
||||
---------
|
||||
octosuite --method view_csv
|
||||
|
||||
|
||||
Read CSV
|
||||
--------
|
||||
octosuite --method read_csv --csv-file <csv_file>
|
||||
|
||||
|
||||
Delete CSV
|
||||
----------
|
||||
octosuite --method delete_csv --csv-file <csv_file>
|
||||
|
||||
|
||||
Clear CSV's
|
||||
-----------
|
||||
octosuite --method clear_csv
|
||||
"""
|
||||
|
||||
|
||||
def create_parser():
|
||||
parser = argparse.ArgumentParser(description='OCTOSUITE: Advanced GitHub osint framework — by Richard Mwewa | https://about.me/rly0nheart', usage=usage())
|
||||
parser.add_argument('-m', '--method', help='method', choices=['user_profile', 'user_repos', 'user_gists', 'user_orgs', 'user_events',
|
||||
'user_subscriptions', 'user_following', 'user_followers', 'user_follows',
|
||||
'org_profile', 'org_repos', 'org_events', 'org_member',
|
||||
'repo_profile', 'repo_contributors', 'repo_stargazers', 'repo_forks',
|
||||
'repo_issues', 'repo_releases', 'repo_path_contents', 'users_search', 'issues_search',
|
||||
'commits_search', 'topics_search', 'repos_search', 'view_logs', 'read_log', 'delete_log',
|
||||
'clear_logs', 'view_csv', 'read_csv', 'delete_csv', 'clear_csv', 'about', 'author'])
|
||||
parser.add_argument('-u', '--username', help='username')
|
||||
parser.add_argument('-uB', '--username_b', help='username_B (used with user_follows)')
|
||||
parser.add_argument('-o', '--organization', '--organisation', help='organi[sz]ation name')
|
||||
parser.add_argument('-r', '--repository', help='repository name')
|
||||
parser.add_argument('-p', '--path_name', help='path name (used with repo_path_contents)')
|
||||
parser.add_argument('-q', '--query', help='query (used with search methods)')
|
||||
parser.add_argument('-l', '--limit', help='output limit (used with methods that return results in bulk) (default: %(default)s)', default=10)
|
||||
parser.add_argument('-c', '--colors', '--colours', help='specify to run octosuite cli with colo[u]rs enabled', action='store_true')
|
||||
parser.add_argument('--csv_file', help='csv file (used with csv management methods)')
|
||||
parser.add_argument('--log_file', help='log file (used with logs management methods)')
|
||||
parser.add_argument('--log-to-csv', help='log output to a csv file', action='store_true', dest='log_csv')
|
||||
return parser
|
||||
|
||||
|
||||
parser = create_parser()
|
||||
args = parser.parse_args()
|
||||
|
||||
# This file is responsible for enabling/disabling colo[u]rs and configuring argparse in OctoSuite
|
||||
# This file gets called first at start up before any other file
|
||||
# config.py is the reason why users get to choose whether to enable/disable colo[u]rs, and call the program with command line arguments
|
||||
# delete this file (I dare you), the entire program breaks
|
||||
system_info = [("RAM", f"{str(round(psutil.virtual_memory().total / (1024.0 ** 3)))}GB"),
|
||||
("Node", platform.node()),
|
||||
("Release", platform.release()),
|
||||
("Version", platform.version()),
|
||||
("Processor", platform.processor()),
|
||||
("Architecture", platform.architecture())]
|
||||
first_banner = f"""
|
||||
OCTOSUITE © 2023 Richard Mwewa
|
||||
{datetime.now().strftime('%A %d %B %Y, %H:%M:%S%p')}
|
||||
|
||||
"""
|
||||
|
||||
if args.colors:
|
||||
header_title = "bold white"
|
||||
red = "[red]"
|
||||
white = "[white]"
|
||||
green = "[green]"
|
||||
yellow = "[yellow]"
|
||||
red_bold = "[white bold]"
|
||||
white_bold = "[white bold]"
|
||||
green_bold = "[green bold]"
|
||||
reset = "[/]"
|
||||
else:
|
||||
print(first_banner)
|
||||
system_tree = Tree(platform.system())
|
||||
for system_key, system_value in system_info:
|
||||
system_tree.add(f"{system_key}: {system_value}")
|
||||
xprint(system_tree)
|
||||
print("\n")
|
||||
try:
|
||||
color_chooser = Confirm.ask(f"Welcome, would you like to enable colo(u)rs for this session?")
|
||||
if color_chooser:
|
||||
header_title = "bold white"
|
||||
red = "[red]"
|
||||
white = "[white]"
|
||||
green = "[green]"
|
||||
yellow = "[yellow]"
|
||||
red_bold = "[white bold]"
|
||||
white_bold = "[white bold]"
|
||||
green_bold = "[green bold]"
|
||||
reset = "[/]"
|
||||
else:
|
||||
header_title = red = white = green = red_bold = white_bold = green_bold = reset = yellow = ""
|
||||
except KeyboardInterrupt:
|
||||
exit(f"[WARNING] Process interrupted with Ctrl+C.")
|
||||
|
||||
442
octosuite/csv_loggers.py
Normal file
@@ -0,0 +1,442 @@
|
||||
import os
|
||||
import csv
|
||||
import logging
|
||||
from rich import print as xprint
|
||||
from octosuite.log_roller import prompt_log_csv, logged_to_csv
|
||||
from octosuite.message_prefixes import PROMPT, WARNING, POSITIVE, NEGATIVE, INFO
|
||||
|
||||
|
||||
# csv_loggers.py
|
||||
# This file holds the functions for creating .csv files of each functionality in main
|
||||
def log_org_profile(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']]
|
||||
|
||||
with open(os.path.join("output", f"{response.json()['name']}.csv"), 'w') as file:
|
||||
write_csv = csv.writer(file)
|
||||
write_csv.writerow(org_profile_fields)
|
||||
write_csv.writerow(org_profile_row)
|
||||
|
||||
logging.info(logged_to_csv.format(file.name))
|
||||
xprint(f"{POSITIVE} {logged_to_csv.format(file.name)}")
|
||||
|
||||
|
||||
# Creating a .csv file of a user' profile
|
||||
def log_user_profile(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']]
|
||||
|
||||
with open(os.path.join("output", f"{response.json()['login']}.csv"), 'w') as file:
|
||||
write_csv = csv.writer(file)
|
||||
write_csv.writerow(user_profile_fields)
|
||||
write_csv.writerow(user_profile_row)
|
||||
|
||||
logging.info(logged_to_csv.format(file.name))
|
||||
xprint(f"{POSITIVE} {logged_to_csv.format(file.name)}")
|
||||
|
||||
|
||||
# create .csv for repository profile
|
||||
def log_repo_profile(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']]
|
||||
|
||||
with open(os.path.join("output", f"{response.json()['name']}.csv"), 'w') as file:
|
||||
write_csv = csv.writer(file)
|
||||
write_csv.writerow(repo_profile_fields)
|
||||
write_csv.writerow(repo_profile_row)
|
||||
|
||||
logging.info(logged_to_csv.format(file.name))
|
||||
xprint(f"{POSITIVE} {logged_to_csv.format(file.name)}")
|
||||
|
||||
|
||||
# create .csv for repository path contents
|
||||
def log_repo_path_contents(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']]
|
||||
|
||||
with open(os.path.join("output", f"{content['name']}_content_from_{repo_name}.csv"), 'w') as file:
|
||||
write_csv = csv.writer(file)
|
||||
write_csv.writerow(path_content_fields)
|
||||
write_csv.writerow(path_content_row)
|
||||
|
||||
logging.info(logged_to_csv.format(file.name))
|
||||
xprint(f"{POSITIVE} {logged_to_csv.format(file.name)}")
|
||||
|
||||
|
||||
# create .csv for repository stargazer
|
||||
def log_repo_stargazers(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']]
|
||||
|
||||
with open(os.path.join("output", f"{stargazer['login']}_stargazer_of_{repo_name}.csv"), 'w') as file:
|
||||
write_csv = csv.writer(file)
|
||||
write_csv.writerow(user_follower_fields)
|
||||
write_csv.writerow(user_follower_row)
|
||||
|
||||
logging.info(logged_to_csv.format(file.name))
|
||||
xprint(f"{POSITIVE} {logged_to_csv.format(file.name)}")
|
||||
|
||||
# create .csv for repository forks
|
||||
def log_repo_forks(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']]
|
||||
|
||||
with open(os.path.join("output", f"{fork['name']}_fork_{count}.csv"), 'w') as file:
|
||||
write_csv = csv.writer(file)
|
||||
write_csv.writerow(repo_fork_fields)
|
||||
write_csv.writerow(repo_fork_row)
|
||||
|
||||
logging.info(logged_to_csv.format(file.name))
|
||||
xprint(f"{POSITIVE} {logged_to_csv.format(file.name)}")
|
||||
|
||||
|
||||
# create .csv for repository issues
|
||||
def log_repo_issues(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']]
|
||||
|
||||
with open(os.path.join("output", f"{repo_name}_issue_{issue['id']}.csv"), 'w') as file:
|
||||
write_csv = csv.writer(file)
|
||||
write_csv.writerow(repo_issue_fields)
|
||||
write_csv.writerow(repo_issue_row)
|
||||
|
||||
logging.info(logged_to_csv.format(file.name))
|
||||
xprint(f"{POSITIVE} {logged_to_csv.format(file.name)}")
|
||||
|
||||
|
||||
# create .csv for repository releases
|
||||
def log_repo_releases(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']]
|
||||
|
||||
with open(os.path.join("output", f"{repo_name}_release_{release['name']}.csv"), 'w') as file:
|
||||
write_csv = csv.writer(file)
|
||||
write_csv.writerow(repo_release_fields)
|
||||
write_csv.writerow(repo_release_row)
|
||||
|
||||
logging.info(logged_to_csv.format(file.name))
|
||||
xprint(f"{POSITIVE} {logged_to_csv.format(file.name)}")
|
||||
|
||||
|
||||
# Create .csv file for repository contributors
|
||||
def log_repo_contributors(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']]
|
||||
|
||||
with open(os.path.join("output", f"{contributor['login']}_contributor_of_{repo_name}.csv"), 'w') as file:
|
||||
write_csv = csv.writer(file)
|
||||
write_csv.writerow(repo_contributor_fields)
|
||||
write_csv.writerow(repo_contributor_row)
|
||||
|
||||
logging.info(logged_to_csv.format(file.name))
|
||||
xprint(f"{POSITIVE} {logged_to_csv.format(file.name)}")
|
||||
|
||||
|
||||
# Create .csv for organization' events
|
||||
def log_repo_events(event, organization):
|
||||
org_event_fields = ['ID', 'Type', 'Created at', 'Payload']
|
||||
org_event_row = [event['id'], event['type'], event['created_at'], event['payload']]
|
||||
|
||||
with open(os.path.join("output", f"{organization}_event_{event['id']}.csv"), 'w') as file:
|
||||
write_csv = csv.writer(file)
|
||||
write_csv.writerow(org_event_fields)
|
||||
write_csv.writerow(org_event_row)
|
||||
|
||||
logging.info(logged_to_csv.format(file.name))
|
||||
xprint(f"{POSITIVE} {logged_to_csv.format(file.name)}")
|
||||
|
||||
|
||||
# Create .csv for organization' repositories
|
||||
def log_org_repos(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']]
|
||||
|
||||
with open(os.path.join("output", f"{repository['name']}_repository_of_{organization}.csv"), 'w') as file:
|
||||
write_csv = csv.writer(file)
|
||||
write_csv.writerow(org_repo_fields)
|
||||
write_csv.writerow(org_repo_row)
|
||||
|
||||
logging.info(logged_to_csv.format(file.name))
|
||||
xprint(f"{POSITIVE} {logged_to_csv.format(file.name)}")
|
||||
|
||||
|
||||
# .csv for user' repositories
|
||||
def log_user_repos(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']]
|
||||
|
||||
with open(os.path.join("output", f"{repository['name']}_{username}.csv"), 'w') as file:
|
||||
write_csv = csv.writer(file)
|
||||
write_csv.writerow(user_repo_fields)
|
||||
write_csv.writerow(user_repo_row)
|
||||
|
||||
logging.info(logged_to_csv.format(file.name))
|
||||
xprint(f"{POSITIVE} {logged_to_csv.format(file.name)}")
|
||||
|
||||
|
||||
# .csv for user events
|
||||
def log_user_events(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']]
|
||||
|
||||
with open(os.path.join("output", f"{event['actor']['login']}_event_{event['id']}.csv"), 'w') as file:
|
||||
write_csv = csv.writer(file)
|
||||
write_csv.writerow(user_event_fields)
|
||||
write_csv.writerow(user_event_row)
|
||||
|
||||
logging.info(logged_to_csv.format(file.name))
|
||||
xprint(f"{POSITIVE} {logged_to_csv.format(file.name)}")
|
||||
|
||||
|
||||
# .csv for user gists
|
||||
def log_user_gists(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']]
|
||||
|
||||
with open(os.path.join("output", f"{gist['id']}_gists_{gist['owner']['login']}.csv"), 'w') as file:
|
||||
write_csv = csv.writer(file)
|
||||
write_csv.writerow(user_gist_fields)
|
||||
write_csv.writerow(user_gist_row)
|
||||
|
||||
logging.info(logged_to_csv.format(file.name))
|
||||
xprint(f"{POSITIVE} {logged_to_csv.format(file.name)}")
|
||||
|
||||
|
||||
# .csv for user followers
|
||||
def log_user_followers(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']]
|
||||
|
||||
with open(f"output/{follower['login']}_follower_of_{username}.csv", 'w') as file:
|
||||
write_csv = csv.writer(file)
|
||||
write_csv.writerow(user_follower_fields)
|
||||
write_csv.writerow(user_follower_row)
|
||||
|
||||
logging.info(logged_to_csv.format(file.name))
|
||||
xprint(f"{POSITIVE} {logged_to_csv.format(file.name)}")
|
||||
|
||||
|
||||
# .csv for user following
|
||||
def log_user_following(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']]
|
||||
|
||||
with open(os.path.join("output", f"{user['login']}_followed_by_{username}.csv"), 'w') as file:
|
||||
write_csv = csv.writer(file)
|
||||
write_csv.writerow(user_following_fields)
|
||||
write_csv.writerow(user_following_row)
|
||||
|
||||
logging.info(logged_to_csv.format(file.name))
|
||||
xprint(f"{POSITIVE} {logged_to_csv.format(file.name)}")
|
||||
|
||||
|
||||
# .csv for user' subscriptions
|
||||
def log_user_subscriptions(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']]
|
||||
|
||||
with open(os.path.join("output", f"{username}_subscriptions_{repository['name']}.csv"), 'w') as file:
|
||||
write_csv = csv.writer(file)
|
||||
write_csv.writerow(user_subscription_fields)
|
||||
write_csv.writerow(user_subscription_row)
|
||||
|
||||
logging.info(logged_to_csv.format(file.name))
|
||||
xprint(f"{POSITIVE} {logged_to_csv.format(file.name)}")
|
||||
|
||||
|
||||
# .csv for user organizations
|
||||
def log_user_orgs(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']]
|
||||
|
||||
with open(os.path.join("output", f"{organization['login']}_{username}.csv"), 'w') as file:
|
||||
write_csv = csv.writer(file)
|
||||
write_csv.writerow(user_org_fields)
|
||||
write_csv.writerow(user_org_row)
|
||||
|
||||
logging.info(logged_to_csv.format(file.name))
|
||||
xprint(f"{POSITIVE} {logged_to_csv.format(file.name)}")
|
||||
|
||||
|
||||
# Create .csv for user search
|
||||
def log_users_search(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']]
|
||||
|
||||
with open(os.path.join("output", f"{user['login']}_user_search_result_for_{query}.csv"), 'w') as file:
|
||||
write_csv = csv.writer(file)
|
||||
write_csv.writerow(user_search_fields)
|
||||
write_csv.writerow(user_search_row)
|
||||
|
||||
logging.info(logged_to_csv.format(file.name))
|
||||
xprint(f"{POSITIVE} {logged_to_csv.format(file.name)}")
|
||||
|
||||
|
||||
# Create .csv for repository search
|
||||
def log_repos_search(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']]
|
||||
|
||||
with open(os.path.join("output", f"{repository['name']}_repository_search_result_for_{query}.csv"), 'w') as file:
|
||||
write_csv = csv.writer(file)
|
||||
write_csv.writerow(repo_search_fields)
|
||||
write_csv.writerow(repo_search_row)
|
||||
|
||||
logging.info(logged_to_csv.format(file.name))
|
||||
xprint(f"{POSITIVE} {logged_to_csv.format(file.name)}")
|
||||
|
||||
|
||||
# Create .csv for topic search
|
||||
def log_topics_search(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']]
|
||||
|
||||
with open(os.path.join("output", f"{topic['name']}_topic_search_result_for_{query}.csv"), 'w') as file:
|
||||
write_csv = csv.writer(file)
|
||||
write_csv.writerow(topic_search_fields)
|
||||
write_csv.writerow(topic_search_row)
|
||||
|
||||
logging.info(logged_to_csv.format(file.name))
|
||||
xprint(f"{POSITIVE} {logged_to_csv.format(file.name)}")
|
||||
|
||||
|
||||
# Create .csv for issues search
|
||||
def log_issues_search(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']]
|
||||
|
||||
with open(os.path.join("output", f"{issue['id']}_issue_search_result_for_{query}.csv"), 'w') as file:
|
||||
write_csv = csv.writer(file)
|
||||
write_csv.writerow(issue_search_fields)
|
||||
write_csv.writerow(issue_search_row)
|
||||
|
||||
logging.info(logged_to_csv.format(file.name))
|
||||
xprint(f"{POSITIVE} {logged_to_csv.format(file.name)}")
|
||||
|
||||
|
||||
# Create .csv for commits search
|
||||
def log_commits_search(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']]
|
||||
|
||||
with open(os.path.join("output", f"{commit['commit']['tree']['sha']}_commit_search_result_for_{query}.csv"), 'w') as file:
|
||||
write_csv = csv.writer(file)
|
||||
write_csv.writerow(commit_search_fields)
|
||||
write_csv.writerow(commit_search_row)
|
||||
|
||||
logging.info(logged_to_csv.format(file.name))
|
||||
xprint(f"{POSITIVE} {logged_to_csv.format(file.name)}")
|
||||
170
octosuite/helper.py
Normal file
@@ -0,0 +1,170 @@
|
||||
from rich.table import Table
|
||||
from octosuite.config import Tree, xprint, white, green, white_bold, green_bold, header_title, reset
|
||||
|
||||
# helper.py
|
||||
# This file holds the help text for available commands.
|
||||
usage_text = 'Use syntax {} to get started with %s{}%s.' % (green_bold, reset)
|
||||
usage_text_1 = '%sUse {} to view all available subcommands.%s' % (white, reset)
|
||||
usage_text_2 = "%sThe {} command works with subcommands. %s" % (white, reset)
|
||||
|
||||
|
||||
def org():
|
||||
xprint(usage_text_2.format(f"{green_bold}org{reset}") + usage_text_1.format(f"{green_bold}help:org{reset}"))
|
||||
|
||||
|
||||
def repo():
|
||||
xprint(usage_text_2.format(f"{green_bold}repo{reset}") + usage_text_1.format(f"{green_bold}help:repo{reset}"))
|
||||
|
||||
|
||||
def user():
|
||||
xprint(usage_text_2.format(f"{green_bold}user{reset}") + usage_text_1.format(f"{green_bold}help:user{reset}"))
|
||||
|
||||
|
||||
def search():
|
||||
xprint(usage_text_2.format(f"{green_bold}search{reset}") + usage_text_1.format(f"{green_bold}help:search{reset}"))
|
||||
|
||||
|
||||
def source():
|
||||
xprint(usage_text_2.format(f"{green_bold}source{reset}") + usage_text_1.format(f"{green_bold}help:source{reset}"))
|
||||
|
||||
|
||||
def logs():
|
||||
xprint(usage_text_2.format(f"{green_bold}logs{reset}") + usage_text_1.format(f"{green_bold}help:logs{reset}"))
|
||||
|
||||
|
||||
def csv():
|
||||
xprint(usage_text_2.format(f"{green_bold}csv{reset}") + usage_text_1.format(f"{green_bold}help:csv{reset}"))
|
||||
|
||||
|
||||
def source_command():
|
||||
source_cmd_table = Table(show_header=True, header_style=header_title)
|
||||
source_cmd_table.add_column("Command", style="dim")
|
||||
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"{usage_text.format(syntax, 'source code downloads')}")
|
||||
xprint(source_cmd_table)
|
||||
|
||||
|
||||
def search_command():
|
||||
search_cmd_table = Table(show_header=True, header_style=header_title)
|
||||
search_cmd_table.add_column("Command", style="dim")
|
||||
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"{usage_text.format(syntax, 'target discovery')}")
|
||||
xprint(search_cmd_table)
|
||||
|
||||
|
||||
def user_command():
|
||||
user_cmd_table = Table(show_header=True, header_style=header_title)
|
||||
user_cmd_table.add_column("Command", style="dim")
|
||||
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"{usage_text.format(syntax, 'user investigation(s)')}")
|
||||
xprint(user_cmd_table)
|
||||
|
||||
|
||||
def org_command():
|
||||
org_cmd_table = Table(show_header=True, header_style=header_title)
|
||||
org_cmd_table.add_column("Command", style="dim")
|
||||
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"{usage_text.format(syntax, 'organization investigation(s)')}")
|
||||
xprint(org_cmd_table)
|
||||
|
||||
|
||||
def repo_command():
|
||||
repo_cmd_table = Table(show_header=True, header_style=header_title)
|
||||
repo_cmd_table.add_column("Command", style="dim")
|
||||
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("contributors", "Return a repository's contributors")
|
||||
repo_cmd_table.add_row("path_contents", "List contents in a path of a repository")
|
||||
|
||||
syntax = f"{green}repo:<command>{reset}"
|
||||
xprint(f"{usage_text.format(syntax, 'repository investigation(s)')}")
|
||||
xprint(repo_cmd_table)
|
||||
|
||||
|
||||
def logs_command():
|
||||
logs_cmd_table = Table(show_header=True, header_style=header_title)
|
||||
logs_cmd_table.add_column("Command", style="dim")
|
||||
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")
|
||||
logs_cmd_table.add_row("clear", "clear logs")
|
||||
|
||||
syntax = f"{green}logs:<command>{reset}"
|
||||
xprint(f"{usage_text.format(syntax, 'log(s) management')}")
|
||||
xprint(logs_cmd_table)
|
||||
|
||||
|
||||
def csv_command():
|
||||
csv_cmd_table = Table(show_header=True, header_style=header_title)
|
||||
csv_cmd_table.add_column("Command", style="dim")
|
||||
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")
|
||||
csv_cmd_table.add_row("clear", "clear csv files")
|
||||
|
||||
syntax = f"{green}csv:<command>{reset}"
|
||||
xprint(f"{usage_text.format(syntax, 'csv management')}")
|
||||
xprint(csv_cmd_table)
|
||||
|
||||
|
||||
def help_command():
|
||||
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("ls", "List contents of the specified directory")
|
||||
core_cmd_table.add_row("cd", "Move to specified directory")
|
||||
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)")
|
||||
|
||||
syntax = f"{green}help:<command>{reset}"
|
||||
xprint(core_cmd_table)
|
||||
xprint(f"\n\n{usage_text.format(syntax, 'octosuite')}")
|
||||
xprint(help_sub_cmd_table)
|
||||
21
octosuite/log_roller.py
Normal file
@@ -0,0 +1,21 @@
|
||||
# LogRoller This class is where the main notification strings/messages are held, and are being used in two different
|
||||
# cases (they're being used by logging to be written to log files, and being printed out to the screen).
|
||||
|
||||
|
||||
ctrl_c = "Session terminated with Ctrl+C."
|
||||
error = "An error occurred: {}"
|
||||
session_opened = "Opened new session on {}:{}"
|
||||
session_closed = "Session closed at {}."
|
||||
viewing_logs = "Viewing logs"
|
||||
viewing_csv = "Viewing CSV file(s)..."
|
||||
deleted = "Deleted: {}"
|
||||
reading = "Reading: {}"
|
||||
file_downloading = "Downloading: {}"
|
||||
file_downloaded = "Downloaded: downloads/{}"
|
||||
info_not_found = "Information not found: {}, {}, {}"
|
||||
user_not_found = "User not found: @{}"
|
||||
org_not_found = "Organization not found: @{}"
|
||||
repo_or_user_not_found = "Repository or User not found: {}, @{}"
|
||||
prompt_log_csv = "Would you like to log this output to a .csv file?"
|
||||
logged_to_csv = "Output logged: {}"
|
||||
limit_output = "Limit '{}' output to how many? (1-100)"
|
||||
52
octosuite/main.py
Normal file
@@ -0,0 +1,52 @@
|
||||
# import everything from the octosuite.py file
|
||||
from octosuite.octosuite import * # I drifted away from the 'pythonic way' here
|
||||
|
||||
|
||||
def octosuite():
|
||||
try:
|
||||
run = Octosuite()
|
||||
path_finder()
|
||||
configure_logging()
|
||||
check_updates()
|
||||
if args.method:
|
||||
"""
|
||||
Iterate over the argument_map and check if the passed command line argument matches any argument in it [argument_map],
|
||||
if there's a match, we return its method. If no match is found, we do nothing (which will return the usage).
|
||||
"""
|
||||
for argument, method in run.argument_map:
|
||||
if args.method == argument:
|
||||
method()
|
||||
print("\n")
|
||||
else:
|
||||
pass
|
||||
else:
|
||||
"""
|
||||
Main loop keeps octosuite running, this will break if Octosuite detects a KeyboardInterrupt (Ctrl+C)
|
||||
or if the 'exit' command is entered.
|
||||
"""
|
||||
xprint(banner()[0], banner()[1])
|
||||
while True:
|
||||
command_input = Prompt.ask(f"{white}┌──({red}{getpass.getuser()}{white}@{red}octosuite{white})\n├──[~{green}{os.getcwd()}{white}]\n└╼{reset}")
|
||||
"""
|
||||
Iterate 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 method. If no match is found, we ignore it.
|
||||
"""
|
||||
if command_input[:2] == 'cd':
|
||||
os.chdir(command_input[3:])
|
||||
elif command_input[:2] == 'ls':
|
||||
os.system(f'dir {command_input[3:]}' if os.name == 'nt' else f'ls {command_input[3:]}')
|
||||
else:
|
||||
for command, method in run.command_map:
|
||||
if command_input == command:
|
||||
method()
|
||||
print("\n")
|
||||
else:
|
||||
pass
|
||||
|
||||
except KeyboardInterrupt:
|
||||
logging.warning(ctrl_c)
|
||||
xprint(f"\n{WARNING} {ctrl_c}")
|
||||
|
||||
except Exception as e:
|
||||
logging.error(error.format(e))
|
||||
xprint(f"{ERROR} {error.format(e)}")
|
||||
13
octosuite/message_prefixes.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from octosuite.config import red, white, green, yellow, reset
|
||||
|
||||
"""
|
||||
message prefixes 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 severity of the notifications you get in a program.
|
||||
"""
|
||||
PROMPT = f"{white}[{green}PROMPT{white}]{reset}"
|
||||
WARNING = f"{white}[{yellow}WARNING{white}]{reset}"
|
||||
ERROR = f"{white}[{red}ERROR{white}]{reset}"
|
||||
POSITIVE = f"{white}[{green}POSITIVE{white}]{reset}"
|
||||
NEGATIVE = f"{white}[{red}NEGATIVE{white}]{reset}"
|
||||
INFO = f"{white}[{green}INFO{white}]{reset}"
|
||||
1103
octosuite/octosuite.py
Normal file
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="3.0.2",
|
||||
author="Richard Mwewa",
|
||||
author_email="rly0nheart@duck.com",
|
||||
packages=["octosuite"],
|
||||
description="Advanced Github OSINT Framework",
|
||||
long_description=long_description,
|
||||
long_description_content_type="text/markdown",
|
||||
url="https://github.com/bellingcat/octosuite",
|
||||
license="GNU General Public License v3 (GPLv3)",
|
||||
install_requires=["requests", "rich", "psutil", "pyreadline3"],
|
||||
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:octosuite",
|
||||
]
|
||||
},
|
||||
)
|
||||
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}')
|
||||
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'{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 organization info
|
||||
userinfo --> Get user profile info
|
||||
repoinfo --> Get user repository info
|
||||
pathcontents --> Get contents of a specified path from a repository
|
||||
orgrepos --> Get organization repositories
|
||||
userrepos --> Get user repositories
|
||||
usergists --> Get user gists
|
||||
userfollowers --> Get user 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 information
|
||||
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)
|
||||