├── README.md └── git_spam.py /README.md: -------------------------------------------------------------------------------- 1 | # Github-Feed-Overflow 2 | POC (maybe? ʘ‿ʘ) to fill up any user's github feed with spam activities. You can also hide your actual activities by generating noise over your actual acitivities using this script. 3 | 4 | ## Story Time 5 | Recently, a friend of mine ran a bot to star and fork some repos. In no time, my github acitivity feed was full of her activites. I scrolled to bottom of my feed, and I could see nothing else but activities of hers. ᕙ(⇀‸↼‶)ᕗ 6 | 7 | > I was like dear god, why'd you do it!? щ(゚Д゚щ) 8 | 9 | Github's feed is not like Facebook's endless feed, you hit an end soon! After a short dialogue about this with one of the people from Github, I came to know that the activity feed you see on your dashboard is actually just a snapshot of events, so you can do nothing (for now) once it's created. Doesn't matter even if you unfollow or block that user. (╯°□°)╯︵ ┻━┻ 10 | 11 | ## Conclusion 12 | I came up with a Proof-Of-Concept to take advantage of this problem and abuse the system in a legit way. You can spam any user present on the github. To spam somone who follows you is really easy. To spam someone who doesn't follow you, Github API is used to get subscribed repos of the user. The script stars all the repos of the user, creates issues on subscribed repos, and also mentions user in comments and body of the issues. Though, this totally depends on how much subscriptions a user has, and thus may limit the spam. But hey! Atleast we tried, and it works perfectly for your followers. (☞゚ヮ゚)☞ 13 | 14 | Anyone who would like to test it is welcome. Although it's a one-time-fun to test it, after which you'll probably be unfollowed or blocked by others, isn't it cool (spam 🠘🠚 cool, maybe not :P)? And you can always apologize to friends for small spams ( ͡° ͜ʖ ͡°) 15 | 16 | Generate API token [here](https://github.com/settings/tokens) with repo, user and write:discussion access. 17 | ``` 18 | $ python git_spam.py --help 19 | usage: git_spam.py [-h] [--target_user USER2SPAM] --token TOKEN --username 20 | USERNAME [--activities ACTIVITIES] 21 | [--issues ISSUE_PER_REPO] [--mode MODE] 22 | $ python git_spam.py --username --token --target_user 23 | ``` 24 | 25 | > After your mass acitvities, snapshots have been generated for target user(s). Now say, you think "Damn, I had starred inportant things, but now it's full of random repos as well". Well, there's a rollback option, that unstars all the repos that were starred during the spam. 26 | 27 | ![](http://i3.kym-cdn.com/photos/images/newsfeed/001/176/251/4d7.png) 28 | 29 | ##### Evil Plans 30 | Generate accounts from mail-clients like [protonmail](https://protonmail.com), [tutanota](https://tutanota.com) etc, and make a giant "spam-mesh" with a thread spawned for each user token. (⌐■_■) -------------------------------------------------------------------------------- /git_spam.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import argparse 3 | from time import ctime, sleep 4 | import random 5 | import json 6 | import sys 7 | 8 | # ToDO - Sleep temporarily in between to sync with rate-limiting of the API token 9 | # We can leave that for now as we enjoy 5000 requests per hour 10 | # Problem is with rate of unauthorized requests - 60 per hour 11 | # Rather crawl and scrape data instead of API then for unauthentic requests? - The saga continues... 12 | 13 | 14 | def get_random_repo_data(seed): 15 | # 30 results per query, so we will use pagination to get large number of 16 | # results 17 | req = requests.get(base_url + "/users?since=%d" % seed) 18 | users = req.json() 19 | 20 | for user in users: 21 | username = user["login"] 22 | print("[%s] Fetching repos of user %s ..." % (ctime(), username)) 23 | req = requests.get(user["repos_url"]) 24 | repos = req.json() 25 | for repo in repos: 26 | yield username, repo["name"] # a little better performance 27 | print("[%s] Done fetching and starring repos of user %s" % (ctime(), username)) 28 | sleep(1) 29 | 30 | 31 | # POC of spamming any user present on github 32 | # No matter if the user follows you or not 33 | # To spam a user who doesn't follow you is a bit complicated 34 | # We will get all the subscriptions 35 | # We'll always send pr and open issue on the subscription 36 | # If subscription repo belongs to user, we'll star it as well to maximise spam :D 37 | # For now I'm being lazy, and so leaving out the pull request part :P 38 | def start_spam(user, token, flag, activity_number, issues_count): 39 | global user_seed 40 | 41 | # this file will be used if user wants to rollback and undo all the 42 | # activities 43 | log_file = open("activity.log", "wb") 44 | 45 | # Check if user follows you, then you do random starring as well to do more spam 46 | new_flag = False 47 | if user is not None: 48 | req = requests.get(base_url + "/users/%s/following/%s" % (user, username)) 49 | if req.status_code // 100 != 4: 50 | new_flag = True 51 | 52 | if flag or new_flag: 53 | while activity_number > 0: 54 | activity_number -= 30 55 | for name, repo in get_random_repo_data(user_seed): 56 | try: 57 | req = requests.put( 58 | base_url + "/user/starred/%s/%s" % (name, repo), 59 | headers={"Authorization": "token %s" % token} 60 | ) 61 | 62 | if req.status_code != 204: 63 | print("[%s] Failed starring %s/%s" % (ctime(), name, repo)) 64 | else: 65 | print("[%s] Starred %s/%s successfully" % (ctime(), name, repo)) 66 | log_file.write(base_url + "/user/starred/%s/%s\n" % (name, repo)) 67 | except Exception as err: 68 | print(err) 69 | print("[%s] Failed starring %s/%s" % (ctime(), name, repo)) 70 | sleep(1) 71 | user_seed += 30 72 | if not flag: 73 | while activity_number > 0: 74 | activity_number -= 30 75 | req = requests.get(base_url + "/users/%s/subscriptions" % user) 76 | subscriptions = req.json() 77 | for sub in subscriptions: 78 | name, repo = sub["full_name"].split("/") 79 | if name == user: # we'll star this also 80 | try: 81 | req = requests.put( 82 | base_url + "/user/starred/%s/%s" % (name, repo), 83 | headers={"Authorization": "token %s" % token} 84 | ) 85 | 86 | if req.status_code != 204: 87 | print("[%s] Failed starring %s/%s" % (ctime(), name, repo)) 88 | else: 89 | print("[%s] Starred %s/%s successfully" % (ctime(), name, repo)) 90 | log_file.write(base_url + "/user/starred/%s/%s\n" % (name, repo)) 91 | except Exception as err: 92 | print(err) 93 | print("[%s] Failed starring %s/%s" % (ctime(), name, repo)) 94 | # making an issue 95 | default_issue = { 96 | "title": "Need help with setting up the project.", 97 | "body": "I'm getting some errors while trying to run it. @%s" % (name) 98 | } 99 | req = requests.get(base_url + "/repos/%s/%s/issues" % (name, repo)) 100 | issues = req.json()[::-1] 101 | issues_to_post = [] 102 | """ 103 | Use below url to get older issues to troll :D 104 | New issues copied will be easily caught as spam 105 | https://api.github.com/repos/servo/servo/issues?since=YYYY-MM-DDTHH:MM:SSZ 106 | """ 107 | if len(issues) < issues_count: 108 | for _ in xrange(issues_count - len(issues)): 109 | issues_to_post.append(default_issue) 110 | for idx, issue in enumerate(issues): 111 | if len(issues_to_post) == issues_count: 112 | break 113 | 114 | issues_to_post.append({ 115 | "title": issue["title"], 116 | "body": issue["body"] + "\n@%s, Can you help me out?" % name 117 | }) 118 | random.shuffle(issues_to_post) 119 | for issue in issues_to_post: 120 | try: 121 | req = requests.post( 122 | base_url + "/repos/%s/%s/issues" % (name, repo), 123 | data=json.dumps(issue), 124 | headers={"Authorization": "token %s" % token} 125 | ) 126 | 127 | if req.status_code != 201: 128 | print("[%s] Failed creating issue for %s/%s" % (ctime(), name, repo)) 129 | break 130 | else: 131 | print("[%s] Issue for %s/%s created successfully" % (ctime(), name, repo)) 132 | except Exception as err: 133 | print(err) 134 | print("[%s] Failed creating issue for %s/%s" % (ctime(), name, repo)) 135 | break 136 | sleep(1) 137 | 138 | log_file.close() 139 | print("[%s] Spam Completed" % ctime()) 140 | if mode == 'rollback': 141 | rollback(token) 142 | 143 | 144 | def rollback(token): 145 | print("[%s] Peforming clean-up and starting unstarring process..." % ctime()) 146 | with open("activity.log", "rb") as f: 147 | links = f.read() 148 | links = links.split("\n")[:-1] 149 | err_urls = [] 150 | for link in links: 151 | cnt = 0 152 | idx = len(link) - 1 153 | while cnt < 2: 154 | idx -= 1 155 | if link[idx] == '/': 156 | cnt += 1 157 | name, repo = link[idx + 1:].split("/") 158 | try: 159 | req = requests.delete(link, headers={"Authorization": "token %s" % token}) 160 | if req.status_code == 204: 161 | print("[%s] Successfully unstarred %s/%s" % (ctime(), name, repo)) 162 | else: 163 | err_urls.append(link) 164 | print("[%s] Failed unstarring %s/%s" % (ctime(), name, repo)) 165 | except Exception as err: 166 | err_urls.append(link) 167 | print(err) 168 | print("[%s] Failed unstarring %s/%s" % (ctime(), name, repo)) 169 | sleep(0.5) 170 | print("[%s] Finished clean-up" % ctime()) 171 | 172 | if err_urls: 173 | print("%d repos were left starred due to some failure." % len(err_urls)) 174 | print("Please run the rollback again for the left repos or unstar them manually.") 175 | with open("activity.log", "wb") as f: 176 | f.write("\n".join(err_urls)) 177 | 178 | 179 | if __name__ == '__main__': 180 | parser = argparse.ArgumentParser() 181 | parser.add_argument('--target_user', dest='user2spam') 182 | parser.add_argument('--token', dest='token', required=True) 183 | parser.add_argument('--username', dest='username', required=True) 184 | parser.add_argument('--activities', dest='activities', default=30, type=int) 185 | parser.add_argument('--issues', dest='issue_per_repo', default=1, type=int) 186 | parser.add_argument('--mode', dest='mode', default='normal') 187 | args = parser.parse_args() 188 | 189 | user2spam, token, username = args.user2spam, args.token, args.username 190 | activity_number, issues_count, mode = args.activities, args.issue_per_repo, args.mode 191 | 192 | user_seed = 1 193 | base_url = "https://api.github.com" 194 | all_followers = False 195 | 196 | if mode not in ["normal", "rollback", 'only-rollback']: 197 | raise("Correct Usage: --mode ") 198 | 199 | req = requests.get("https://api.github.com/rate_limit", headers={"Authorization": "token %s" % token}) 200 | if req.status_code // 100 == 4: 201 | raise("Something went wrong. Probably with your token.") 202 | 203 | if mode == "only-rollback": 204 | rollback(token) 205 | sys.exit(0) 206 | 207 | if user2spam is None: 208 | all_followers = True 209 | 210 | # if issues_count > 30: 211 | # print("[%s] Max issues per repo can be 30, setting to 30..." % ctime()) 212 | # issues_count = 30 213 | 214 | print("[%s] Starting Spam..." % ctime()) 215 | start_spam(user2spam, token, all_followers, activity_number, issues_count) 216 | --------------------------------------------------------------------------------