├── .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 | 95 | ${formatTable(followers)} 96 |
97 | 98 | ## LICENSE 99 | Copyright (c) 2023-present [${user.login}](https://github.com/${user.login}) 100 | `; 101 | } 102 | 103 | function formatBlog(blog) { 104 | return blog ? `[${blog}](https://${blog})` : "-"; 105 | } 106 | 107 | function formatCompany(company) { 108 | return company ? `[@${company.replace("@", "")}](https://github.com/${company.replace("@", "")})` : "-"; 109 | } 110 | 111 | function formatTable(users) { 112 | return users.reduce((acc, user, index) => { 113 | if (index % 10 === 0) acc += ""; 114 | acc += ``; 115 | if (index % 10 === 9) acc += ""; 116 | return acc; 117 | }, ""); 118 | } 119 | 120 | run(); 121 | --------------------------------------------------------------------------------