├── .gitignore
├── package.json
├── .github
└── workflows
│ └── node.js.yml
├── action.yaml
├── LICENSE
├── README.md
└── main.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Github-Auto-Follow-Unfollow-View-User-List",
3 | "version": "1.0.0",
4 | "description": "The GitHub action to workflow runs for auto follow/unfollow/view users",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/Huniko519/Github-Auto-Follow-Unfollow-View-User-List.git"
12 | },
13 | "keywords": [
14 | "workflow runs",
15 | "Actions",
16 | "auto follow/unfollow/view workflow runs"
17 | ],
18 | "author": "",
19 | "license": "ISC",
20 | "bugs": {
21 | "url": "https://github.com/Huniko519/Github-Auto-Follow-Unfollow-View-User-List/issues"
22 | },
23 | "homepage": "https://github.com/Huniko519/Github-Auto-Follow-Unfollow-View-User-List#readme",
24 | "dependencies": {
25 | "@actions/core": "^1.10.1",
26 | "@actions/github": "^6.0.0",
27 | "@octokit/rest": "^20.0.2"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/.github/workflows/node.js.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
3 |
4 | name: Node.js CI
5 |
6 | on:
7 | push:
8 | branches: [ "main" ]
9 | pull_request:
10 | branches: [ "main" ]
11 |
12 | jobs:
13 | build:
14 |
15 | runs-on: ubuntu-latest
16 |
17 | strategy:
18 | matrix:
19 | node-version: [18.x]
20 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
21 |
22 | steps:
23 | - uses: actions/checkout@v3
24 | - name: Use Node.js ${{ matrix.node-version }}
25 | uses: actions/setup-node@v3
26 | with:
27 | node-version: ${{ matrix.node-version }}
28 | cache: 'npm'
29 | - run: npm ci
30 | - run: npm install
31 | - run: npm run build --if-present
32 |
--------------------------------------------------------------------------------
/action.yaml:
--------------------------------------------------------------------------------
1 | name: 'Github Follows & Unfollows Automation Bot'
2 | description: 'Features unfollow all non-followers of the owner and follow back those who the owner was not following.'
3 | author: 'Huniko519'
4 |
5 | inputs:
6 | token:
7 | description: 'The token used to authenticate.'
8 | required: true
9 | default: ${{ github.token }}
10 |
11 | repository:
12 | description: 'The name of the repository.'
13 | required: true
14 | default: ${{ github.repository }}
15 |
16 | isReadmeUpdate:
17 | description: 'Readme update enable status.'
18 | required: false
19 | default: ${{ github.isReadmeUpdate }}
20 |
21 | safeUserList:
22 | description: 'Lists of Safe Users.'
23 | required: false
24 | default: ${{ github.safeUserList }}
25 |
26 | isEnableFollow:
27 | description: 'Enable Follow'
28 | requried: false
29 | default: true
30 |
31 | isEnableUnfollow:
32 | description: 'Enable Unfollow'
33 | required: false
34 | default: true
35 |
36 | runs:
37 | using: 'node20'
38 | main: 'main.js'
39 |
40 | branding:
41 | icon: 'star'
42 | color: 'yellow'
43 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023
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.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Auto Follows & Unfollows by GitHub Actions for GitHub users
2 |
3 | A feature that unfollows all non-followers of the owner and a feature that follows back those who were not being followed by the owner.
4 | GitHub Action automatically creates or updates README with information of owner and followers.
5 |
6 | ## Usage
7 |
8 | The purpose of this program is to generate an updated README file for any user's repository, displaying details about their account's followers and current user's info.
9 | The program also implements two functions to perform actions on the GitHub API: a feature that unfollows all non-followers of the owner and a feature that follows back those who were not being followed by the owner.
10 |
11 | ## Example
12 |
13 | ```yaml
14 | name: Github-Auto-Follow-Unfollow-View-User-List
15 |
16 | on:
17 | workflow_dispatch:
18 | schedule:
19 | - cron: '0 */8 * * 1-5'
20 |
21 | jobs:
22 | auto-update:
23 | runs-on: ubuntu-latest
24 | steps:
25 | - name: Github-Auto-Follow-Unfollow-View-User-List
26 | uses: Huniko519/Github-Auto-Follow-Unfollow-View-User-List@main
27 | with:
28 | token: ${{ secrets.TOKEN }}
29 | repository: ${{ github.repository }}
30 | isReadmeUpdate: true
31 | isEnableFollow: true
32 | isEnableUnfollow: true
33 | safeUserList: 'Babi1205,Huniko-Team'
34 | ```
35 | [Example Link](https://github.com/Huniko519/Auto-Follows-Unfollows-by-Github-Actions-for-Github-users)
36 |
37 | ## Inputs
38 |
39 | | inputs | required | default | description |
40 | |--------------------------|----------|-----------------------|-------------|
41 | | `token` | true | `${{ github.token }}` | The token used to authenticate. |
42 | | `repository` | true | `${{ github.repository }}` | The name of the repository. |
43 | | `isReadmeUpdate` | false | `${{ github.isReadmeUpdate }}` | Readme update feature enable status. |
44 | | `isEnableFollow` | false | `${{ github.isEnableFollow }}` | Follow feature enable status. |
45 | | `isEnableUnfollow` | false | `${{ github.isEnableUnfollow }}` | Unfollow feature enable status. |
46 | | `safeUserList` | false | `${{ github.safeUserList }}` | Lists of Safe Users. |
47 |
48 | ## Input of this action
49 |
50 | - input:
51 | - `token`: [GitHub personal access token](https://github.com/settings/tokens/new) with at least **'read:user' and 'repo'** scope. _⚠️ You should store this token as [secret](#secrets)._ This input is required.
52 | - `repository`: This is the name of installed repository. This input is required.
53 |
54 | ## Contributors
55 |
56 |
57 | | [![Huniko519][huniko519_avatar]][huniko519_homepage]
[Huniko519][huniko519_homepage] | [![B.B][bibi1205_avatar]][bibi1205_homepage]
[B.B][bibi1205_homepage] |
58 | | --- | --- |
59 |
60 |
61 | [huniko519_homepage]: https://github.com/Huniko519
62 | [huniko519_avatar]: https://img.cloudposse.com/111x111/https://github.com/Huniko519.png
63 | [bibi1205_homepage]: https://github.com/Bibi1205
64 | [bibi1205_avatar]: https://img.cloudposse.com/111x111/https://github.com/Bibi1205.png
65 |
66 | ## LICENSE
67 | Copyright (c) 2023-present [Huniko519](https://github.com/Huniko519)
68 |
--------------------------------------------------------------------------------
/main.js:
--------------------------------------------------------------------------------
1 | const { Octokit } = require("@octokit/rest");
2 | const core = require("@actions/core");
3 |
4 | async function run() {
5 | try {
6 | const token = core.getInput('token');
7 | const repository = core.getInput('repository');
8 | const isReadmeUpdate = core.getInput('isReadmeUpdate') === 'true';
9 | const isEnableFollow = core.getInput('isEnableFollow') === 'true';
10 | const isEnableUnfollow = core.getInput('isEnableUnfollow') === 'true';
11 | const safeUserList = core.getInput('safeUserList').split(",");
12 |
13 | const [username, reponame] = repository.split("/");
14 | const octokit = new Octokit({ auth: `token ${token}` });
15 |
16 | const followers = await fetchAllPages(octokit.users.listFollowersForUser, { username });
17 | const following = await fetchAllPages(octokit.users.listFollowingForUser, { username });
18 |
19 | const unfollowers = following.filter(user => !followers.some(follower => follower.login === user.login));
20 | const unfollowing = followers.filter(user => !following.some(follow => follow.login === user.login));
21 |
22 | if (isEnableUnfollow && unfollowers.length > 0) {
23 | await processUsers(unfollowers, octokit.users.unfollow, safeUserList);
24 | console.log(`You unfollowed ${unfollowers.length} user(s).`);
25 | }
26 |
27 | if (isEnableFollow && unfollowing.length > 0) {
28 | await processUsers(unfollowing, octokit.users.follow);
29 | console.log(`You followed ${unfollowing.length} user(s).`);
30 | }
31 |
32 | if (isReadmeUpdate && (isEnableFollow || isEnableUnfollow) && (unfollowers.length > 0 || unfollowing.length > 0)) {
33 | await updateReadme(octokit, username, reponame, followers);
34 | }
35 |
36 | console.log("Done!");
37 | } catch (error) {
38 | console.log(error.message);
39 | }
40 | }
41 |
42 | async function fetchAllPages(apiMethod, params, page = 1, results = []) {
43 | const { data } = await apiMethod({ ...params, per_page: 100, page });
44 | results.push(...data);
45 | return data.length === 100 ? fetchAllPages(apiMethod, params, page + 1, results) : results;
46 | }
47 |
48 | async function processUsers(users, apiMethod, safeUserList = []) {
49 | for (const user of users) {
50 | if (!safeUserList.includes(user.login)) {
51 | await apiMethod({ username: user.login });
52 | }
53 | }
54 | }
55 |
56 | async function updateReadme(octokit, username, reponame, followers) {
57 | const { data: user } = await octokit.users.getByUsername({ username });
58 | const content = generateReadmeContent(user, followers);
59 | const { status, message: sha } = await checkFileExistence(octokit, username, reponame);
60 |
61 | const requestData = {
62 | owner: username,
63 | repo: reponame,
64 | path: "README.md",
65 | message: "Updated: Readme.md With New Infos By Github Action",
66 | content: Buffer.from(content).toString('base64'),
67 | committer: { name: username, email: `${username}@users.noreply.github.com` },
68 | author: { name: username, email: `${username}@users.noreply.github.com` },
69 | ...(status && { sha })
70 | };
71 |
72 | await octokit.repos.createOrUpdateFileContents(requestData);
73 | }
74 |
75 | async function checkFileExistence(octokit, owner, repo) {
76 | try {
77 | const { data: { sha } } = await octokit.repos.getReadme({ owner, repo });
78 | return { status: true, message: sha };
79 | } catch (error) {
80 | return { status: false, message: error.status === 404 ? "File does not exist." : error.message };
81 | }
82 | }
83 |
84 | function generateReadmeContent(user, followers) {
85 | return `## ${user.login}
86 |
87 |
88 | | Name | Bio | Blog | Location | Company |
89 | | -- | -- | -- | -- | -- |
90 | | ${user.name || "-"} | ${user.bio || "-"} | ${formatBlog(user.blog)} | ${user.location || "-"} | ${formatCompany(user.company)} |
91 |
92 | ## Followers ${followers.length}
93 |
94 |