├── jam-spam-ml ├── .env.example ├── requirements.txt ├── model │ ├── 1604946961 │ │ ├── group1-shard1of1.bin │ │ └── model.json │ └── 1605194508 │ │ ├── group1-shard1of1.bin │ │ └── model.json ├── data │ ├── sample.csv │ ├── ham.csv │ └── spam.csv ├── .gitignore ├── README.md ├── spam_keywords.py ├── utils.py └── train.py ├── Screenshots ├── ham.png ├── ham2.png └── spam.png ├── jam-spam-app ├── model │ ├── group1-shard1of1.bin │ └── model.json ├── .dockerignore ├── Dockerfile ├── .env.example ├── test │ ├── fixtures │ │ ├── issues.opened.json │ │ └── mock-cert.pem │ └── index.test.js ├── fetchtext.js ├── docschanged.js ├── README.md ├── .gitignore ├── package.json ├── checkspamcount.js ├── index.js └── app.yml ├── .gitignore ├── LICENSE ├── CONTRIBUTING.md ├── CODE_OF_CONDUCT.md └── README.md /jam-spam-ml/.env.example: -------------------------------------------------------------------------------- 1 | TOKEN=YOUR_GITHUB_ACCESS_TOKEN -------------------------------------------------------------------------------- /Screenshots/ham.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MLH-Fellowship/JamSpam/HEAD/Screenshots/ham.png -------------------------------------------------------------------------------- /Screenshots/ham2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MLH-Fellowship/JamSpam/HEAD/Screenshots/ham2.png -------------------------------------------------------------------------------- /Screenshots/spam.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MLH-Fellowship/JamSpam/HEAD/Screenshots/spam.png -------------------------------------------------------------------------------- /jam-spam-ml/requirements.txt: -------------------------------------------------------------------------------- 1 | octokitpy 2 | python-dotenv 3 | requests 4 | multi-rake 5 | tensorflow 6 | matplotlib 7 | tensorflowjs -------------------------------------------------------------------------------- /jam-spam-app/model/group1-shard1of1.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MLH-Fellowship/JamSpam/HEAD/jam-spam-app/model/group1-shard1of1.bin -------------------------------------------------------------------------------- /jam-spam-ml/model/1604946961/group1-shard1of1.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MLH-Fellowship/JamSpam/HEAD/jam-spam-ml/model/1604946961/group1-shard1of1.bin -------------------------------------------------------------------------------- /jam-spam-ml/model/1605194508/group1-shard1of1.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MLH-Fellowship/JamSpam/HEAD/jam-spam-ml/model/1605194508/group1-shard1of1.bin -------------------------------------------------------------------------------- /jam-spam-ml/data/sample.csv: -------------------------------------------------------------------------------- 1 | https://github.com/pallets/flask/pull/2955 2 | https://github.com/pallets/flask/pull/3772 3 | https://github.com/MLH-Fellowship/transcribio/pull/19 -------------------------------------------------------------------------------- /jam-spam-app/.dockerignore: -------------------------------------------------------------------------------- 1 | **/node_modules/ 2 | **/.git 3 | **/README.md 4 | **/LICENSE 5 | **/.vscode 6 | **/npm-debug.log 7 | **/coverage 8 | **/.env 9 | **/.editorconfig 10 | **/dist 11 | **/*.pem 12 | Dockerfile 13 | -------------------------------------------------------------------------------- /jam-spam-app/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12-slim 2 | WORKDIR /usr/src/app 3 | COPY package.json package-lock.json ./ 4 | RUN npm ci --production 5 | RUN npm cache clean --force 6 | ENV NODE_ENV="production" 7 | COPY . . 8 | CMD [ "npm", "start" ] 9 | -------------------------------------------------------------------------------- /jam-spam-app/.env.example: -------------------------------------------------------------------------------- 1 | # The ID of your GitHub App 2 | APP_ID= 3 | WEBHOOK_SECRET=development 4 | 5 | # Use `trace` to get verbose logging or `info` to show less 6 | LOG_LEVEL=debug 7 | 8 | # Go to https://smee.io/new set this to the URL that you are redirected to. 9 | WEBHOOK_PROXY_URL= 10 | -------------------------------------------------------------------------------- /jam-spam-app/test/fixtures/issues.opened.json: -------------------------------------------------------------------------------- 1 | { 2 | "action": "opened", 3 | "issue": { 4 | "number": 1, 5 | "user": { 6 | "login": "hiimbex" 7 | } 8 | }, 9 | "repository": { 10 | "name": "testing-things", 11 | "owner": { 12 | "login": "hiimbex" 13 | } 14 | }, 15 | "installation": { 16 | "id": 2 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /jam-spam-app/fetchtext.js: -------------------------------------------------------------------------------- 1 | const fetch = require('node-fetch'); 2 | 3 | const fetchText = (url) => { 4 | let settings = { method: "Get" }; 5 | return fetch(url, settings) 6 | .then ( res => { 7 | return res.text().then((data)=> { 8 | 9 | return data; 10 | }); 11 | 12 | }) 13 | } 14 | module.exports = fetchText; 15 | -------------------------------------------------------------------------------- /jam-spam-app/docschanged.js: -------------------------------------------------------------------------------- 1 | const eol = require('eol') 2 | const docschanged = (diff)=>{ 3 | var str = diff.split("\n"); 4 | //console.log(str) 5 | const lines = eol.split(diff) 6 | let count = 0; 7 | for (var i = 0; i < str.length; i++) { 8 | if (str[i].startsWith("diff") && (str[i].includes("md")||str[i].includes("txt")||str[i].includes("rst") )){ 9 | count +=1 10 | } 11 | } 12 | return count 13 | } 14 | 15 | module.exports = docschanged; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### macOS ### 2 | # General 3 | .DS_Store 4 | .AppleDouble 5 | .LSOverride 6 | 7 | # Icon must end with two \r 8 | Icon 9 | 10 | 11 | # Thumbnails 12 | ._* 13 | 14 | # Files that might appear in the root of a volume 15 | .DocumentRevisions-V100 16 | .fseventsd 17 | .Spotlight-V100 18 | .TemporaryItems 19 | .Trashes 20 | .VolumeIcon.icns 21 | .com.apple.timemachine.donotpresent 22 | 23 | # Directories potentially created on remote AFP share 24 | .AppleDB 25 | .AppleDesktop 26 | Network Trash Folder 27 | Temporary Items 28 | .apdisk 29 | .vscode 30 | -------------------------------------------------------------------------------- /jam-spam-app/README.md: -------------------------------------------------------------------------------- 1 | # jam-spam-app 2 | 3 | > A Machine Learning powered GitHub App built with [Probot](https://github.com/probot/probot) to jam the spam PRs on your repo and keep maintainers stress-free (even in Hacktober 🎃) 4 | 5 | ## Setup 6 | 7 | ```sh 8 | # Install dependencies 9 | npm install 10 | 11 | # Run the bot 12 | npm start 13 | ``` 14 | 15 | ## Docker 16 | 17 | ```sh 18 | # 1. Build container 19 | docker build -t jam-spam-app . 20 | 21 | # 2. Start container 22 | docker run -e APP_ID= -e PRIVATE_KEY= jam-spam-app 23 | ``` 24 | -------------------------------------------------------------------------------- /jam-spam-app/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | *.pem 4 | !mock-cert.pem 5 | .env 6 | coverage 7 | 8 | ### macOS ### 9 | # General 10 | .DS_Store 11 | .AppleDouble 12 | .LSOverride 13 | 14 | # Icon must end with two \r 15 | Icon 16 | 17 | 18 | # Thumbnails 19 | ._* 20 | 21 | # Files that might appear in the root of a volume 22 | .DocumentRevisions-V100 23 | .fseventsd 24 | .Spotlight-V100 25 | .TemporaryItems 26 | .Trashes 27 | .VolumeIcon.icns 28 | .com.apple.timemachine.donotpresent 29 | 30 | # Directories potentially created on remote AFP share 31 | .AppleDB 32 | .AppleDesktop 33 | Network Trash Folder 34 | Temporary Items 35 | .apdisk -------------------------------------------------------------------------------- /jam-spam-ml/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | venv/ 3 | .env 4 | 5 | ### macOS ### 6 | # General 7 | .DS_Store 8 | .AppleDouble 9 | .LSOverride 10 | 11 | # Icon must end with two \r 12 | Icon 13 | 14 | 15 | # Thumbnails 16 | ._* 17 | 18 | # Files that might appear in the root of a volume 19 | .DocumentRevisions-V100 20 | .fseventsd 21 | .Spotlight-V100 22 | .TemporaryItems 23 | .Trashes 24 | .VolumeIcon.icns 25 | .com.apple.timemachine.donotpresent 26 | 27 | # Directories potentially created on remote AFP share 28 | .AppleDB 29 | .AppleDesktop 30 | Network Trash Folder 31 | Temporary Items 32 | .apdisk 33 | 34 | checkpoint 35 | *.ckpt.* 36 | summary_logs/ 37 | -------------------------------------------------------------------------------- /jam-spam-ml/README.md: -------------------------------------------------------------------------------- 1 | # jam-spam-ml 2 | 3 | > A Machine Learning powered GitHub App built with [Probot](https://github.com/probot/probot) to jam the spam PRs on your repo and keep maintainers stress-free (even in Hacktober 🎃) 4 | 5 | ## Setup 6 | 7 | ```sh 8 | # Setting up virtual environment for local development 9 | virtualenv venv 10 | 11 | # Activate environment 12 | source venv/Scripts/activate 13 | 14 | # Install Dependencies 15 | pip install -r requirements.txt 16 | 17 | # Add GitHub Personal Access token to .env file (create a copy from .env.example file and replace YOUR_GITHUB_ACCESS_TOKEN) - allows for higher rate limits on GitHub API 18 | 19 | # Start training 20 | python train.py 21 | 22 | # Find the spam keywords 23 | python spam_keywords.py 24 | ``` 25 | -------------------------------------------------------------------------------- /jam-spam-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jam-spam-app", 3 | "version": "1.0.0", 4 | "private": true, 5 | "description": "A Machine Learning powered GitHub App to jam the spam PRs on your repo and keep maintainers stress-free (even in Hacktober 🎃)", 6 | "author": "Ajwad Shaikh & Vrushti Mody ", 7 | "license": "ISC", 8 | "repository": "https://github.com/MLH-Fellowship/JamSpam.git", 9 | "homepage": "https://github.com/MLH-Fellowship/JamSpam", 10 | "bugs": "https://github.com/MLH-Fellowship/JamSpam/issues", 11 | "keywords": [ 12 | "probot", 13 | "github", 14 | "probot-app" 15 | ], 16 | "scripts": { 17 | "start": "probot run ./index.js", 18 | "test": "jest" 19 | }, 20 | "dependencies": { 21 | "@tensorflow/tfjs-node": "^2.7.0", 22 | "eol": "^0.9.1", 23 | "node-fetch": "^2.6.1", 24 | "probot": "^10.9.3" 25 | }, 26 | "devDependencies": { 27 | "jest": "^26.4.0", 28 | "nock": "^13.0.4", 29 | "smee-client": "^1.1.0" 30 | }, 31 | "engines": { 32 | "node": ">= 10.13.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 MLH Fellowship 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /jam-spam-app/checkspamcount.js: -------------------------------------------------------------------------------- 1 | /* Spam blob */ 2 | const spam = [ 3 | "versionchanged directives", 4 | "save time", 5 | "existing algorithm", 6 | "existing functionality", 7 | "naming conventions", 8 | "understand areas", 9 | "changelog entry", 10 | "run automatically", 11 | "style guidelines", 12 | "md file", 13 | "filename follow", 14 | "auto close", 15 | "update readme", 16 | "contributing document", 17 | "update contributing", 18 | "relevant docstrings", 19 | "relevant motivation", 20 | "relevant issues", 21 | "pr fixes", 22 | "breaking change", 23 | "change requires", 24 | "delete options", 25 | "added tests", 26 | "update change", 27 | "quickly create", 28 | "provide instructions", 29 | "md describe", 30 | "issue exists", 31 | "change add", 32 | "feature works", 33 | "documentation update", 34 | "relevant details", 35 | "test configuration", 36 | ]; 37 | 38 | /* Check count of a substring in the string */ 39 | const counter = (main_str, sub_str) => { 40 | main_str += ""; 41 | sub_str += ""; 42 | return (main_str.match(new RegExp(sub_str, "gi")) || []).length; 43 | }; 44 | 45 | /* Count the total spam keywords in the PR */ 46 | const spam_count = (text_corpus) => { 47 | let count = 0; 48 | text_corpus = text_corpus.replace(/[^a-zA-Z0-9 \n\.]/g, ' '); 49 | for (word of spam) { 50 | var countSpam = counter(text_corpus, word); 51 | count = count + countSpam; 52 | } 53 | return count; 54 | }; 55 | 56 | module.exports = spam_count; 57 | -------------------------------------------------------------------------------- /jam-spam-app/test/fixtures/mock-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEAli7V49NdZe+XYC1pLaHM0te8kiDmZBJ1u2HJHN8GdbROB6NO 3 | VpC3xK7NxQn6xpvZ9ux20NvcDvGle+DOptZztBH+np6h2jZQ1/kD1yG1eQvVH4th 4 | /9oqHuIjmIfO8lIe4Hyd5Fw5xHkGqVETTGR+0c7kdZIlHmkOregUGtMYZRUi4YG+ 5 | q0w+uFemiHpGKXbeCIAvkq7aIkisEzvPWfSyYdA6WJHpxFk7tD7D8VkzABLVRHCq 6 | AuyqPG39BhGZcGLXx5rGK56kDBJkyTR1t3DkHpwX+JKNG5UYNwOG4LcQj1fteeta 7 | TdkYUMjIyWbanlMYyC+dq7B5fe7el99jXQ1gXwIDAQABAoIBADKfiPOpzKLOtzzx 8 | MbHzB0LO+75aHq7+1faayJrVxqyoYWELuB1P3NIMhknzyjdmU3t7S7WtVqkm5Twz 9 | lBUC1q+NHUHEgRQ4GNokExpSP4SU63sdlaQTmv0cBxmkNarS6ZuMBgDy4XoLvaYX 10 | MSUf/uukDLhg0ehFS3BteVFtdJyllhDdTenF1Nb1rAeN4egt8XLsE5NQDr1szFEG 11 | xH5lb+8EDtzgsGpeIddWR64xP0lDIKSZWst/toYKWiwjaY9uZCfAhvYQ1RsO7L/t 12 | sERmpYgh+rAZUh/Lr98EI8BPSPhzFcSHmtqzzejvC5zrZPHcUimz0CGA3YBiLoJX 13 | V1OrxmECgYEAxkd8gpmVP+LEWB3lqpSvJaXcGkbzcDb9m0OPzHUAJDZtiIIf0UmO 14 | nvL68/mzbCHSj+yFjZeG1rsrAVrOzrfDCuXjAv+JkEtEx0DIevU1u60lGnevOeky 15 | r8Be7pmymFB9/gzQAd5ezIlTv/COgoO986a3h1yfhzrrzbqSiivw308CgYEAwecI 16 | aZZwqH3GifR+0+Z1B48cezA5tC8LZt5yObGzUfxKTWy30d7lxe9N59t0KUVt/QL5 17 | qVkd7mqGzsUMyxUN2U2HVnFTWfUFMhkn/OnCnayhILs8UlCTD2Xxoy1KbQH/9FIr 18 | xf0pbMNJLXeGfyRt/8H+BzSZKBw9opJBWE4gqfECgYBp9FdvvryHuBkt8UQCRJPX 19 | rWsRy6pY47nf11mnazpZH5Cmqspv3zvMapF6AIxFk0leyYiQolFWvAv+HFV5F6+t 20 | Si1mM8GCDwbA5zh6pEBDewHhw+UqMBh63HSeUhmi1RiOwrAA36CO8i+D2Pt+eQHv 21 | ir52IiPJcs4BUNrv5Q1BdwKBgBHgVNw3LGe8QMOTMOYkRwHNZdjNl2RPOgPf2jQL 22 | d/bFBayhq0jD/fcDmvEXQFxVtFAxKAc+2g2S8J67d/R5Gm/AQAvuIrsWZcY6n38n 23 | pfOXaLt1x5fnKcevpFlg4Y2vM4O416RHNLx8PJDehh3Oo/2CSwMrDDuwbtZAGZok 24 | icphAoGBAI74Tisfn+aeCZMrO8KxaWS5r2CD1KVzddEMRKlJvSKTY+dOCtJ+XKj1 25 | OsZdcDvDC5GtgcywHsYeOWHldgDWY1S8Z/PUo4eK9qBXYBXp3JEZQ1dqzFdz+Txi 26 | rBn2WsFLsxV9j2/ugm0PqWVBcU2bPUCwvaRu3SOms2teaLwGCkhr 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /jam-spam-ml/spam_keywords.py: -------------------------------------------------------------------------------- 1 | from multi_rake import Rake; 2 | import re 3 | 4 | 5 | def get_keywords(pr): 6 | text_keywords = '' 7 | 8 | for item in pr: 9 | if type(item) is str: 10 | text_keywords += item + " " 11 | elif type(item) is list: 12 | for item2 in item: 13 | text_keywords += item2 + " " 14 | 15 | return text_keywords 16 | 17 | def get_spam_keywords(spam_features, ham_features): 18 | #POPULATE THE SPAM AND HAM TEXT BLOBS 19 | text_spam = '' 20 | text_ham = '' 21 | 22 | for pr in spam_features: 23 | text_spam += get_keywords(pr) 24 | 25 | for pr in ham_features: 26 | text_ham += get_keywords(pr) 27 | 28 | text_spam = re.sub('[^a-zA-Z0-9 \n\.]', ' ', text_spam) 29 | text_ham = re.sub('[^a-zA-Z0-9 \n\.]', ' ', text_ham) 30 | # print(text_spam,"\n------------------------------------------\n",text_ham) 31 | #INITIALISE RAKE FOR POPULAR WORDS 32 | rake = Rake(max_words=2, min_freq=5) 33 | 34 | #EXTRACT POPULAR KEYWORDS FOR SPAM AND HAM 35 | keywords_spam = rake.apply(text_spam.lower()) 36 | keywords_ham = rake.apply(text_ham.lower()) 37 | 38 | # print(keywords_ham) 39 | # print(keywords_spam) 40 | 41 | spam = [spam_keyword[0] for spam_keyword in keywords_spam[:50]] 42 | ham = [ham_keyword[0] for ham_keyword in keywords_ham[:50]] 43 | 44 | #STORE TOP 30 SPAM AND HAM KEYWORDS 45 | # for i in range(0, 30): 46 | # spam.append(keywords_spam[i][0]) 47 | # ham.append(keywords_ham[i][0]) 48 | 49 | # GENERATE KEYWORDS PRESENT IN SPAM WHICH ARE NOT PRESENT IN HAM 50 | spam_final = [] 51 | for word in spam: 52 | if word not in ham: 53 | spam_final.append(word) 54 | 55 | # print(spam_final) 56 | return spam_final 57 | -------------------------------------------------------------------------------- /jam-spam-app/test/index.test.js: -------------------------------------------------------------------------------- 1 | const nock = require('nock') 2 | // Requiring our app implementation 3 | const myProbotApp = require('..') 4 | const { Probot, ProbotOctokit } = require('probot') 5 | // Requiring our fixtures 6 | const payload = require('./fixtures/issues.opened') 7 | const issueCreatedBody = { body: 'Thanks for opening this issue!' } 8 | const fs = require('fs') 9 | const path = require('path') 10 | 11 | const privateKey = fs.readFileSync(path.join(__dirname, 'fixtures/mock-cert.pem'), 'utf-8') 12 | 13 | describe('My Probot app', () => { 14 | let probot 15 | 16 | beforeEach(() => { 17 | nock.disableNetConnect() 18 | probot = new Probot({ 19 | id: 123, 20 | privateKey, 21 | // disable request throttling and retries for testing 22 | Octokit: ProbotOctokit.defaults({ 23 | retry: { enabled: false }, 24 | throttle: { enabled: false } 25 | }) 26 | }) 27 | // Load our app into probot 28 | probot.load(myProbotApp) 29 | }) 30 | 31 | test('creates a comment when an issue is opened', async () => { 32 | const mock = nock('https://api.github.com') 33 | 34 | // Test that we correctly return a test token 35 | .post('/app/installations/2/access_tokens') 36 | .reply(200, { 37 | token: 'test', 38 | permissions: { 39 | issues: 'write' 40 | } 41 | }) 42 | 43 | // Test that a comment is posted 44 | .post('/repos/hiimbex/testing-things/issues/1/comments', (body) => { 45 | expect(body).toMatchObject(issueCreatedBody) 46 | return true 47 | }) 48 | .reply(200) 49 | 50 | // Receive a webhook event 51 | await probot.receive({ name: 'issues', payload }) 52 | 53 | expect(mock.pendingMocks()).toStrictEqual([]) 54 | }) 55 | 56 | afterEach(() => { 57 | nock.cleanAll() 58 | nock.enableNetConnect() 59 | }) 60 | }) 61 | 62 | // For more information about testing with Jest see: 63 | // https://facebook.github.io/jest/ 64 | 65 | // For more information about testing with Nock see: 66 | // https://github.com/nock/nock 67 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing 2 | 3 | [fork]: /fork 4 | [pr]: /compare 5 | [code-of-conduct]: CODE_OF_CONDUCT.md 6 | 7 | Hi there! We're thrilled that you'd like to contribute to this project. Your help is essential for keeping it great. 8 | 9 | Please note that this project is released with a [Contributor Code of Conduct][code-of-conduct]. By participating in this project you agree to abide by its terms. 10 | 11 | ## Issues and PRs 12 | 13 | If you have suggestions for how this project could be improved, or want to report a bug, open an issue! We'd love all and any contributions. If you have questions, too, we'd love to hear them. 14 | 15 | We'd also love PRs. If you're thinking of a large PR, we advise opening up an issue first to talk about it, though! Look at the links below if you're not sure how to open a PR. 16 | 17 | ## Submitting a pull request 18 | 19 | 1. [Fork][fork] and clone the repository. 20 | 1. Configure and install the dependencies: `npm install`. 21 | 1. Make sure the tests pass on your machine: `npm test`, note: these tests also apply the linter, so there's no need to lint separately. 22 | 1. Create a new branch: `git checkout -b my-branch-name`. 23 | 1. Make your change, add tests, and make sure the tests still pass. 24 | 1. Push to your fork and [submit a pull request][pr]. 25 | 1. Pat your self on the back and wait for your pull request to be reviewed and merged. 26 | 27 | Here are a few things you can do that will increase the likelihood of your pull request being accepted: 28 | 29 | - Write and update tests. 30 | - Keep your changes as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests. 31 | - Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). 32 | 33 | Work in Progress pull requests are also welcome to get feedback early on, or if there is something blocked you. 34 | 35 | ## Resources 36 | 37 | - [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/) 38 | - [Using Pull Requests](https://help.github.com/articles/about-pull-requests/) 39 | - [GitHub Help](https://help.github.com) 40 | -------------------------------------------------------------------------------- /jam-spam-app/model/model.json: -------------------------------------------------------------------------------- 1 | {"format": "layers-model", "generatedBy": "keras v2.4.0", "convertedBy": "TensorFlow.js Converter v2.7.0", "modelTopology": {"keras_version": "2.4.0", "backend": "tensorflow", "model_config": {"class_name": "Sequential", "config": {"name": "sequential", "layers": [{"class_name": "InputLayer", "config": {"batch_input_shape": [null, 5], "dtype": "float32", "sparse": false, "ragged": false, "name": "dense_input"}}, {"class_name": "Dense", "config": {"name": "dense", "trainable": true, "batch_input_shape": [null, 5], "dtype": "float32", "units": 5, "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}, {"class_name": "Dense", "config": {"name": "dense_1", "trainable": true, "dtype": "float32", "units": 16, "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}, {"class_name": "Dense", "config": {"name": "dense_2", "trainable": true, "dtype": "float32", "units": 16, "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}, {"class_name": "Dense", "config": {"name": "dense_3", "trainable": true, "dtype": "float32", "units": 1, "activation": "sigmoid", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}]}}, "training_config": {"loss": "binary_crossentropy", "metrics": ["accuracy"], "weighted_metrics": null, "loss_weights": null, "optimizer_config": {"class_name": "Adam", "config": {"name": "Adam", "learning_rate": 0.0010000000474974513, "decay": 0.0, "beta_1": 0.8999999761581421, "beta_2": 0.9990000128746033, "epsilon": 1e-07, "amsgrad": false}}}}, "weightsManifest": [{"paths": ["group1-shard1of1.bin"], "weights": [{"name": "dense/kernel", "shape": [5, 5], "dtype": "float32"}, {"name": "dense/bias", "shape": [5], "dtype": "float32"}, {"name": "dense_1/kernel", "shape": [5, 16], "dtype": "float32"}, {"name": "dense_1/bias", "shape": [16], "dtype": "float32"}, {"name": "dense_2/kernel", "shape": [16, 16], "dtype": "float32"}, {"name": "dense_2/bias", "shape": [16], "dtype": "float32"}, {"name": "dense_3/kernel", "shape": [16, 1], "dtype": "float32"}, {"name": "dense_3/bias", "shape": [1], "dtype": "float32"}]}]} -------------------------------------------------------------------------------- /jam-spam-ml/model/1604946961/model.json: -------------------------------------------------------------------------------- 1 | {"format": "layers-model", "generatedBy": "keras v2.4.0", "convertedBy": "TensorFlow.js Converter v2.7.0", "modelTopology": {"keras_version": "2.4.0", "backend": "tensorflow", "model_config": {"class_name": "Sequential", "config": {"name": "sequential", "layers": [{"class_name": "InputLayer", "config": {"batch_input_shape": [null, 5], "dtype": "float32", "sparse": false, "ragged": false, "name": "dense_input"}}, {"class_name": "Dense", "config": {"name": "dense", "trainable": true, "batch_input_shape": [null, 5], "dtype": "float32", "units": 5, "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}, {"class_name": "Dense", "config": {"name": "dense_1", "trainable": true, "dtype": "float32", "units": 16, "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}, {"class_name": "Dense", "config": {"name": "dense_2", "trainable": true, "dtype": "float32", "units": 16, "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}, {"class_name": "Dense", "config": {"name": "dense_3", "trainable": true, "dtype": "float32", "units": 1, "activation": "sigmoid", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}]}}, "training_config": {"loss": "binary_crossentropy", "metrics": ["accuracy"], "weighted_metrics": null, "loss_weights": null, "optimizer_config": {"class_name": "Adam", "config": {"name": "Adam", "learning_rate": 0.0010000000474974513, "decay": 0.0, "beta_1": 0.8999999761581421, "beta_2": 0.9990000128746033, "epsilon": 1e-07, "amsgrad": false}}}}, "weightsManifest": [{"paths": ["group1-shard1of1.bin"], "weights": [{"name": "dense/kernel", "shape": [5, 5], "dtype": "float32"}, {"name": "dense/bias", "shape": [5], "dtype": "float32"}, {"name": "dense_1/kernel", "shape": [5, 16], "dtype": "float32"}, {"name": "dense_1/bias", "shape": [16], "dtype": "float32"}, {"name": "dense_2/kernel", "shape": [16, 16], "dtype": "float32"}, {"name": "dense_2/bias", "shape": [16], "dtype": "float32"}, {"name": "dense_3/kernel", "shape": [16, 1], "dtype": "float32"}, {"name": "dense_3/bias", "shape": [1], "dtype": "float32"}]}]} -------------------------------------------------------------------------------- /jam-spam-ml/model/1605194508/model.json: -------------------------------------------------------------------------------- 1 | {"format": "layers-model", "generatedBy": "keras v2.4.0", "convertedBy": "TensorFlow.js Converter v2.7.0", "modelTopology": {"keras_version": "2.4.0", "backend": "tensorflow", "model_config": {"class_name": "Sequential", "config": {"name": "sequential", "layers": [{"class_name": "InputLayer", "config": {"batch_input_shape": [null, 5], "dtype": "float32", "sparse": false, "ragged": false, "name": "dense_input"}}, {"class_name": "Dense", "config": {"name": "dense", "trainable": true, "batch_input_shape": [null, 5], "dtype": "float32", "units": 5, "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}, {"class_name": "Dense", "config": {"name": "dense_1", "trainable": true, "dtype": "float32", "units": 16, "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}, {"class_name": "Dense", "config": {"name": "dense_2", "trainable": true, "dtype": "float32", "units": 16, "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}, {"class_name": "Dense", "config": {"name": "dense_3", "trainable": true, "dtype": "float32", "units": 1, "activation": "sigmoid", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}]}}, "training_config": {"loss": "binary_crossentropy", "metrics": ["accuracy"], "weighted_metrics": null, "loss_weights": null, "optimizer_config": {"class_name": "Adam", "config": {"name": "Adam", "learning_rate": 0.0010000000474974513, "decay": 0.0, "beta_1": 0.8999999761581421, "beta_2": 0.9990000128746033, "epsilon": 1e-07, "amsgrad": false}}}}, "weightsManifest": [{"paths": ["group1-shard1of1.bin"], "weights": [{"name": "dense/kernel", "shape": [5, 5], "dtype": "float32"}, {"name": "dense/bias", "shape": [5], "dtype": "float32"}, {"name": "dense_1/kernel", "shape": [5, 16], "dtype": "float32"}, {"name": "dense_1/bias", "shape": [16], "dtype": "float32"}, {"name": "dense_2/kernel", "shape": [16, 16], "dtype": "float32"}, {"name": "dense_2/bias", "shape": [16], "dtype": "float32"}, {"name": "dense_3/kernel", "shape": [16, 1], "dtype": "float32"}, {"name": "dense_3/bias", "shape": [1], "dtype": "float32"}]}]} -------------------------------------------------------------------------------- /jam-spam-ml/data/ham.csv: -------------------------------------------------------------------------------- 1 | https://github.com/TheAlgorithms/Python/pull/3811 2 | https://github.com/TheAlgorithms/Python/pull/3767 3 | https://github.com/TheAlgorithms/Python/pull/3750 4 | https://github.com/TheAlgorithms/Python/pull/3733 5 | https://github.com/TheAlgorithms/Python/pull/3731 6 | https://github.com/TheAlgorithms/Python/pull/3694 7 | https://github.com/TheAlgorithms/Python/pull/3692 8 | https://github.com/TheAlgorithms/Python/pull/3691 9 | https://github.com/TheAlgorithms/Python/pull/3671 10 | https://github.com/TheAlgorithms/Python/pull/3626 11 | https://github.com/TheAlgorithms/Python/pull/3618 12 | https://github.com/TheAlgorithms/Python/pull/3607 13 | https://github.com/TheAlgorithms/Python/pull/3601 14 | https://github.com/TheAlgorithms/Python/pull/3594 15 | https://github.com/TheAlgorithms/Python/pull/3561 16 | https://github.com/TheAlgorithms/Python/pull/3398 17 | https://github.com/TheAlgorithms/Python/pull/3392 18 | https://github.com/TheAlgorithms/Python/pull/3190 19 | https://github.com/TheAlgorithms/Python/pull/3179 20 | https://github.com/TheAlgorithms/Python/pull/3165 21 | https://github.com/TheAlgorithms/Python/pull/3070 22 | https://github.com/TheAlgorithms/Python/pull/2954 23 | https://github.com/TheAlgorithms/Python/pull/2852 24 | https://github.com/TheAlgorithms/Python/pull/2783 25 | https://github.com/TheAlgorithms/Python/pull/2782 26 | https://github.com/TheAlgorithms/Python/pull/2781 27 | https://github.com/TheAlgorithms/Python/pull/2757 28 | https://github.com/TheAlgorithms/Python/pull/2752 29 | https://github.com/TheAlgorithms/Python/pull/2735 30 | https://github.com/TheAlgorithms/Python/pull/2733 31 | https://github.com/TheAlgorithms/Python/pull/2720 32 | https://github.com/TheAlgorithms/Python/pull/2703 33 | https://github.com/TheAlgorithms/Python/pull/2698 34 | https://github.com/TheAlgorithms/Python/pull/2698 35 | https://github.com/TheAlgorithms/Python/pull/2687 36 | https://github.com/TheAlgorithms/Python/pull/2684 37 | https://github.com/TheAlgorithms/Python/pull/2683 38 | https://github.com/TheAlgorithms/Python/pull/2682 39 | https://github.com/TheAlgorithms/Python/pull/2619 40 | https://github.com/TheAlgorithms/Python/pull/2587 41 | https://github.com/TheAlgorithms/Python/pull/2569 42 | https://github.com/TheAlgorithms/Python/pull/2557 43 | https://github.com/TheAlgorithms/Python/pull/2538 44 | https://github.com/chrislgarry/Apollo-11/pull/803 45 | https://github.com/chrislgarry/Apollo-11/pull/802 46 | https://github.com/chrislgarry/Apollo-11/pull/801 47 | https://github.com/chrislgarry/Apollo-11/pull/800 48 | https://github.com/chrislgarry/Apollo-11/pull/797 49 | https://github.com/chrislgarry/Apollo-11/pull/795 50 | https://github.com/chrislgarry/Apollo-11/pull/794 51 | https://github.com/chrislgarry/Apollo-11/pull/793 52 | https://github.com/meteor/meteor/pull/11105 53 | https://github.com/meteor/meteor/pull/11196 54 | https://github.com/meteor/meteor/pull/11211 55 | https://github.com/meteor/meteor/pull/11213 56 | https://github.com/meteor/meteor/pull/11223 57 | https://github.com/meteor/meteor/pull/11223 58 | https://github.com/meteor/meteor/pull/11225 59 | https://github.com/meteor/meteor/pull/11229 -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | education, socio-economic status, nationality, personal appearance, race, 10 | religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at fellowship@mlh.io. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JamSpam 2 | 3 | > A Machine Learning powered GitHub App built with [Probot](https://github.com/probot/probot) to jam the spam PRs on your repo and keep maintainers stress-free (even in Hacktober 🎃) 4 | 5 | ## Summary 6 | 7 | ### Building Dataset 8 | 9 | - We listed links of PRs labelled as ⚠ `SPAM` or `INVALID` ⚠ on some popular repositories especially those that faced a pool of spam pull-requests during the recently concluded Hacktoberfest 🎃 in a [`.csv` file](jam-spam-ml/data/spam.csv). 10 | - Similarly, we also listed links of ✅ `MERGED` PRs on the repositories in a separate [`.csv` file](jam-spam-ml/data/ham.csv) for Ham (not Spam) features. 11 | - We used [Octokit](https://octokit.github.io/), an API framework by GitHub to extract Pull Request Information from the PR links and save desired features locally to build our model. 12 | 13 | ### Feature Extraction 14 | 15 | We chose the standard PR attributes and some derived features to train our model 16 | 17 | - **Standard** 18 | - *Number of Commits* 19 | - *Number of Files Changed* 20 | - *Number of Changes* `(Additions + Deletions)` 21 | - **Derived** 22 | - *Number of Files Changed of Documentation Type* 23 | ```py 24 | # File Extensions considered to be of Doc-Type 25 | ['md', 'txt', 'rst', ''] 26 | ``` 27 | - *Occurences of spam hit-words in text corpus of PR* 28 | 29 | Text Corpus of a Pull Request includes the PR Title, Body, Commit Messages and Diffs. 30 | 31 | All text is pre-processed with regex to exclude any symbols. 32 | 33 | ### Model Design 34 | 35 | We are using Keras to build our baseline model. It is essentially a (5-16-16-1) Sequential Neural Network with first three layers being 'RELU' activated and the final output layer activated as a sigmoid function. 36 | 37 | The model is run over 500 epochs with a unit batch size. 38 | 39 | ### Transfer Model to Bot 40 | 41 | The model is exported from Python using `tensorflowjs` that creates a `model.json` and a `.bin` file to store the model structure, variables and associated weights. 42 | 43 | The model is imported seamlessly into Node.js using `@tensorflow/tfjs-node` for predictions to be made for incoming PRs 44 | 45 | ## Getting Started 46 | 47 | - For setup instructions to train and export the model, visit [jam-spam-ml/README.md](jam-spam-ml/README.md) 48 | - For setup instructions to build the bot and getting the GitHub App running, head to [jam-spam-app/README.md]([jam-spam-app/README.md]) 49 | 50 | ## Contributing 51 | 52 | If you have suggestions for how JamSpam could be improved, or want to report a bug, open an issue! We'd love all and any contributions. 53 | 54 | For more, check out the [Contributing Guide](CONTRIBUTING.md). 55 | 56 | ## Screenshots 57 | 58 | 1. If you are a Collaborator, Contributor, Member, or Owner of the repository your pull request will never be flagged. 59 | ![Ham PR](/Screenshots/ham.png) 60 | 61 | 62 | 2. If you are a First Timer, Mannequin or First Time Contributor your pull requests will be checked. 63 | 64 | 65 | If the pull request is legit, it is not flagged 66 | ![Ham PR](/Screenshots/ham2.png) 67 | 68 | 69 | If the pull request is suspected to be spam, it is marked as spam and closed. 70 | ![Spam PR](/Screenshots/spam.png) 71 | 72 | ## License 73 | 74 | [MIT](LICENSE) © 2020 MLH Fellowship 75 | 76 | Made with :heart: by [Ajwad Shaikh](https://github.com/ajwad-shaikh) & [Vrushti Mody](https://github.com/vrushti-mody) during Sprint 3 of the MLH Fellowship Explorer Batch, Fall 2020. 77 | -------------------------------------------------------------------------------- /jam-spam-app/index.js: -------------------------------------------------------------------------------- 1 | const { loadLayersModel, tensor } = require('@tensorflow/tfjs-node') 2 | tf = require('@tensorflow/tfjs-node') 3 | const spam_count = require('./checkspamcount') 4 | const fetchText = require('./fetchtext') 5 | const docschanged = require ('./docschanged') 6 | /** 7 | * This is the main entrypoint to your Probot app 8 | * @param {import('probot').Application} app 9 | */ 10 | 11 | 12 | module.exports = app => { 13 | // Your code here 14 | app.log.info('Yay, the app was loaded!') 15 | 16 | var model = null; 17 | var SPAM_THRESHOLD = 0.90; 18 | 19 | async function getStarted() { 20 | model = await loadLayersModel(`file://${process.cwd()}/model/model.json`); 21 | } 22 | 23 | async function close (context, params) { 24 | const closeParams = Object.assign({}, params, {state: 'closed'}) 25 | return context.github.issues.update(closeParams) 26 | } 27 | 28 | async function predict (inputTensor) { 29 | var res = await model.predict(inputTensor) 30 | var spam_prob = res.dataSync()[0] 31 | return spam_prob > SPAM_THRESHOLD 32 | } 33 | 34 | getStarted(); 35 | 36 | app.on('issues.opened', async context => { 37 | const issueComment = context.issue({ body: 'Thanks for opening this issue!' }) 38 | return context.github.issues.createComment(issueComment) 39 | }) 40 | 41 | app.on('pull_request.opened', async context => { 42 | // app.log.info(context); 43 | var pull_request = context.payload.pull_request; 44 | try { 45 | const files_changed = parseInt(pull_request.changed_files); 46 | const commits = parseInt(pull_request.commits); 47 | const title1 = pull_request.title.toString(); 48 | const body1 = pull_request.body.toString(); 49 | const changes = parseInt(pull_request.additions) + parseInt(pull_request.deletions); 50 | const {spam_counted, docschange} = await fetchText(pull_request.diff_url).then(diff => { 51 | const textblob = title1 + body1 + diff 52 | const spam_counted = spam_count(textblob.toLowerCase()) 53 | const docschange = docschanged(diff) 54 | return {spam_counted, docschange} 55 | }); 56 | var inputTensor = tensor([[files_changed, docschange, commits, changes, spam_counted]]); 57 | // app.log.info(inputTensor); 58 | } 59 | catch(e){ 60 | app.log.error(`Error: ${e}`); 61 | } 62 | var isSpam = await predict(inputTensor); 63 | if (["COLLABORATOR", "CONTRIBUTOR", "MEMBER", "OWNER"].includes(pull_request.author_association) || !isSpam){ 64 | // skip spam check - verified userconst files_changed = pull_request.changed_files; 65 | const prComment = context.issue({ 66 | body: `Thanks for your Pull Request @${pull_request.user.login} 🙌 This seems like a legit 💯 contribution` 67 | }); 68 | return context.github.issues.createComment(prComment); 69 | } 70 | else if (["FIRST_TIMER", "MANNEQUIN", "FIRST_TIME_CONTRIBUTOR", "NONE", ""].includes(pull_request.author_association) && isSpam){ 71 | // possibly spam - check for spam in classifier 72 | const prComment = context.issue({ 73 | body: `Hmmm, something is fishy 🐠 here! We think this Pull Request does not meet the standards. 74 | Kindly refer to the Contributing Guidelines of the project. We look forward to your future contributions!` 75 | }); 76 | await context.github.issues.createComment(prComment) 77 | return close(context, context.issue()) 78 | } 79 | }) 80 | 81 | // For more information on building apps: 82 | // https://probot.github.io/docs/ 83 | 84 | // To get your app running against GitHub, see: 85 | // https://probot.github.io/docs/development/ 86 | } 87 | -------------------------------------------------------------------------------- /jam-spam-app/app.yml: -------------------------------------------------------------------------------- 1 | # This is a GitHub App Manifest. These settings will be used by default when 2 | # initially configuring your GitHub App. 3 | # 4 | # NOTE: changing this file will not update your GitHub App settings. 5 | # You must visit github.com/settings/apps/your-app-name to edit them. 6 | # 7 | # Read more about configuring your GitHub App: 8 | # https://probot.github.io/docs/development/#configuring-a-github-app 9 | # 10 | # Read more about GitHub App Manifests: 11 | # https://developer.github.com/apps/building-github-apps/creating-github-apps-from-a-manifest/ 12 | 13 | # The list of events the GitHub App subscribes to. 14 | # Uncomment the event names below to enable them. 15 | default_events: 16 | # - check_run 17 | # - check_suite 18 | # - commit_comment 19 | # - create 20 | # - delete 21 | # - deployment 22 | # - deployment_status 23 | # - fork 24 | # - gollum 25 | # - issue_comment 26 | - issues 27 | # - label 28 | # - milestone 29 | # - member 30 | # - membership 31 | # - org_block 32 | # - organization 33 | # - page_build 34 | # - project 35 | # - project_card 36 | # - project_column 37 | # - public 38 | # - pull_request 39 | # - pull_request_review 40 | # - pull_request_review_comment 41 | # - push 42 | # - release 43 | # - repository 44 | # - repository_import 45 | # - status 46 | # - team 47 | # - team_add 48 | # - watch 49 | 50 | # The set of permissions needed by the GitHub App. The format of the object uses 51 | # the permission name for the key (for example, issues) and the access type for 52 | # the value (for example, write). 53 | # Valid values are `read`, `write`, and `none` 54 | default_permissions: 55 | # Repository creation, deletion, settings, teams, and collaborators. 56 | # https://developer.github.com/v3/apps/permissions/#permission-on-administration 57 | # administration: read 58 | 59 | # Checks on code. 60 | # https://developer.github.com/v3/apps/permissions/#permission-on-checks 61 | # checks: read 62 | 63 | # Repository contents, commits, branches, downloads, releases, and merges. 64 | # https://developer.github.com/v3/apps/permissions/#permission-on-contents 65 | # contents: read 66 | 67 | # Deployments and deployment statuses. 68 | # https://developer.github.com/v3/apps/permissions/#permission-on-deployments 69 | # deployments: read 70 | 71 | # Issues and related comments, assignees, labels, and milestones. 72 | # https://developer.github.com/v3/apps/permissions/#permission-on-issues 73 | issues: write 74 | 75 | # Search repositories, list collaborators, and access repository metadata. 76 | # https://developer.github.com/v3/apps/permissions/#metadata-permissions 77 | metadata: read 78 | 79 | # Retrieve Pages statuses, configuration, and builds, as well as create new builds. 80 | # https://developer.github.com/v3/apps/permissions/#permission-on-pages 81 | # pages: read 82 | 83 | # Pull requests and related comments, assignees, labels, milestones, and merges. 84 | # https://developer.github.com/v3/apps/permissions/#permission-on-pull-requests 85 | # pull_requests: read 86 | 87 | # Manage the post-receive hooks for a repository. 88 | # https://developer.github.com/v3/apps/permissions/#permission-on-repository-hooks 89 | # repository_hooks: read 90 | 91 | # Manage repository projects, columns, and cards. 92 | # https://developer.github.com/v3/apps/permissions/#permission-on-repository-projects 93 | # repository_projects: read 94 | 95 | # Retrieve security vulnerability alerts. 96 | # https://developer.github.com/v4/object/repositoryvulnerabilityalert/ 97 | # vulnerability_alerts: read 98 | 99 | # Commit statuses. 100 | # https://developer.github.com/v3/apps/permissions/#permission-on-statuses 101 | # statuses: read 102 | 103 | # Organization members and teams. 104 | # https://developer.github.com/v3/apps/permissions/#permission-on-members 105 | # members: read 106 | 107 | # View and manage users blocked by the organization. 108 | # https://developer.github.com/v3/apps/permissions/#permission-on-organization-user-blocking 109 | # organization_user_blocking: read 110 | 111 | # Manage organization projects, columns, and cards. 112 | # https://developer.github.com/v3/apps/permissions/#permission-on-organization-projects 113 | # organization_projects: read 114 | 115 | # Manage team discussions and related comments. 116 | # https://developer.github.com/v3/apps/permissions/#permission-on-team-discussions 117 | # team_discussions: read 118 | 119 | # Manage the post-receive hooks for an organization. 120 | # https://developer.github.com/v3/apps/permissions/#permission-on-organization-hooks 121 | # organization_hooks: read 122 | 123 | # Get notified of, and update, content references. 124 | # https://developer.github.com/v3/apps/permissions/ 125 | # organization_administration: read 126 | 127 | 128 | # The name of the GitHub App. Defaults to the name specified in package.json 129 | # name: My Probot App 130 | 131 | # The homepage of your GitHub App. 132 | # url: https://example.com/ 133 | 134 | # A description of the GitHub App. 135 | # description: A description of my awesome app 136 | 137 | # Set to true when your GitHub App is available to the public or false when it is only accessible to the owner of the app. 138 | # Default: true 139 | # public: false 140 | -------------------------------------------------------------------------------- /jam-spam-ml/utils.py: -------------------------------------------------------------------------------- 1 | from octokit import Octokit 2 | import csv, requests, os, sys 3 | from dotenv import load_dotenv 4 | 5 | load_dotenv() 6 | 7 | 8 | def fetch_data_from_github(pull_request: str) -> dict: 9 | """Takes a pull-request link and returns a feature array for it 10 | 11 | Parameters 12 | ---------- 13 | pull_request : str 14 | The link to the pull request on GitHub 15 | 16 | Returns 17 | ------- 18 | dict 19 | The feature dict contains parameters fetched from GitHub required to build training model 20 | """ 21 | 22 | pr_attrs = pull_request.split('/') 23 | # ['https:', '', 'github.com', 'owner', 'repo', 'pull', 'pull_number'] 24 | octokit = Octokit(auth='token', token=os.getenv("TOKEN")) 25 | # octokit = Octokit() 26 | pr_data = octokit.pulls.get(owner=pr_attrs[3], 27 | repo=pr_attrs[4], 28 | pull_number=int(pr_attrs[6])) 29 | commits = octokit.pulls.list_commits(owner=pr_attrs[3], 30 | repo=pr_attrs[4], 31 | pull_number=int(pr_attrs[6])) 32 | try: 33 | commit_messages = '' 34 | number_of_commits = pr_data.json["commits"] 35 | number_of_changes = pr_data.json["additions"] + pr_data.json["deletions"] 36 | number_of_files_changed = pr_data.json["changed_files"] 37 | for commit_object in commits.json: 38 | commit_messages += f'{commit_object["commit"]["message"]} ' 39 | diffs = requests.get(pr_data.json["diff_url"]).text 40 | number_of_docs_changed = get_docs_changed(diffs) 41 | 42 | return { 43 | "url": pull_request, 44 | "title": pr_data.json["title"], 45 | "body": pr_data.json["body"], 46 | "diffs": diffs, 47 | "commit_messages": commit_messages, 48 | "files_changed": number_of_files_changed, 49 | "docs_changed": number_of_docs_changed, 50 | "commits": number_of_commits, 51 | "changes": number_of_changes 52 | } 53 | except: 54 | print(f"Error in {pull_request}: ", pr_data.json) 55 | 56 | def get_docs_changed(diffs: str) -> int: 57 | """Processes diffs from a Pull Request to extract number of doc-type files changed in the PR 58 | 59 | Parameters 60 | ---------- 61 | diffs : str 62 | A string denoting the diffs in the PR 63 | 64 | Returns 65 | ------- 66 | int 67 | number of files of documentation type that have been changed in the PR 68 | 69 | """ 70 | 71 | # Documentation-type file extensions 72 | exts = ['md', 'txt', 'rst', ''] 73 | diff_set = [ 74 | line for line in diffs.split('\n') 75 | if line.startswith('diff') and line.split('.')[-1] in exts 76 | ] 77 | return len(diff_set) 78 | 79 | 80 | def read_csv(file_path: str) -> list: 81 | """Takes a filepath returns a data with list of PR links from CSV data file 82 | 83 | Parameters 84 | ---------- 85 | file_path : str 86 | The path to the CSV file on your system that contains the list of PR links 87 | 88 | Returns 89 | ------- 90 | list 91 | List of strings of PR Links 92 | """ 93 | 94 | with open(file=file_path) as f: 95 | reader = csv.reader(f) 96 | data = [row[0] for row in reader] 97 | return data 98 | 99 | def import_local_dataset() -> (list, list): 100 | 101 | spam_data = ham_data = [] 102 | 103 | maxInt = sys.maxsize 104 | 105 | while True: 106 | # decrease the maxInt value by factor 10 107 | # as long as the OverflowError occurs. 108 | try: 109 | csv.field_size_limit(maxInt) 110 | break 111 | except OverflowError: 112 | maxInt = int(maxInt/10) 113 | 114 | with open("data/spam_fetch.csv", "r", newline='', encoding='utf8') as file: 115 | read = csv.reader(file) 116 | spam_data = [row for row in read] 117 | 118 | with open("data/ham_fetch.csv", "r", newline='', encoding='utf8') as file: 119 | read = csv.reader(file) 120 | ham_data = [row for row in read] 121 | 122 | return spam_data, ham_data 123 | 124 | 125 | def fetch_from_remote(updateLocal: bool) -> (list, list): 126 | 127 | # import URLs list 128 | SPAM_PRS = read_csv("data/spam.csv") 129 | HAM_PRS = read_csv("data/ham.csv") 130 | 131 | spam_feature_array = [ 132 | fetch_data_from_github(pr_link) for pr_link in SPAM_PRS 133 | ] 134 | ham_feature_array = [ 135 | fetch_data_from_github(pr_link) for pr_link in HAM_PRS 136 | ] 137 | 138 | spam_list_features = [[ 139 | pr_feature["url"], pr_feature["title"], pr_feature["body"], pr_feature["diffs"], 140 | pr_feature["commit_messages"], pr_feature["files_changed"], pr_feature["docs_changed"], 141 | pr_feature["commits"], pr_feature["changes"] 142 | ] for pr_feature in spam_feature_array] 143 | 144 | ham_list_features = [[ 145 | pr_feature["url"], pr_feature["title"], pr_feature["body"], pr_feature["diffs"], 146 | pr_feature["commit_messages"], pr_feature["files_changed"], pr_feature["docs_changed"], 147 | pr_feature["commits"], pr_feature["changes"] 148 | ] for pr_feature in ham_feature_array] 149 | 150 | if updateLocal is True: 151 | with open("data/spam_fetch.csv", "w", newline='', encoding='utf8') as file: 152 | writer = csv.writer(file) 153 | writer.writerows(spam_list_features) 154 | 155 | with open("data/ham_fetch.csv", "w", newline='', encoding='utf8') as file: 156 | writer = csv.writer(file) 157 | writer.writerows(ham_list_features) 158 | 159 | return spam_list_features, ham_list_features 160 | -------------------------------------------------------------------------------- /jam-spam-ml/train.py: -------------------------------------------------------------------------------- 1 | from __future__ import division 2 | from utils import read_csv, fetch_data_from_github, import_local_dataset, fetch_from_remote 3 | from spam_keywords import get_keywords, get_spam_keywords 4 | 5 | import tensorflow as tf 6 | from tensorflow import keras 7 | import tensorflowjs as tfjs 8 | import numpy as np 9 | import tarfile 10 | import os, time, re, random 11 | 12 | def print_array(ys): 13 | for xs in ys: 14 | print("[") 15 | print(", ".join(map(str, xs))) 16 | print("]") 17 | 18 | def count_freq(pat, txt): 19 | M = len(pat) 20 | N = len(txt) 21 | res = 0 22 | 23 | # A loop to slide pat[] one by one 24 | for i in range(N - M + 1): 25 | 26 | # For current index i, check 27 | # for pattern match 28 | j = 0 29 | while j < M: 30 | if (txt[i + j] != pat[j]): 31 | break 32 | j += 1 33 | 34 | if (j == M): 35 | res += 1 36 | j = 0 37 | return res 38 | 39 | def main(): 40 | 41 | ################### 42 | ### IMPORT DATA ### 43 | ################### 44 | 45 | def import_data(): 46 | spam_data, ham_data = import_local_dataset() 47 | # csv_row -> [url, title, body, diffs, commit_messages, files_changed, docs_changed, commits, changes] 48 | 49 | spam_text_corpus = [ 50 | [row[1], row[2], row[4]] # [title, body, commit_messages] 51 | for row in spam_data 52 | ] 53 | ham_text_corpus = [ 54 | [row[1], row[2], row[4]] # [title, body, commit_messages] 55 | for row in ham_data 56 | ] 57 | 58 | ## TO FETCH FROM REMOTE UNCOMMENT THE BLOCK BELOW 59 | # 60 | # spam_data, ham_data = fetch_from_remote(updateLocal=True) 61 | # spam_text_corpus = [[ 62 | # pr_feature["title"], pr_feature["body"], pr_feature["commit_messages"] 63 | # ] for pr_feature in spam_data if type(pr_feature) is dict] 64 | # ham_text_corpus = [[ 65 | # pr_feature["title"], pr_feature["body"], pr_feature["commit_messages"] 66 | # ] for pr_feature in ham_data if type(pr_feature) is dict] 67 | 68 | spam_keywords = get_spam_keywords(spam_text_corpus, ham_text_corpus) 69 | 70 | print(spam_keywords) 71 | 72 | spam_feature_array = [] 73 | ham_feature_array = [] 74 | 75 | spam_feature_array.extend([[ 76 | # pr_feature["url"], 77 | int(pr_feature[5]), int(pr_feature[6]), int(pr_feature[7]), int(pr_feature[8])] 78 | # [files_changed, docs_changed, commits, changes] 79 | for pr_feature in spam_data]) 80 | ham_feature_array.extend([[ 81 | # pr_feature["url"], 82 | int(pr_feature[5]), int(pr_feature[6]), int(pr_feature[7]), int(pr_feature[8])] 83 | # [files_changed, docs_changed, commits, changes] 84 | for pr_feature in ham_data]) 85 | 86 | for i in range(len(spam_text_corpus)): 87 | num_spam_keywords = 0 88 | text = re.sub('[^a-zA-Z0-9 \n\.]', ' ', get_keywords(spam_text_corpus[i]).lower()) 89 | for keyword in spam_keywords: 90 | num_spam_keywords += count_freq(keyword, text) 91 | spam_feature_array[i].append(num_spam_keywords) 92 | 93 | for i in range(len(ham_text_corpus)): 94 | num_spam_keywords = 0 95 | text = re.sub('[^a-zA-Z0-9 \n\.]', ' ', get_keywords(ham_text_corpus[i]).lower()) 96 | for keyword in spam_keywords: 97 | num_spam_keywords += count_freq(keyword, text) 98 | ham_feature_array[i].append(num_spam_keywords) 99 | 100 | random.shuffle(spam_feature_array) 101 | random.shuffle(ham_feature_array) 102 | 103 | TRAIN_PARTITION = 80 104 | TRAIN_SIZE_SPAM = int(len(spam_feature_array) * (TRAIN_PARTITION / 100)) 105 | TEST_SIZE_SPAM = len(spam_feature_array) - TRAIN_SIZE_SPAM 106 | TRAIN_SIZE_HAM = int(len(ham_feature_array) * (TRAIN_PARTITION / 100)) 107 | TEST_SIZE_HAM = len(ham_feature_array) - TRAIN_SIZE_HAM 108 | 109 | 110 | features_array = spam_feature_array[:TRAIN_SIZE_SPAM] + ham_feature_array[:TRAIN_SIZE_HAM] 111 | testing_array = spam_feature_array[-TEST_SIZE_SPAM:] + ham_feature_array[-TEST_SIZE_HAM:] 112 | 113 | labels_array_train = [] 114 | for spam_pr in spam_feature_array[:TRAIN_SIZE_SPAM]: 115 | labels_array_train.append([1]) 116 | 117 | for ham_pr in ham_feature_array[:TRAIN_SIZE_HAM]: 118 | labels_array_train.append([0]) 119 | 120 | labels_array_test = [] 121 | for spam_pr in spam_feature_array[-TEST_SIZE_SPAM:]: 122 | labels_array_test.append([1]) 123 | 124 | for ham_pr in ham_feature_array[-TEST_SIZE_HAM:]: 125 | labels_array_test.append([0]) 126 | 127 | # print("loading training data") 128 | trainX = np.array(features_array) 129 | trainY = np.array(labels_array_train) 130 | # print(trainX) 131 | # print(trainY) 132 | 133 | # print("loading test data") 134 | testX = np.array(testing_array) 135 | testY = np.array(labels_array_test) 136 | return trainX,trainY,testX,testY 137 | 138 | 139 | trainX,trainY,testX,testY = import_data() 140 | feature_count = trainX.shape[1] 141 | label_count = trainY.shape[1] 142 | 143 | 144 | model = keras.Sequential([ 145 | # keras.layers.Flatten(input_shape=(5,)), 146 | keras.layers.Dense(5, activation=tf.nn.relu, input_shape=(5,)), 147 | keras.layers.Dense(16, activation=tf.nn.relu), 148 | keras.layers.Dense(16, activation=tf.nn.relu), 149 | keras.layers.Dense(1, activation=tf.nn.sigmoid), 150 | ]) 151 | 152 | model.compile(optimizer='adam', 153 | loss='binary_crossentropy', 154 | metrics=['accuracy']) 155 | 156 | model.fit(trainX, trainY, epochs=500, batch_size=1) 157 | 158 | test_loss, test_acc = model.evaluate(testX, testY) 159 | print('Test accuracy:', test_acc) 160 | 161 | tfjs.converters.save_keras_model(model, f'model/{int(time.time())}') 162 | 163 | if __name__ == "__main__": 164 | main() 165 | -------------------------------------------------------------------------------- /jam-spam-ml/data/spam.csv: -------------------------------------------------------------------------------- 1 | https://github.com/pallets/flask/pull/2955 2 | https://github.com/pallets/flask/pull/3390 3 | https://github.com/pallets/flask/pull/3407 4 | https://github.com/pallets/flask/pull/3772 5 | https://github.com/pallets/flask/pull/3782 6 | https://github.com/pallets/flask/pull/3784 7 | https://github.com/pallets/flask/pull/3792 8 | https://github.com/pallets/flask/pull/3795 9 | https://github.com/pallets/flask/pull/3797 10 | https://github.com/pallets/flask/pull/3799 11 | https://github.com/gohugoio/hugo/pull/7762 12 | https://github.com/gohugoio/hugo/pull/7770 13 | https://github.com/gohugoio/hugo/pull/7821 14 | https://github.com/gohugoio/hugo/pull/7843 15 | https://github.com/gohugoio/hugo/pull/7862 16 | https://github.com/ansible/ansible/pull/72047 17 | https://github.com/ansible/ansible/pull/72048 18 | https://github.com/ansible/ansible/pull/72188 19 | https://github.com/scrapy/scrapy/pull/4832 20 | https://github.com/scrapy/scrapy/pull/4837 21 | https://github.com/scrapy/scrapy/pull/4838 22 | https://github.com/scrapy/scrapy/pull/4862 23 | https://github.com/scrapy/scrapy/pull/4863 24 | https://github.com/ohmyzsh/ohmyzsh/pull/8294 25 | https://github.com/ohmyzsh/ohmyzsh/pull/9352 26 | https://github.com/ohmyzsh/ohmyzsh/pull/9358 27 | https://github.com/ohmyzsh/ohmyzsh/pull/9361 28 | https://github.com/TheAlgorithms/Python/pull/3779 29 | https://github.com/TheAlgorithms/Python/pull/3463 30 | https://github.com/TheAlgorithms/Python/pull/3457 31 | https://github.com/TheAlgorithms/Python/pull/3428 32 | https://github.com/TheAlgorithms/Python/pull/3400 33 | https://github.com/TheAlgorithms/Python/pull/3391 34 | https://github.com/TheAlgorithms/Python/pull/3415 35 | https://github.com/TheAlgorithms/Python/pull/3311 36 | https://github.com/TheAlgorithms/Python/pull/3236 37 | https://github.com/TheAlgorithms/Python/pull/3160 38 | https://github.com/TheAlgorithms/Python/pull/3061 39 | https://github.com/TheAlgorithms/Python/pull/3037 40 | https://github.com/TheAlgorithms/Python/pull/2872 41 | https://github.com/neovim/neovim/pull/13015 42 | https://github.com/neovim/neovim/pull/11162 43 | https://github.com/neovim/neovim/pull/13127 44 | https://github.com/neovim/neovim/pull/13159 45 | https://github.com/scrapy/scrapy/pull/4815 46 | https://github.com/scrapy/scrapy/pull/4819 47 | https://github.com/sdmg15/Best-websites-a-programmer-should-visit/pull/666 48 | https://github.com/sdmg15/Best-websites-a-programmer-should-visit/pull/665 49 | https://github.com/sdmg15/Best-websites-a-programmer-should-visit/pull/663 50 | https://github.com/sdmg15/Best-websites-a-programmer-should-visit/pull/575 51 | https://github.com/appsmithorg/appsmith/pull/1658 52 | https://github.com/rocketseat-education/nlw-02-discovery/pull/5 53 | https://github.com/zinefer/zinefer.github.com/pull/339 54 | https://github.com/thedevdojo/tails/pull/280 55 | https://github.com/lakshya125/py_beginners/pull/126 56 | https://github.com/lakshya125/py_beginners/pull/123 57 | https://github.com/zeek/zeek/pull/1263 58 | https://github.com/lakshya125/py_beginners/pull/120 59 | https://github.com/isnur/is-web-component/pull/84 60 | https://github.com/appsmithorg/appsmith/pull/1486 61 | https://github.com/developer-portal/website/pull/111 62 | https://github.com/nnstreamer/nntrainer/pull/688 63 | https://github.com/mattjegan/awesome-hacktoberfest/pull/99 64 | https://github.com/womenwhogocwb/hacktoberfest2020/pull/14 65 | https://github.com/HarshCasper/Rotten-Scripts/pull/518 66 | https://github.com/chartjs/Chart.js/pull/7982 67 | https://github.com/sbt/website/pull/947/ 68 | https://github.com/Project-Navy/NavyOS/pull/10 69 | https://github.com/appsmithorg/appsmith/pull/1472 70 | https://github.com/womenwhogocwb/hacktoberfest2020/pull/13 71 | https://github.com/appsmithorg/appsmith/pull/893 72 | https://github.com/appsmithorg/appsmith/pull/915 73 | https://github.com/appsmithorg/appsmith/pull/916 74 | https://github.com/appsmithorg/appsmith/pull/920 75 | https://github.com/appsmithorg/appsmith/pull/921 76 | https://github.com/appsmithorg/appsmith/pull/1151 77 | https://github.com/appsmithorg/appsmith/pull/1229 78 | https://github.com/appsmithorg/appsmith/pull/1252 79 | https://github.com/appsmithorg/appsmith/pull/1452 80 | https://github.com/appsmithorg/appsmith/pull/1658 81 | https://github.com/appsmithorg/appsmith/pull/1486 82 | https://github.com/DSC-COEA-Ambajogai/Hacktoberfest2020/pull/370 83 | https://github.com/DSC-COEA-Ambajogai/Hacktoberfest2020/pull/368 84 | https://github.com/sahat/hackathon-starter/pull/1113 85 | https://github.com/sahat/hackathon-starter/pull/1092 86 | https://github.com/sahat/hackathon-starter/pull/1089 87 | https://github.com/sahat/hackathon-starter/pull/1087 88 | https://github.com/exercism/python/pull/2270 89 | https://github.com/exercism/python/pull/2092 90 | https://github.com/exercism/python/pull/2094 91 | https://github.com/exercism/python/pull/2093 92 | https://github.com/exercism/python/pull/2063 93 | https://github.com/rubyforgood/casa/pull/1204 94 | https://github.com/rubyforgood/casa/pull/1174 95 | https://github.com/EndlessHosting/website/pull/3 96 | https://github.com/krismyu/website/pull/32 97 | https://github.com/dagurval/doublespend.cash/pull/15 98 | https://github.com/cimplec/sim-c/pull/151 99 | https://github.com/istio/istio.io/pull/8258 100 | https://github.com/vanruby/vanruby.github.io/pull/73 101 | https://github.com/redhat-openstack/website/pull/1411 102 | https://github.com/Openscapes/website/pull/11 103 | https://github.com/torchbox/django-libsass/pull/44 104 | https://github.com/Chinmay-KB/project-kopie/pull/14 105 | https://github.com/thomersch/Mikrowelle-OS/pull/24 106 | https://github.com/Chinmay-KB/project_fireflies/pull/50 107 | https://github.com/wesbos/beginner-javascript/pull/71 108 | https://github.com/Chinmay-KB/project_fireflies/pull/48 109 | https://github.com/gatsbyjs/gatsby-fr/pull/62 110 | https://github.com/Chinmay-KB/project_fireflies/pull/46 111 | https://github.com/Chinmay-KB/project_fireflies/pull/43 112 | https://github.com/redhat-openstack/website/pull/1411 113 | https://github.com/AliasIO/wappalyzer/pull/3409 114 | https://github.com/OWASP/www-project-juice-shop/pull/3 115 | https://github.com/vanruby/vanruby.github.io/pull/73 116 | https://github.com/istio/istio.io/pull/8258 117 | https://github.com/vJechsmayr/PythonAlgorithms/pull/200 118 | https://github.com/cimplec/sim-c/pull/151 119 | https://github.com/dagurval/doublespend.cash/pull/15 120 | https://github.com/krismyu/website/pull/32 121 | https://github.com/JustSteveKing/companies-house-laravel/pull/1 122 | https://github.com/kaustubhgupta/Hacktoberfest2020_/pull/126 123 | https://github.com/verekia/initializr-website/pull/23 124 | https://github.com/llvm/llvm-project/pull/256 125 | https://github.com/Openscapes/website/pull/12 126 | https://github.com/TodoBackend/todo-backend-site/pull/233 127 | https://github.com/OffPe/react-covid19-stats/pull/20 128 | https://github.com/Chinmay-KB/project_fireflies/pull/51 129 | https://github.com/owntracks/android/pull/863 130 | https://github.com/TheAlgorithms/Javascript/pull/376 131 | https://github.com/kaustubhgupta/Hacktoberfest2020_/pull/128 132 | https://github.com/shower/shower/pull/362 133 | https://github.com/microsoft/sql-server-samples/pull/825 134 | https://github.com/meedan/pender/pull/227 135 | https://github.com/RichardLitt/awesome-fantasy/pull/135 136 | https://github.com/kaustubhgupta/Hacktoberfest2020_/pull/129 137 | https://github.com/ScreamingTaco/EGESPLOIT/pull/2 138 | https://github.com/mjhea0/thinkful-html/pull/12 139 | https://github.com/whatwg/html/pull/6014 140 | https://github.com/bitcraze/bitcraze-website/pull/75 141 | https://github.com/techqueria/website/pull/771 142 | https://github.com/jcubic/try-python/pull/4 143 | https://github.com/krismyu/website/pull/33 144 | https://github.com/gargarchit/Data_Structure_n_Algorithms/pull/27 145 | https://github.com/kaustubhgupta/Hacktoberfest2020_/pull/132 146 | https://github.com/kaustubhgupta/Hacktoberfest2020_/pull/131 147 | https://github.com/kaustubhgupta/Hacktoberfest2020_/pull/130 148 | https://github.com/mihir3k/mihir.work/pull/1 149 | https://github.com/papercups-io/papercups/pull/270 150 | https://github.com/Iltwats/Data-Structures-and-Algorithms/pull/92 151 | https://github.com/whatwg/html/pull/6015 152 | https://github.com/erxes/erxes/pull/2362 153 | https://github.com/vJechsmayr/PythonAlgorithms/pull/209 154 | https://github.com/appsmithorg/appsmith/pull/893 155 | https://github.com/scikit-learn/scikit-learn/pull/18526 156 | https://github.com/vJechsmayr/PythonAlgorithms/pull/208 157 | https://github.com/fccManila/fccmanila.github.io/pull/47 158 | https://github.com/boostorg/website/pull/556 159 | https://github.com/ChaitanyaGadodia/chaitanyagadodia.github.io.old/pull/1 160 | https://github.com/JamOORev/Website/pull/64 161 | https://github.com/papercups-io/papercups/pull/271 162 | https://github.com/grpc/grpc/pull/24314 163 | https://github.com/fission/fission/pull/1804 164 | https://github.com/electricg/jQuery-Html5-Crossword/pull/6 165 | https://github.com/whatwg/html/pull/6016 166 | https://github.com/UoGSOCIS/uogsocis.github.io/pull/116 167 | https://github.com/kiitfossosh/covid-tracker-android/pull/7 168 | https://github.com/kiitfossosh/Official_App_android/pull/5 169 | https://github.com/kaustubhgupta/Hacktoberfest2020_/pull/134 170 | https://github.com/llvm/llvm-project/pull/257 171 | https://github.com/AlexKvazos/VanillaToasts/pull/37 172 | https://github.com/danmar/cppcheck/pull/2833 173 | https://github.com/kaustubhgupta/Hacktoberfest2020_/pull/137 174 | https://github.com/danmar/cppcheck/pull/2832 175 | https://github.com/grpc/grpc/pull/24316 176 | https://github.com/grpc/grpc/pull/24315 177 | https://github.com/CaffeinatedDave/my-first-website/pull/7 178 | https://github.com/tokio-rs/website/pull/502 179 | https://github.com/whatwg/html/pull/6017 180 | https://github.com/kaustubhgupta/Hacktoberfest2020_/pull/138 181 | https://github.com/Python-World/python-mini-projects/pull/317 182 | https://github.com/kaustubhgupta/Hacktoberfest2020_/pull/139 183 | https://github.com/enaqx/awesome-pentest/pull/393 184 | https://github.com/kaustubhgupta/Hacktoberfest2020_/pull/139 185 | https://github.com/enaqx/awesome-pentest/pull/393 186 | https://github.com/fccManila/fccmanila.github.io/pull/48 187 | https://github.com/jakevdp/PythonDataScienceHandbook/pull/272 188 | https://github.com/kaustubhgupta/Hacktoberfest2020_/pull/140 189 | https://github.com/grpc/grpc/pull/24317 190 | https://github.com/KenzieAcademy/react-basics-sports-game/pull/1 191 | https://github.com/shoumikdey/brute-force/pull/21 192 | https://github.com/nlohmann/json/pull/2415 193 | https://github.com/apache/shiro-site/pull/68 194 | https://github.com/prateekiiest/Code-Sleep-Python/pull/191 195 | https://github.com/wir-coders/Hacktoberfest/pull/155 196 | https://github.com/grml/grml.org/pull/33 197 | https://github.com/kiitfossosh/Cool-Yield/pull/2 198 | https://github.com/OCA/web/pull/1694 199 | https://github.com/hillhacks/website/pull/42 200 | https://github.com/Crooms-Academy/tsa-website/pull/1 201 | https://github.com/OSLC/logo/pull/7 202 | https://github.com/appium/appium/pull/14781 203 | https://github.com/ahampriyanshu/algo_ds_101/pull/540 204 | https://github.com/Python-World/python-mini-projects/pull/325 205 | https://github.com/Python-World/Python_and_the_Web/pull/314 206 | https://github.com/godotengine/godot/pull/42551 207 | https://github.com/vJechsmayr/PythonAlgorithms/pull/223 208 | https://github.com/OCA/project/pull/720 209 | https://github.com/JuliaCloud/AWSCore.jl/pull/128 210 | https://github.com/vJechsmayr/PythonAlgorithms/pull/228 211 | https://github.com/OleVik/personal-academic-website/pull/6 212 | https://github.com/Palanaeum/palanaeum/pull/82 213 | https://github.com/appsmithorg/appsmith/pull/915 --------------------------------------------------------------------------------