Full Code of ryo-ma/github-profile-trophy for AI

master bff648c9991b cached
50 files
149.1 KB
42.3k tokens
137 symbols
1 requests
Download .txt
Repository: ryo-ma/github-profile-trophy
Branch: master
Commit: bff648c9991b
Files: 50
Total size: 149.1 KB

Directory structure:
gitextract_uvsbr8fm/

├── .editorconfig
├── .github/
│   ├── FUNDING.yml
│   └── workflows/
│       ├── close-old-issues.yml
│       ├── test-repository-action.yml
│       └── testing.yml
├── .gitignore
├── .vercelignore
├── CONTRIBUTING.md
├── Dockerfile
├── LICENSE
├── README.md
├── action.yml
├── api/
│   └── index.ts
├── deno.json
├── deps.ts
├── docker-compose.yml
├── env-example
├── main.ts
├── render_svg.ts
├── src/
│   ├── Helpers/
│   │   ├── Logger.ts
│   │   ├── Retry.ts
│   │   └── __tests__/
│   │       └── Retry.test.ts
│   ├── Repository/
│   │   └── GithubRepository.ts
│   ├── Schemas/
│   │   └── index.ts
│   ├── Services/
│   │   ├── GithubApiService.ts
│   │   ├── __mocks__/
│   │   │   ├── notFoundUserMock.json
│   │   │   ├── rateLimitMock.json
│   │   │   └── successGithubResponse.json
│   │   ├── __tests__/
│   │   │   └── githubApiService.test.ts
│   │   └── request.ts
│   ├── StaticRenderRegeneration/
│   │   ├── cache_manager.ts
│   │   ├── index.ts
│   │   ├── types.ts
│   │   └── utils.ts
│   ├── Types/
│   │   ├── EServiceKindError.ts
│   │   ├── Request.ts
│   │   ├── ServiceError.ts
│   │   └── index.ts
│   ├── card.ts
│   ├── config/
│   │   └── cache.ts
│   ├── error_page.ts
│   ├── icons.ts
│   ├── pages/
│   │   └── Error.ts
│   ├── theme.ts
│   ├── trophy.ts
│   ├── trophy_list.ts
│   ├── user_info.ts
│   └── utils.ts
├── test/
│   └── test.ts
└── vercel.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
root = true

[*]
indent_style = space
indent_size = 2
end_of_line = LF
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[vercel.json]
indent_size = 4

[*.md]
trim_trailing_whitespace = false


================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms

github: [ryo-ma]


================================================
FILE: .github/workflows/close-old-issues.yml
================================================
name: Close inactive issues
on:
  schedule:
    - cron: "30 1 * * *"

jobs:
  close-issues:
    runs-on: ubuntu-latest
    permissions:
      issues: write
      pull-requests: write
    steps:
      - uses: actions/stale@v5
        with:
          days-before-issue-stale: 30
          days-before-issue-close: 14
          stale-issue-label: "stale"
          stale-issue-message: "This issue is stale because it has been open for 30 days with no activity."
          close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale."
          days-before-pr-stale: -1
          days-before-pr-close: -1
          repo-token: ${{ secrets.GITHUB_TOKEN }}


================================================
FILE: .github/workflows/test-repository-action.yml
================================================
name: Test GitHub Profile Trophy Action
on: [push, pull_request, workflow_dispatch]

jobs:
  test-trophy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout this repo
        uses: actions/checkout@v4
      - name: Test Action
        id: trophy
        uses: ./.
        with:
          username: ${{ github.repository_owner }}
          output_path: ./trophy.svg
          token: ${{ secrets.GITHUB_TOKEN }}

      - name: Verify SVG generated
        run: |
          if [ -f trophy.svg ]; then
            echo "SVG exsists ($(wc -c < trophy.svg) Bytes)"
            file trophy.svg
          else
            echo "SVG failed to generate"
            exit 1
          fi


================================================
FILE: .github/workflows/testing.yml
================================================
name: Check PR Test

on:
  push:
    branches:
      - master
  pull_request:
    branches:
      - master
  workflow_dispatch:

jobs:
  install-dependencies:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        deno-version: ["1.44.4"]

    steps:
      # Step 1: Checkout repository
      - name: Git Checkout Deno Module
        uses: actions/checkout@v4

      # Step 2: Setup Deno
      - name: Setup Deno
        uses: denoland/setup-deno@v1
        with:
          deno-version: ${{ matrix.deno-version }}

      # Step 3: Cache / Install dependencies
      # Change src/mod.ts to main.ts if your project uses main.ts
      - name: Install dependencies
        run: deno cache --reload deps.ts

      # Step 4: Format check
      - name: Deno format check
        run: deno fmt --check

      # Step 5: Lint check (won't fail workflow)
      - name: Deno lint check
        run: deno lint || true

      # Step 6: Run tests
      - name: Test Deno Module
        run: deno task test


================================================
FILE: .gitignore
================================================
.vscode
.env
.idea
deno.lock
*.sh
**/.DS_Store


================================================
FILE: .vercelignore
================================================
.gitignore
.github
README.md
LICENSE
debug.ts

================================================
FILE: CONTRIBUTING.md
================================================
# Contribution Guide

## Environment

- Deno >= v1.36.1
- [Vercel](https://vercel.com/)
- GitHub API v4
- Docker and Docker compose (optional)

## Local Run

Create `.env` file to project root directory, and write your GitHub token to the
`.env` file. Please select the authority of `repo` when creating token.

```properties
GITHUB_TOKEN1=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
GITHUB_TOKEN2=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

# if using GitHub Enterprise:
# (this env var defaults to https://api.github.com/graphql)
GITHUB_API=https://github.example.com/api/graphql
```

Run local server.

```sh
deno task start
```

You can enable the Redis if you want, but it's not mandatory.

```sh
docker compose up -d
```

Rename `env-example` to `.env`, and change ENABLE_REDIS to true

Open localhost from your browser.

http://localhost:8080/?username=ryo-ma

## Editor config

Read the [.editorconfig](./.editorconfig)

## Pull Requests

Pull requests are always welcome! In general, they should a single concern in
the least number of changed lines as possible. For changes that address core
functionality, it is best to open an issue to discuss your proposal first. I
look forward to seeing what you come up with!

## Run deno lint

## What to do before contributing

### 1. Run deno lint

```sh
deno task lint
```

### 2. Run deno format

```sh
deno task format
```

### 3. Run deno test

```sh
deno task test
```


================================================
FILE: Dockerfile
================================================
FROM denoland/deno:latest

# Create working directory
WORKDIR /app

# Copy source
COPY . .

# Compile the main app
RUN deno cache main.ts

# Run the app
CMD ["deno", "run", "-A", "main.ts"]


================================================
FILE: LICENSE
================================================
Copyright (c) 2020 ryo-ma

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


================================================
FILE: README.md
================================================
<div align="center">
  <img width="140" src="https://user-images.githubusercontent.com/6661165/91657958-61b4fd00-eb00-11ea-9def-dc7ef5367e34.png"  alt="GitHub Profile Trophy"/>
  <h2 align="center">GitHub Profile Trophy</h2>
  <p align="center">🏆 Add dynamically generated GitHub Stat Trophies on your README</p>
</div>
<div align="center">

[![stargazers](https://img.shields.io/github/stars/ryo-ma/github-profile-trophy)](https://github.com/ryo-ma/github-profile-trophy/stargazers)
[![forks](https://img.shields.io/github/forks/ryo-ma/github-profile-trophy)](https://github.com/ryo-ma/github-profile-trophy/network/members)
[![issues](https://img.shields.io/github/issues/ryo-ma/github-profile-trophy)](https://github.com/ryo-ma/github-profile-trophy/issues)
[![license](https://img.shields.io/github/license/ryo-ma/github-profile-trophy)](https://github.com/ryo-ma/github-profile-trophy/blob/master/LICENSE)
[![share](https://img.shields.io/twitter/url?style=social&url=https%3A%2F%2Fgithub.com%2Fryo-ma%2Fgithub-profile-trophy)](https://twitter.com/intent/tweet?text=Add%20dynamically%20generated%20GitHub%20Trophy%20on%20your%20readme%0D%0A&url=https%3A%2F%2Fgithub.com%2Fryo-ma%2Fgithub-profile-trophy)

</div>
<p align="center">
  You can use this service for free. I'm looking for sponsors to help us keep up with this service❤️
</p>
<div align="center">
  <a href="https://github.com/sponsors/ryo-ma">
    <img src="https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub&color=ff69b4" alt="Sponsor"/>
  </a>
</div>

> ⚠️ **Notice from the Project Owner**
>
> Hello everyone,\
> I am the owner of this project.
>
> Currently, due to the increase in users and traffic, the cost of maintaining
> this service has become quite high, and it is becoming financially difficult
> to sustain.\
> As costs continue to rise, there is a possibility that the service may have to
> be discontinued.\
> We are now at a stage where monetization and financial support are essential.
>
> Your support in the following ways would be greatly appreciated:
>
> 1. Financial support: [GitHub Sponsors](https://github.com/sponsors/ryo-ma)
> 2. Reducing server load through self-hosting (by forking on GitHub and
>    deploying to Vercel)
> 3. Share your github-profile-trophy URL deployed to Vercel for load balancing
>    (Send email: saka_ro@yahoo.co.jp or Pull Request)

# Load balancing endpoints

These are endpoints provided by volunteers. Please use these in moderation.

- [https://github-profile-trophy-liard-delta.vercel.app](https://github-profile-trophy-liard-delta.vercel.app/)
  by [Adwitya](https://github.com/Adwitya)
- [https://github-profile-trophy-fork-two.vercel.app](https://github-profile-trophy-fork-two.vercel.app)
  by [hesreallyhim](https://github.com/hesreallyhim)
- [https://github-profile-trophy-winning.vercel.app](https://github-profile-trophy-winning.vercel.app)
  by [hongbo-wei](https://github.com/hongbo-wei)
- [https://github-profile-trophy-kannan.vercel.app](https://github-profile-trophy-kannan.vercel.app)
  by [kann4n](https://github.com/kann4n)
- [https://trophy.ryglcloud.net](https://trophy.ryglcloud.net) by
  [PracticalRyan](https://github.com/PracticalRyan)
- [https://github-profile-trophy-tawny.vercel.app](https://github-profile-trophy-tawny.vercel.app)
  by [vijaypurohit322](https://github.com/vijaypurohit322)
- [https://github-profile-repo.vercel.app](https://github-profile-repo.vercel.app/))
  by [HackyCoder0951](https://github.com/hackycoder0951)
- [https://gh-trophy.cdnsoft.net](https://gh-trophy.cdnsoft.net) by
  [cromatikap](https://github.com/cromatikap)
- [https://trophygh.kolioaris.xyz](https://trophygh.kolioaris.xyz) by
  [kolioaris](https://github.com/kolioaris)
- [https://github-profile-trophy-orcin-eta.vercel.app](https://github-profile-trophy-orcin-eta.vercel.app/)
  by [manupawick](https://github.com/manupawickramasinghe)
- [https://github-profile-trophy-reiyua-mirror.vercel.app](https://github-profile-trophy-reiyua-mirror.vercel.app)
  by [reiyua](https://github.com/reiyua)

# Quick Start

Add the following code to your readme. When pasting the code into your profile's
readme, change the `?username=` value to your GitHub's username.

```
[![trophy](https://github-profile-trophy.vercel.app/?username=ryo-ma)](https://github.com/ryo-ma/github-profile-trophy)
```

<p align="center">
  <img src="https://github-profile-trophy.vercel.app/?username=ryo-ma&column=8&rank=SSS,SS,S,AAA,AA,A,B,C" />
</p>

## Use theme

Add optional parameter of the theme.

```
[![trophy](https://github-profile-trophy.vercel.app/?username=ryo-ma&theme=onedark)](https://github.com/ryo-ma/github-profile-trophy)
```

<p align="center">
  <img width="660" src="https://user-images.githubusercontent.com/6661165/92327052-d99b9e00-f091-11ea-9a24-c7ec86982370.png">
</p>

**[More detail](#apply-theme)**

# About Rank

Ranks are `SSS` `SS` `S` `AAA` `AA` `A` `B` `C` `UNKNOWN` `SECRET`.

| Rank       | Description                                                                                |
| ---------- | ------------------------------------------------------------------------------------------ |
| SSS, SS, S | You are at a hard to reach rank. You can brag.                                             |
| AAA, AA, A | You will reach this rank if you do your best. Let's aim here first.                        |
| B, C       | You are currently making good progress. Let's aim a bit higher.                            |
| UNKNOWN    | You have not taken action yet. Let's act first.                                            |
| SECRET     | This rank is very rare. The trophy will not be displayed until certain conditions are met. |

**NOTE: The `UNKNOWN` rank is denoted by `?`**

## Secret Rank

The acquisition condition is secret, but you can see this.

<p align="center">
  <img width="110" src="https://github.com/user-attachments/assets/40461f38-a317-431c-93d2-a56c2e803cf3" />
</p>

There are only a few secret trophies. Therefore, if you come up with interesting
conditions, I will consider adding a trophy. I am waiting for contributions.

# About Display details

<p align="center">
  <img width="220" src="https://user-images.githubusercontent.com/6661165/91642962-6333e600-ea6a-11ea-83af-e371e996bfa6.png" />
</p>

1. Title name of aggregation target.
2. Current rank.
3. Title according to rank.
4. Target aggregation result.
5. Rank progress bar.

# Optional Request Parameters

- [title](#filter-by-titles)
- [rank](#filter-by-ranks)
- [column](#specify-the-maximum-row--column-size)
- [row](#specify-the-maximum-row--column-size)
- [theme](#apply-theme)
- [margin-w](#margin-width)
- [margin-h](#margin-height)
- [no-bg](#transparent-background)
- [no-frame](#hide-frames)

## Filter by titles

You can filter the display by specifying the titles of trophy.

```
https://github-profile-trophy.vercel.app/?username=ryo-ma&title=Followers
```

<p align="center">
  <img width="110" src="https://user-images.githubusercontent.com/6661165/92317141-80ebe700-f038-11ea-8501-4015bfbb2cf4.png">
</p>

If you want to specify multiple titles.

```
https://github-profile-trophy.vercel.app/?username=ryo-ma&title=Stars,Followers
```

<p align="center">
  <img width="220" src="https://github.com/user-attachments/assets/3b8a1c8b-afcd-49dc-ab18-a439d5c36a83">
</p>

You can also exclude the trophies you don't want to display.

```
https://github-profile-trophy.vercel.app/?username=ryo-ma&title=-Stars,-Followers
```

## Filter by ranks

You can filter the display by specifying the ranks.\
`Available values: SECRET SSS SS S AAA AA A B C`

```
https://github-profile-trophy.vercel.app/?username=ryo-ma&rank=S
```

<p align="center">
  <img width="110" src="https://user-images.githubusercontent.com/6661165/91642657-1cdd8780-ea68-11ea-994b-4568a55cd22a.png" />
</p>

If you want to specify multiple ranks.

```
https://github-profile-trophy.vercel.app/?username=ryo-ma&rank=S,AAA
```

<p align="center">
  <img width="220" src="https://github.com/user-attachments/assets/0c2ffca8-4b03-4d46-b1d7-4e1eb6702f68">
</p>

You can also exclude ranks.

```
https://github-profile-trophy.vercel.app/?username=ryo-ma&rank=-C,-B
```

**NOTE: Since `UNKNOWN` is denoted by `?`, in order to include or exclude it you
will have to use `rank=?` and `rank=-?` respectively**

## Specify the maximum row & column size

You can specify the maximum row and column size.\
Trophy will be hidden if it exceeds the range of both row and column.

`Available value: number type`\
`Default: column=6 row=3`

Restrict only row

```
https://github-profile-trophy.vercel.app/?username=ryo-ma&row=2
```

Restrict only column

```
https://github-profile-trophy.vercel.app/?username=ryo-ma&column=2
```

Restrict row & column

```
https://github-profile-trophy.vercel.app/?username=ryo-ma&row=2&column=3
```

<p align="center">
  <img width="330" src="https://user-images.githubusercontent.com/6661165/91659474-c07f7400-eb0a-11ea-84f2-eb6b42547829.png">
</p>

Adaptive column

```
https://github-profile-trophy.vercel.app/?username=ryo-ma&column=-1
```

You can set `column` to `-1` to adapt the width to the number of trophies, the
parameter `row` will be ignored.

## Apply theme

Available themes.

| theme                       |
| --------------------------- |
| [flat](#flat)               |
| [onedark](#onedark)         |
| [gruvbox](#gruvbox)         |
| [dracula](#dracula)         |
| [monokai](#monokai)         |
| [chalk](#chalk)             |
| [nord](#nord)               |
| [alduin](#alduin)           |
| [darkhub](#darkhub)         |
| [juicyfresh](#juicyfresh)   |
| [buddhism](#buddhism)       |
| [oldie](#oldie)             |
| [radical](#radical)         |
| [onestar](#onestar)         |
| [discord](#discord)         |
| [algolia](#algolia)         |
| [gitdimmed](#gitdimmed)     |
| [tokyonight](#tokyonight)   |
| [matrix](#matrix)           |
| [apprentice](#apprentice)   |
| [dark_dimmed](#dark_dimmed) |
| [dark_lover](#dark_lover)   |
| [kimbie_dark](#kimbie_dark) |
| [aura](#aura)               |

### flat

```
https://github-profile-trophy.vercel.app/?username=ryo-ma&theme=flat
```

<p align="center">
  <img width="660" src="https://user-images.githubusercontent.com/6661165/92325601-039b9300-f087-11ea-983a-fce8133549ee.png">
</p>

### onedark

```
https://github-profile-trophy.vercel.app/?username=ryo-ma&theme=onedark
```

<p align="center">
  <img width="660" src="https://user-images.githubusercontent.com/6661165/92327052-d99b9e00-f091-11ea-9a24-c7ec86982370.png">
</p>

### gruvbox

```
https://github-profile-trophy.vercel.app/?username=ryo-ma&theme=gruvbox
```

<p align="center">
  <img width="660" src="https://user-images.githubusercontent.com/6661165/92315152-e9c56600-f01c-11ea-9536-1bfbb158cfcb.png">
</p>

### dracula

```
https://github-profile-trophy.vercel.app/?username=ryo-ma&theme=dracula
```

<p align="center">
  <img width="660" src="https://user-images.githubusercontent.com/6661165/92490273-c91f2b00-f22b-11ea-9481-b5daae4d7bc3.png">
</p>

### monokai

```
https://github-profile-trophy.vercel.app/?username=ryo-ma&theme=monokai
```

<p align="center">
  <img width="660" src="https://user-images.githubusercontent.com/6661165/93725426-2c289e80-fbea-11ea-96a4-f6490ccf2126.png">
</p>

### chalk

```
https://github-profile-trophy.vercel.app/?username=ryo-ma&theme=chalk
```

<p align="center">
  <img width="660" src="https://user-images.githubusercontent.com/6661165/94294003-1de7d300-ff9a-11ea-91d1-60417a4d919b.png">
</p>

### nord

```
https://github-profile-trophy.vercel.app/?username=ryo-ma&theme=nord
```

<p align="center">
  <img width="660" src="https://user-images.githubusercontent.com/6661165/94346857-7ab2be80-006a-11eb-9082-36d377ae2531.png">
</p>

### alduin

```
https://github-profile-trophy.vercel.app/?username=ryo-ma&theme=alduin
```

<p align="center">
  <img width="660" src="https://user-images.githubusercontent.com/6661165/99085932-2a88bf00-260c-11eb-9b26-d2f125773831.png">
</p>

### darkhub

```
https://github-profile-trophy.vercel.app/?username=ryo-ma&theme=darkhub
```

<p align="center">
  <img width="660" src="https://user-images.githubusercontent.com/6661165/102801126-249ab080-43f8-11eb-91c8-f56f94c35777.png">
</p>

### juicyfresh

```
https://github-profile-trophy.vercel.app/?username=ryo-ma&theme=juicyfresh
```

<p align="center">
  <img width="660" src="https://user-images.githubusercontent.com/6661165/104810094-edbc8c80-5835-11eb-8c20-a76192a00728.png">
</p>

### buddhism

```
https://github-profile-trophy.vercel.app/?username=ryo-ma&theme=buddhism
```

<p align="center">
  <img width="660" src="https://user-images.githubusercontent.com/6661165/113709167-2412f500-971d-11eb-9ee5-0ab292cf8b4c.png">
</p>

### oldie

```
https://github-profile-trophy.vercel.app/?username=ryo-ma&theme=oldie
```

<p align="center">
  <img width="660" src="https://user-images.githubusercontent.com/6661165/113709581-a0a5d380-971d-11eb-8583-770dc4091ebf.png">
</p>

### radical

```
https://github-profile-trophy.vercel.app/?username=ryo-ma&theme=radical
```

<p align="center">
  <img width="660" src="https://user-images.githubusercontent.com/6661165/116633521-adbc8800-a994-11eb-97c4-e45a32721491.png">
</p>

### onestar

```
https://github-profile-trophy.vercel.app/?username=ryo-ma&theme=onestar
```

<p align="center">
  <img width="660" src="https://user-images.githubusercontent.com/6661165/122048400-2af46d00-ce1c-11eb-94e0-c2c6ddaf6819.png">
</p>

### discord

```
https://github-profile-trophy.vercel.app/?username=ryo-ma&theme=discord
```

<p align="center">
  <img width="660" src="https://user-images.githubusercontent.com/6661165/122048628-7dce2480-ce1c-11eb-9792-1e600b384c4d.png">
</p>

### algolia

```
https://github-profile-trophy.vercel.app/?username=ryo-ma&theme=algolia
```

<p align="center">
  <img width="660" src="https://user-images.githubusercontent.com/6661165/131685203-92a31101-2d93-4d18-b24a-d81a8bb012c5.png">
</p>

### gitdimmed

```
https://github-profile-trophy.vercel.app/?username=ryo-ma&theme=gitdimmed
```

<p align="center">
  <img width="660" src="https://user-images.githubusercontent.com/6661165/131685406-799a864f-2691-4840-bb71-1db9c087a507.png">
</p>

### tokyonight

```
https://github-profile-trophy.vercel.app/?username=ryo-ma&theme=tokyonight
```

<p align="center">
  <img width="660" src="https://user-images.githubusercontent.com/6661165/135482087-27764d6f-53b4-4c2a-8473-32431d12660c.png">
</p>

### matrix

```
https://github-profile-trophy.vercel.app/?username=ryo-ma&theme=matrix
```

<p align="center">
  <img width="660" src="https://user-images.githubusercontent.com/31789752/141647414-15cfe279-af12-4746-a886-f494c25c096d.png">
</p>

### apprentice

```
https://github-profile-trophy.vercel.app/?username=ryo-ma&theme=apprentice
```

<p align="center">
  <img width="660" src="https://user-images.githubusercontent.com/31789752/144701036-285cdd4b-d687-4ddc-95c2-7ccae9e25a1f.png">
</p>

### dark_dimmed

```
https://github-profile-trophy.vercel.app/?username=ryo-ma&theme=dark_dimmed
```

<p align="center">
  <img width="660" src="https://user-images.githubusercontent.com/31789752/147340893-655b9fa5-138f-4f29-91ec-2a17c93822d1.png">
</p>

### dark_lover

```
https://github-profile-trophy.vercel.app/?username=ryo-ma&theme=dark_lover
```

<p align="center">
  <img width="660" src="https://user-images.githubusercontent.com/31789752/152659041-de5b23cb-1be8-4e6b-b07b-726127ab8c3a.png">
</p>

### kimbie_dark

```
https://github-profile-trophy.vercel.app/?username=ryo-ma&theme=kimbie_dark
```

<p align="center">
  <img width="660" src="https://user-images.githubusercontent.com/8161064/288417332-408705a4-ae9c-47fe-af1a-9fb08555f526.png">
</p>

### aura

```
https://github-profile-trophy.vercel.app/?username=ryo-ma&theme=aura
```

<p align="center">
  <img width="660" src="https://github.com/user-attachments/assets/18a2266c-9a88-4882-940d-162c0c4d36e0">
</p>

## Margin Width

You can put a margin in the width between trophies.\
`Available value: number type`\
`Default: margin-w=0`

```
https://github-profile-trophy.vercel.app/?username=ryo-ma&margin-w=15
```

<p align="center">
  <img width="735" src="https://user-images.githubusercontent.com/6661165/93668661-e0ca9f00-fac8-11ea-9bec-325454f49fb4.png">
</p>

## Margin Height

You can put a margin in the height between trophies.\
`Available value: number type`\
`Default: margin-h=0`

```
https://github-profile-trophy.vercel.app/?username=ryo-ma&margin-h=15
```

<p align="center">
  <img width="110" height="330" src="https://github.com/user-attachments/assets/233dee5b-4491-46cc-884a-39d0aa928752">
</p>

## Example layout

```
https://github-profile-trophy.vercel.app/?username=ryo-ma&column=3&margin-w=15&margin-h=15
```

<p align="center">
  <img width="360" src="https://user-images.githubusercontent.com/6661165/93668677-ff309a80-fac8-11ea-8ae3-3e3e8adbef39.png">
</p>

## Transparent background

You can turn the background transparent.\
`Available value: boolean type (true or false)`\
`Default: no-bg=false`

```
https://github-profile-trophy.vercel.app/?username=ryo-ma&no-bg=true
```

<p align="center">
  <img width="969" src="https://github.com/user-attachments/assets/32d3b63b-7845-42cb-b71c-31abaa673bcb">
</p>

## Hide frames

You can hide the frames around the trophies.\
`Available value: boolean type (true or false)`\
`Default: no-frame=false`

```
https://github-profile-trophy.vercel.app/?username=ryo-ma&no-frame=true
```

<p align="center">
  <img width="936" src="https://github.com/user-attachments/assets/54de15a3-d907-4a50-8117-170aae74d1cd">
</p>

## Generate an svg file localy

Using the render_svg.ts script you can generate your trophys as an svg file
given your username, (Enviroment Vars: See [env-example](env-example)).

Usage:

```bash
deno run --allow-net --allow-env --allow-read --allow-write ./render_svg.ts USERNAME OUTPUT_DIR THEME
```

## Generate an svg inside Github CI (Workflow)

Using the provided github action you can easly generate the trophy inside an
github workflow. This eliminates the needs of an online service running but you
have to manualy update rerun the action to update the file.

Usage:

```yaml
- name: Generate trophy
  uses: Erik-Donath/github-profile-trophy@feature/generate-svg
  with:
    username: your-username
    output_path: trophy.svg
    token: ${{ secrets.GITHUB_TOKEN }}
```

# Contribution Guide

Check [CONTRIBUTING.md](./CONTRIBUTING.md) for more details.

# License

This product is licensed under the
[MIT License](https://github.com/ryo-ma/github-profile-trophy/blob/master/LICENSE).

# 🙏 Sponsors

Thank you so much to all the amazing sponsors who support this project! Your
contributions help keep development going and make this work possible.

## 💖 Monthly Sponsors

- [@Leay15](https://github.com/Leay15) - $10 / month
- [@hesreallyhim](https://github.com/hesreallyhim) - $10 / month
- [@pmsosa](https://github.com/pmsosa) — $10 / month
- [@chenfeng-huang](https://github.com/chenfeng-huang) — $10 / month
- [@holly-hacker](https://github.com/holly-hacker) — $2 / month
- [@skillerious](https://github.com/skillerious) — $2 / month (since Aug
  17, 2024)
- [@LudovicGardy](https://github.com/LudovicGardy) — $10 / month (since Aug
  15, 2024)
- [@alexcastrodev](https://github.com/alexcastrodev) — $10 / month (since Oct
  13, 2023, previously $50 / month)
- [@great-work-told-is](https://github.com/great-work-told-is) — $10 / month
  (since Apr 12, 2023)
- [@Ilithy](https://github.com/Ilithy) — $5 / month (since Jul 21, 2022)
- [@weakish](https://github.com/weakish) — $2 / month (since Jan 22, 2022)
- [@Kazuhito00](https://github.com/Kazuhito00) — $2 / month (since Jan 19, 2022)
- [@KATO-Hiro](https://github.com/KATO-Hiro) — $2 / month (since Jan 5, 2022)
- [@batazor](https://github.com/batazor) — $2 / month (since Oct 28, 2021)
- [@port19x](https://github.com/port19x) — $2 / month (since Jan 27, 2022)

## 🎁 One-Time Sponsors

- [@pronoym99](https://github.com/pronoym99) - $20 (Feb 8, 2026)
- [@hesreallyhim](https://github.com/hesreallyhim) - $100 (Jan 11, 2026)
- [@Seo-4d696b75](https://github.com/Seo-4d696b75) — $10 (Jan 4, 2026)
- [@massif-01](https://github.com/massif-01) — $5 (December 17, 2025)
- [@tapegram](https://github.com/tapegram) — $20 (December 6, 2025)
- [@WilliamCorotan](https://github.com/WilliamCorotan) — $5 (November 5, 2025)
- [@arnabnandy7](https://github.com/arnabnandy7) — $10 (Oct 3, 2025)
- [@JoqarSabon](https://github.com/JoqarSabon) — $5 (May 24, 2024)
- [@syaghoubi00](https://github.com/syaghoubi00) — $5 (Jan 28, 2024)
- [@pylapp](https://github.com/pylapp) — $20 (Jan 15, 2024)
- [@Dobefu](https://github.com/Dobefu) — $10 (Dec 22, 2024)
- [@michele-lorenzoni](https://github.com/michele-lorenzoni) — $10 (Nov
  26, 2024)
- [@skillerious](https://github.com/skillerious) — $10 (Aug 17, 2024)


================================================
FILE: action.yml
================================================
name: Generate GitHub Profile Trophy SVG
description: Run the local generator script to produce an SVG.
inputs:
  username:
    description: "GitHub username to generate the trophy for"
    required: true
  output_path:
    description: "Output path to write the SVG"
    required: true
    default: "trophy.svg"
  token:
    description: "PAT or token to use for GitHub API"
    required: true
  theme:
    description: "Theme for the card"
    required: false
    default: "default"
runs:
  using: "composite"
  steps:
    - name: Setup Deno
      uses: denoland/setup-deno@v1
      with:
        deno-version: v1.x

    - name: Generate trophy
      shell: bash
      env:
        GITHUB_TOKEN1: ${{ inputs.token }}
      run: |
        deno run --allow-net --allow-env --allow-read --allow-write $GITHUB_ACTION_PATH/render_svg.ts "${{ inputs.username }}" "${{ inputs.output_path }}" "${{ inputs.theme }}"


================================================
FILE: api/index.ts
================================================
import { Card } from "../src/card.ts";
import { CONSTANTS, parseParams } from "../src/utils.ts";
import { COLORS, Theme } from "../src/theme.ts";
import { Error400 } from "../src/error_page.ts";
import "https://deno.land/x/dotenv@v0.5.0/load.ts";
import { staticRenderRegeneration } from "../src/StaticRenderRegeneration/index.ts";
import { GithubRepositoryService } from "../src/Repository/GithubRepository.ts";
import { GithubApiService } from "../src/Services/GithubApiService.ts";
import { ServiceError } from "../src/Types/index.ts";
import { ErrorPage } from "../src/pages/Error.ts";
import { cacheProvider } from "../src/config/cache.ts";

const serviceProvider = new GithubApiService();
const client = new GithubRepositoryService(serviceProvider).repository;

// Build cache control header with optimized caching strategy
const cacheControlHeader = [
  "public",
  `max-age=${CONSTANTS.CACHE_MAX_AGE}`,
  `s-maxage=${CONSTANTS.CDN_CACHE_MAX_AGE}`,
  `stale-while-revalidate=${CONSTANTS.STALE_WHILE_REVALIDATE}`,
].join(", ");

const defaultHeaders = new Headers(
  {
    "Content-Type": "image/svg+xml",
    "Cache-Control": cacheControlHeader,
  },
);

export default (request: Request) =>
  staticRenderRegeneration(request, {
    revalidate: CONSTANTS.REVALIDATE_TIME,
    headers: defaultHeaders,
  }, function (req: Request) {
    return app(req);
  });

async function app(req: Request): Promise<Response> {
  const params = parseParams(req);
  const username = params.get("username");
  const row = params.getNumberValue("row", CONSTANTS.DEFAULT_MAX_ROW);
  const column = params.getNumberValue("column", CONSTANTS.DEFAULT_MAX_COLUMN);
  const themeParam: string = params.getStringValue("theme", "default");
  if (username === null) {
    const [base] = req.url.split("?");
    const error = new Error400(
      `<section>
      <div>
        <h2>"username" is a required query parameter</h2>
        <p>The URL should look like
        <div>
          <p id="base-show">${base}?username=USERNAME</p>
          <button>Copy Base Url</button>
          <span id="temporary-span"></span>
        </div>where
        <code>USERNAME</code> is <em>your GitHub username.</em>
      </div>
      <div>
        <h2>You can use this form: </h2>
        <p>Enter your username and click "Get Trophies"</p>
        <form action="${base}" method="get">
          <label for="username">GitHub Username</label>
          <input type="text" name="username" id="username" placeholder="Ex. gabriel-logan" required>
          <label for="theme">Theme (Optional)</label>
          <input type="text" name="theme" id="theme" placeholder="Ex. onedark" value="light">
          <text>
            See all the available themes
            <a href="https://github.com/ryo-ma/github-profile-trophy?tab=readme-ov-file#apply-theme" target="_blank">here</a>
          </text>
          <br>
          <button type="submit">Get Trophies</button>
        </form>
      </div>
      <script>
        const button = document.querySelector("button");
        const input = document.querySelector("input");
        const temporarySpan = document.querySelector("#temporary-span");

        button.addEventListener("click", () => {
          navigator.clipboard.writeText(document.querySelector("#base-show").textContent);
          temporarySpan.textContent = "Copied!";
          setTimeout(() => {
            temporarySpan.textContent = "";
          }, 1500);
        });
      </script>
    </section>`,
    );
    return new Response(
      error.render(),
      {
        status: error.status,
        headers: new Headers({
          "Content-Type": "text/html",
          "Cache-Control": cacheControlHeader,
        }),
      },
    );
  }
  let theme: Theme = COLORS.default;
  if (Object.keys(COLORS).includes(themeParam)) {
    theme = COLORS[themeParam];
  }
  const marginWidth = params.getNumberValue(
    "margin-w",
    CONSTANTS.DEFAULT_MARGIN_W,
  );
  const paddingHeight = params.getNumberValue(
    "margin-h",
    CONSTANTS.DEFAULT_MARGIN_H,
  );
  const noBackground = params.getBooleanValue(
    "no-bg",
    CONSTANTS.DEFAULT_NO_BACKGROUND,
  );
  const noFrame = params.getBooleanValue(
    "no-frame",
    CONSTANTS.DEFAULT_NO_FRAME,
  );
  const titles: Array<string> = params.getAll("title").flatMap((r) =>
    r.split(",")
  ).map((r) => r.trim());
  const ranks: Array<string> = params.getAll("rank").flatMap((r) =>
    r.split(",")
  ).map((r) => r.trim());

  const userKeyCache = ["v1", username].join("-");
  const userInfoCached = await cacheProvider.get(userKeyCache) || "{}";
  let userInfo = JSON.parse(userInfoCached);
  const hasCache = !!Object.keys(userInfo).length;

  if (!hasCache) {
    const userResponseInfo = await client.requestUserInfo(username);
    if (userResponseInfo instanceof ServiceError) {
      return new Response(
        ErrorPage({ error: userResponseInfo }).render(),
        {
          status: userResponseInfo.code,
          headers: new Headers({
            "Content-Type": "text/html",
            "Cache-Control": cacheControlHeader,
          }),
        },
      );
    }
    userInfo = userResponseInfo;
    await cacheProvider.set(userKeyCache, JSON.stringify(userInfo));
  }
  // Success Response
  return new Response(
    new Card(
      titles,
      ranks,
      column,
      row,
      CONSTANTS.DEFAULT_PANEL_SIZE,
      marginWidth,
      paddingHeight,
      noBackground,
      noFrame,
    ).render(userInfo, theme),
    {
      headers: defaultHeaders,
    },
  );
}


================================================
FILE: deno.json
================================================
{
  "tasks": {
    "start": "deno run -A main.ts",
    "debug": "deno run --inspect-brk -A main.ts",
    "format": "deno fmt",
    "lint": "deno lint",
    "test": "ENV_TYPE=test deno test --allow-env"
  }
}


================================================
FILE: deps.ts
================================================
import { Soxa as ServiceProvider } from "https://deno.land/x/soxa@1.4/src/core/Soxa.ts";
import { defaults } from "https://deno.land/x/soxa@1.4/src/defaults.ts";
import {
  assertEquals,
  assertRejects,
} from "https://deno.land/std@0.203.0/assert/mod.ts";
import {
  assertSpyCalls,
  returnsNext,
  spy,
  stub,
} from "https://deno.land/std@0.203.0/testing/mock.ts";

export {
  type Bulk,
  connect,
  type Redis,
} from "https://deno.land/x/redis@v0.31.0/mod.ts";

import { CONSTANTS } from "./src/utils.ts";

const baseURL = Deno.env.get("GITHUB_API") || CONSTANTS.DEFAULT_GITHUB_API;

const soxa = new ServiceProvider({
  ...defaults,
  baseURL,
});

export {
  assertEquals,
  assertRejects,
  assertSpyCalls,
  returnsNext,
  soxa,
  spy,
  stub,
};


================================================
FILE: docker-compose.yml
================================================
version: "3"
services:
  redis:
    container_name: trophy-redis
    image: redis:latest
    ports:
      - "6379:6379"
  deno-app:
    build: .
    volumes:
      - .:/app
    ports:
      - "80:8080"
    environment:
      - DENO_ENV=development
    command: ["deno", "run", "--watch", "-A", "main.ts"]


================================================
FILE: env-example
================================================
PORT=8080
GITHUB_TOKEN1=
GITHUB_TOKEN2=
GITHUB_API=https://api.github.com/graphql
ENABLE_REDIS=
REDIS_PORT=6379
REDIS_HOST=
REDIS_USERNAME=
REDIS_PASSWORD=


================================================
FILE: main.ts
================================================
import { serve } from "https://deno.land/std@0.125.0/http/server.ts";
import requestHandler from "./api/index.ts";

serve(requestHandler, { port: Number(Deno.env.get("PORT")) || 8080 });


================================================
FILE: render_svg.ts
================================================
import "https://deno.land/x/dotenv@v0.5.0/load.ts";

const username = Deno.args[0];
const outputPath = Deno.args[1] ?? "./assets/trophy.svg";
const themeName = Deno.args[2] ?? "default";

if (!username) {
  console.error(
    "Usage: deno run --allow-net --allow-env --allow-read --allow-write ./render_svg.ts USERNAME [OUTPUT_PATH] [THEME]",
  );
  Deno.exit(1);
}

import { GithubApiService } from "./src/Services/GithubApiService.ts";
import { Card } from "./src/card.ts";
import { COLORS } from "./src/theme.ts";

async function main() {
  console.log("Starting trophy render...");
  console.log("Username:", username);
  console.log("Output path:", outputPath);
  console.log("Theme:", themeName);

  const svc = new GithubApiService();

  const userInfoOrError = await svc.requestUserInfo(username);

  if (
    !(userInfoOrError && (userInfoOrError as any).totalCommits !== undefined)
  ) {
    console.error(
      "Failed to fetch user info. Check token, username and rate limits.",
    );
    Deno.exit(2);
  }

  const userInfo = userInfoOrError as any;

  const panelSize = 115;
  const maxRow = 10;
  const maxColumn = -1; // auto
  const marginWidth = 10;
  const marginHeight = 10;
  const noBackground = false;
  const noFrame = false;

  const card = new Card(
    [],
    [],
    maxColumn,
    maxRow,
    panelSize,
    marginWidth,
    marginHeight,
    noBackground,
    noFrame,
  );
  const theme = (COLORS as any)[themeName] ?? (COLORS as any).default;
  const svg = card.render(userInfo, theme);

  try {
    const dir = outputPath.replace(/\/[^/]+$/, "");
    if (dir) await Deno.mkdir(dir, { recursive: true });
  } catch {
    console.error("Failed to create directory. No permission?");
    Deno.exit(3);
  }

  await Deno.writeTextFile(outputPath, svg);
  console.log(`Wrote ${outputPath}`);
}

await main();


================================================
FILE: src/Helpers/Logger.ts
================================================
const enableLogging = Deno.env.get("ENV_TYPE") !== "test";

export class Logger {
  public static log(message: unknown): void {
    if (!enableLogging) return;
    console.log(message);
  }

  public static error(message: unknown): void {
    if (!enableLogging) return;

    console.error(message);
  }
  public static warn(message: unknown): void {
    if (!enableLogging) return;

    console.warn(message);
  }
}


================================================
FILE: src/Helpers/Retry.ts
================================================
import { ServiceError } from "../Types/index.ts";
import { Logger } from "./Logger.ts";

export type RetryCallbackProps = {
  attempt: number;
};

type callbackType<T = unknown> = (data: RetryCallbackProps) => Promise<T> | T;

async function* createAsyncIterable<T>(
  callback: callbackType<T>,
  retries: number,
  delay: number,
) {
  for (let i = 0; i < retries; i++) {
    const isLastAttempt = i === retries - 1;
    try {
      const data = await callback({ attempt: i });
      yield data;
      return;
    } catch (e) {
      if (e instanceof ServiceError && isLastAttempt) {
        yield e;
        return;
      }

      yield null;
      Logger.error(e);
      await new Promise((resolve) => setTimeout(resolve, delay));
    }
  }
}

export class Retry {
  constructor(private maxRetries = 2, private retryDelay = 1000) {}
  async fetch<T = unknown>(
    callback: callbackType<T>,
  ) {
    let lastError = null;
    for await (
      const callbackResult of createAsyncIterable<T>(
        callback,
        this.maxRetries,
        this.retryDelay,
      )
    ) {
      const isError = callbackResult instanceof Error;

      if (callbackResult && !isError) {
        return callbackResult as T;
      }

      if (isError) {
        lastError = callbackResult;
      }
    }

    throw new Error(`Max retries (${this.maxRetries}) exceeded.`, {
      cause: lastError,
    });
  }
}


================================================
FILE: src/Helpers/__tests__/Retry.test.ts
================================================
import { Retry } from "../Retry.ts";
import {
  assertEquals,
  assertRejects,
  assertSpyCalls,
  spy,
} from "../../../deps.ts";

type MockResponse = {
  value: number;
};

Deno.test("Retry.fetch", () => {
  const retryInstance = new Retry();
  const callback = spy(retryInstance, "fetch");

  retryInstance.fetch<MockResponse>(() => {
    return { value: 1 };
  });

  assertSpyCalls(callback, 1);
});

Deno.test("Should retry", async () => {
  let countErrors = 0;

  const callbackError = () => {
    countErrors++;
    throw new Error("Panic! Threw Error");
  };
  const retries = 3;
  const retryInstance = new Retry(retries);

  await assertRejects(
    () => {
      return retryInstance.fetch<MockResponse>(callbackError);
    },
    Error,
    `Max retries (${retries}) exceeded.`,
  );

  assertEquals(countErrors, 3);
});

Deno.test("Should retry the asyncronous callback", async () => {
  let countErrors = 0;
  const callbackError = async () => {
    countErrors++;
    // Mock request in callback
    await new Promise((_, reject) => setTimeout(reject, 100));
  };

  const retries = 3;
  const retryInstance = new Retry(retries);

  await assertRejects(
    () => {
      return retryInstance.fetch(callbackError);
    },
    Error,
    `Max retries (${retries}) exceeded.`,
  );

  assertEquals(countErrors, 3);
});


================================================
FILE: src/Repository/GithubRepository.ts
================================================
import { ServiceError } from "../Types/index.ts";
import {
  GitHubUserActivity,
  GitHubUserAll,
  GitHubUserIssue,
  GitHubUserPullRequest,
  GitHubUserRepository,
  UserInfo,
} from "../user_info.ts";

export abstract class GithubRepository {
  abstract requestUserInfo(username: string): Promise<UserInfo | ServiceError>;
  abstract requestUserAll(
    username: string,
  ): Promise<GitHubUserAll | ServiceError>;
  abstract requestUserActivity(
    username: string,
  ): Promise<GitHubUserActivity | ServiceError>;
  abstract requestUserIssue(
    username: string,
  ): Promise<GitHubUserIssue | ServiceError>;
  abstract requestUserPullRequest(
    username: string,
  ): Promise<GitHubUserPullRequest | ServiceError>;
  abstract requestUserRepository(
    username: string,
  ): Promise<GitHubUserRepository | ServiceError>;
}

export class GithubRepositoryService {
  constructor(public repository: GithubRepository) {}
}


================================================
FILE: src/Schemas/index.ts
================================================
export const queryUserActivity = `
    query userInfo($username: String!) {
      user(login: $username) {
        createdAt
        contributionsCollection {
          totalCommitContributions
          restrictedContributionsCount
          totalPullRequestReviewContributions
        }
        organizations(first: 1) {
          totalCount
        }
        followers(first: 1) {
          totalCount
        }
      }
    }
`;

export const queryUserIssue = `
  query userInfo($username: String!) {
    user(login: $username) {
      openIssues: issues(states: OPEN) {
        totalCount
      }
      closedIssues: issues(states: CLOSED) {
        totalCount
      }
    }
  }
`;

export const queryUserPullRequest = `
  query userInfo($username: String!) {
    user(login: $username) {
      pullRequests(first: 1) {
        totalCount
      }
    }
  }
`;

export const queryUserRepository = `
  query userInfo($username: String!) {
    user(login: $username) {
      repositories(first: 50, ownerAffiliations: OWNER, orderBy: {direction: DESC, field: STARGAZERS}) {
        totalCount
        nodes {
          languages(first: 2, orderBy: {direction:DESC, field: SIZE}) {
            nodes {
              name
            }
          }
          stargazers {
            totalCount
          }
          createdAt
        }
      }
    }
  }
`;

export const queryUserAll = `
  query userInfo($username: String!) {
    user(login: $username) {
      createdAt
      contributionsCollection {
        totalCommitContributions
        restrictedContributionsCount
        totalPullRequestReviewContributions
      }
      organizations(first: 1) {
        totalCount
      }
      followers(first: 1) {
        totalCount
      }
      openIssues: issues(states: OPEN) {
        totalCount
      }
      closedIssues: issues(states: CLOSED) {
        totalCount
      }
      pullRequests(first: 1) {
        totalCount
      }
      repositories(first: 50, ownerAffiliations: OWNER, orderBy: {direction: DESC, field: STARGAZERS}) {
        totalCount
        nodes {
          languages(first: 2, orderBy: {direction:DESC, field: SIZE}) {
            nodes {
              name
            }
          }
          stargazers {
            totalCount
          }
          createdAt
        }
      }
    }
  }
`;


================================================
FILE: src/Services/GithubApiService.ts
================================================
import { GithubRepository } from "../Repository/GithubRepository.ts";
import {
  GitHubUserActivity,
  GitHubUserAll,
  GitHubUserIssue,
  GitHubUserPullRequest,
  GitHubUserRepository,
  UserInfo,
} from "../user_info.ts";
import {
  queryUserActivity,
  queryUserAll,
  queryUserIssue,
  queryUserPullRequest,
  queryUserRepository,
} from "../Schemas/index.ts";
import { Retry } from "../Helpers/Retry.ts";
import { CONSTANTS } from "../utils.ts";
import { EServiceKindError, ServiceError } from "../Types/index.ts";
import { Logger } from "../Helpers/Logger.ts";
import { requestGithubData } from "./request.ts";

// Need to be here - Exporting from another file makes array of null
export const TOKENS = [
  Deno.env.get("GITHUB_TOKEN1"),
  Deno.env.get("GITHUB_TOKEN2"),
];

export class GithubApiService extends GithubRepository {
  async requestUserAll(
    username: string,
  ): Promise<GitHubUserAll | ServiceError> {
    return await this.executeQuery<GitHubUserAll>(queryUserAll, {
      username,
    });
  }
  async requestUserRepository(
    username: string,
  ): Promise<GitHubUserRepository | ServiceError> {
    return await this.executeQuery<GitHubUserRepository>(queryUserRepository, {
      username,
    });
  }
  async requestUserActivity(
    username: string,
  ): Promise<GitHubUserActivity | ServiceError> {
    return await this.executeQuery<GitHubUserActivity>(queryUserActivity, {
      username,
    });
  }
  async requestUserIssue(
    username: string,
  ): Promise<GitHubUserIssue | ServiceError> {
    return await this.executeQuery<GitHubUserIssue>(queryUserIssue, {
      username,
    });
  }
  async requestUserPullRequest(
    username: string,
  ): Promise<GitHubUserPullRequest | ServiceError> {
    return await this.executeQuery<GitHubUserPullRequest>(
      queryUserPullRequest,
      { username },
    );
  }
  async requestUserInfo(username: string): Promise<UserInfo | ServiceError> {
    // Use single combined query instead of 4 separate queries to reduce Function Duration
    try {
      const result = await this.requestUserAll(username);
      if (result instanceof ServiceError) {
        return result;
      }
      return UserInfo.fromCombined(result);
    } catch {
      Logger.error(`Error fetching user info for username: ${username}`);
      return new ServiceError("Not found", EServiceKindError.NOT_FOUND);
    }
  }

  async executeQuery<T = unknown>(
    query: string,
    variables: { [key: string]: string },
  ) {
    try {
      const retry = new Retry(
        TOKENS.length,
        CONSTANTS.DEFAULT_GITHUB_RETRY_DELAY,
      );
      return await retry.fetch<Promise<T>>(async ({ attempt }) => {
        return await requestGithubData(
          query,
          variables,
          TOKENS[attempt],
        );
      });
    } catch (error) {
      if (error.cause instanceof ServiceError) {
        Logger.error(error.cause.message);
        return error.cause;
      }
      if (error instanceof Error && error.cause) {
        Logger.error(JSON.stringify(error.cause, null, 2));
      } else {
        Logger.error(error);
      }
      return new ServiceError("not found", EServiceKindError.NOT_FOUND);
    }
  }
}


================================================
FILE: src/Services/__mocks__/notFoundUserMock.json
================================================
{
  "data": {
    "data": {
      "user": null
    },
    "errors": [
      {
        "type": "NOT_FOUND",
        "path": [
          "user"
        ],
        "locations": [
          {
            "line": 2,
            "column": 5
          }
        ],
        "message": "Could not resolve to a User with the login of 'alekinho'."
      }
    ]
  }
}


================================================
FILE: src/Services/__mocks__/rateLimitMock.json
================================================
{
  "exceeded": {
    "data": {
      "documentation_url": "https://docs.github.com/en/free-pro-team@latest/rest/overview/resources-in-the-rest-api#secondary-rate-limits",
      "message": "You have exceeded a secondary rate limit. Please wait a few minutes before you try again. If you reach out to GitHub Support for help, please include the request ID DBD8:FB98:31801A8:3222432:65195FDB."
    }
  },
  "rate_limit": {
    "data": {
      "data": {
        "user": null
      },
      "errors": [
        {
          "type": "RATE_LIMITED",
          "message": "API rate limit exceeded for user ID 10711649."
        }
      ]
    }
  }
}


================================================
FILE: src/Services/__mocks__/successGithubResponse.json
================================================
{
  "data": {
    "data": {
      "user": {
        "repositories": {
          "totalCount": 128,
          "nodes": [
            {
              "languages": {
                "nodes": [
                  {
                    "name": "JavaScript"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 23
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "JavaScript"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 11
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "TypeScript"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 9
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "JavaScript"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 6
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "JavaScript"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 6
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "Java"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 5
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "TypeScript"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 5
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "Jupyter Notebook"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 5
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "TypeScript"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 4
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "JavaScript"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 3
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "TypeScript"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 2
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "JavaScript"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 2
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "JavaScript"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 2
              }
            },
            {
              "languages": {
                "nodes": []
              },
              "stargazers": {
                "totalCount": 1
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "JavaScript"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 1
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "PHP"
                  },
                  {
                    "name": "Go"
                  },
                  {
                    "name": "HTML"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 1
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "TypeScript"
                  },
                  {
                    "name": "JavaScript"
                  },
                  {
                    "name": "CSS"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 1
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "TypeScript"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 1
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "TypeScript"
                  },
                  {
                    "name": "JavaScript"
                  },
                  {
                    "name": "Dockerfile"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 1
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "TypeScript"
                  },
                  {
                    "name": "JavaScript"
                  },
                  {
                    "name": "Dockerfile"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 1
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "Dart"
                  },
                  {
                    "name": "Swift"
                  },
                  {
                    "name": "Kotlin"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 1
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "HTML"
                  },
                  {
                    "name": "CSS"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 1
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "JavaScript"
                  },
                  {
                    "name": "Vue"
                  },
                  {
                    "name": "HTML"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 1
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "Jupyter Notebook"
                  },
                  {
                    "name": "Python"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 1
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "Dart"
                  },
                  {
                    "name": "HTML"
                  },
                  {
                    "name": "Swift"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 1
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "JavaScript"
                  },
                  {
                    "name": "Vue"
                  },
                  {
                    "name": "HTML"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 1
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "PHP"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 1
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "TypeScript"
                  },
                  {
                    "name": "CSS"
                  },
                  {
                    "name": "JavaScript"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "TypeScript"
                  },
                  {
                    "name": "HTML"
                  },
                  {
                    "name": "JavaScript"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "TypeScript"
                  },
                  {
                    "name": "CSS"
                  },
                  {
                    "name": "JavaScript"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": []
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": []
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "JavaScript"
                  },
                  {
                    "name": "Shell"
                  },
                  {
                    "name": "Dockerfile"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "JavaScript"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "TypeScript"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "CSS"
                  },
                  {
                    "name": "TypeScript"
                  },
                  {
                    "name": "JavaScript"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "TypeScript"
                  },
                  {
                    "name": "JavaScript"
                  },
                  {
                    "name": "Dockerfile"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": []
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "JavaScript"
                  },
                  {
                    "name": "TypeScript"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "TypeScript"
                  },
                  {
                    "name": "CSS"
                  },
                  {
                    "name": "JavaScript"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "JavaScript"
                  },
                  {
                    "name": "HTML"
                  },
                  {
                    "name": "C#"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "JavaScript"
                  },
                  {
                    "name": "HTML"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "TypeScript"
                  },
                  {
                    "name": "JavaScript"
                  },
                  {
                    "name": "CSS"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "PHP"
                  },
                  {
                    "name": "Vue"
                  },
                  {
                    "name": "Blade"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "CSS"
                  },
                  {
                    "name": "TypeScript"
                  },
                  {
                    "name": "JavaScript"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "C"
                  },
                  {
                    "name": "C++"
                  },
                  {
                    "name": "HTML"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "C++"
                  },
                  {
                    "name": "Makefile"
                  },
                  {
                    "name": "CMake"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "JavaScript"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "TypeScript"
                  },
                  {
                    "name": "JavaScript"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "C++"
                  },
                  {
                    "name": "Makefile"
                  },
                  {
                    "name": "CMake"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "JavaScript"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "JavaScript"
                  },
                  {
                    "name": "TypeScript"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "JavaScript"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "JavaScript"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "TypeScript"
                  },
                  {
                    "name": "CSS"
                  },
                  {
                    "name": "HTML"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "JavaScript"
                  },
                  {
                    "name": "TypeScript"
                  },
                  {
                    "name": "CSS"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "JavaScript"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "TypeScript"
                  },
                  {
                    "name": "JavaScript"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "Vue"
                  },
                  {
                    "name": "TypeScript"
                  },
                  {
                    "name": "CSS"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "TypeScript"
                  },
                  {
                    "name": "CSS"
                  },
                  {
                    "name": "HTML"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "TypeScript"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": []
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "JavaScript"
                  },
                  {
                    "name": "HTML"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "PHP"
                  },
                  {
                    "name": "JavaScript"
                  },
                  {
                    "name": "Blade"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "JavaScript"
                  },
                  {
                    "name": "Rust"
                  },
                  {
                    "name": "HTML"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "JavaScript"
                  },
                  {
                    "name": "Svelte"
                  },
                  {
                    "name": "HTML"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "CSS"
                  },
                  {
                    "name": "HTML"
                  },
                  {
                    "name": "JavaScript"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "JavaScript"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "TypeScript"
                  },
                  {
                    "name": "Dockerfile"
                  },
                  {
                    "name": "JavaScript"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "Rust"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "JavaScript"
                  },
                  {
                    "name": "Dockerfile"
                  },
                  {
                    "name": "Shell"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "TypeScript"
                  },
                  {
                    "name": "Shell"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "TypeScript"
                  },
                  {
                    "name": "JavaScript"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "HTML"
                  },
                  {
                    "name": "JavaScript"
                  },
                  {
                    "name": "CSS"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "TypeScript"
                  },
                  {
                    "name": "CSS"
                  },
                  {
                    "name": "HTML"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "Dart"
                  },
                  {
                    "name": "HTML"
                  },
                  {
                    "name": "Swift"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "Dart"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "Rust"
                  },
                  {
                    "name": "JavaScript"
                  },
                  {
                    "name": "HTML"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "JavaScript"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "JavaScript"
                  },
                  {
                    "name": "CSS"
                  },
                  {
                    "name": "Swift"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "TypeScript"
                  },
                  {
                    "name": "JavaScript"
                  },
                  {
                    "name": "SCSS"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "TypeScript"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "JavaScript"
                  },
                  {
                    "name": "Shell"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "JavaScript"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "TypeScript"
                  },
                  {
                    "name": "JavaScript"
                  },
                  {
                    "name": "SCSS"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "TypeScript"
                  },
                  {
                    "name": "HTML"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "TypeScript"
                  },
                  {
                    "name": "JavaScript"
                  },
                  {
                    "name": "HTML"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "JavaScript"
                  },
                  {
                    "name": "CSS"
                  },
                  {
                    "name": "HTML"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "TypeScript"
                  },
                  {
                    "name": "JavaScript"
                  },
                  {
                    "name": "CSS"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "TypeScript"
                  },
                  {
                    "name": "JavaScript"
                  },
                  {
                    "name": "HTML"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "TypeScript"
                  },
                  {
                    "name": "JavaScript"
                  },
                  {
                    "name": "HTML"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "TypeScript"
                  },
                  {
                    "name": "JavaScript"
                  },
                  {
                    "name": "HTML"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "TypeScript"
                  },
                  {
                    "name": "SCSS"
                  },
                  {
                    "name": "JavaScript"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "TypeScript"
                  },
                  {
                    "name": "JavaScript"
                  },
                  {
                    "name": "SCSS"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "JavaScript"
                  },
                  {
                    "name": "HTML"
                  },
                  {
                    "name": "CSS"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "JavaScript"
                  },
                  {
                    "name": "HTML"
                  },
                  {
                    "name": "CSS"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "JavaScript"
                  },
                  {
                    "name": "HTML"
                  },
                  {
                    "name": "SCSS"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "Swift"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "TypeScript"
                  },
                  {
                    "name": "HTML"
                  },
                  {
                    "name": "CSS"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            },
            {
              "languages": {
                "nodes": [
                  {
                    "name": "TypeScript"
                  },
                  {
                    "name": "HTML"
                  },
                  {
                    "name": "CSS"
                  }
                ]
              },
              "stargazers": {
                "totalCount": 0
              }
            }
          ]
        }
      }
    }
  }
}


================================================
FILE: src/Services/__tests__/githubApiService.test.ts
================================================
import { GithubApiService } from "../GithubApiService.ts";
import { assertEquals, returnsNext, soxa, stub } from "../../../deps.ts";
import { GitHubUserRepository } from "../../user_info.ts";

const rateLimitMock = await import("../__mocks__/rateLimitMock.json", {
  with: { type: "json" },
});

const successGithubResponseMock = await import(
  "../__mocks__/successGithubResponse.json",
  { with: { type: "json" } }
);

const notFoundGithubResponseMock = await import(
  "../__mocks__/notFoundUserMock.json",
  { with: { type: "json" } }
);

import { ServiceError } from "../../Types/index.ts";

// Unfortunatelly, The spy is a global instance
// We can't reset mock as Jest does.
stub(
  soxa,
  "post",
  returnsNext([
    // Should get data in first try
    new Promise((resolve) => {
      resolve(successGithubResponseMock.default);
    }),
    // Should throw NOT FOUND (requestUserInfo makes 1 combined API call)
    // Each call makes 2 attempts (one per token), so 2 promises total
    new Promise((resolve) => {
      resolve(notFoundGithubResponseMock.default);
    }),
    new Promise((resolve) => {
      resolve(notFoundGithubResponseMock.default);
    }),
    // Should throw NOT FOUND even if request the user only
    new Promise((resolve) => {
      resolve(notFoundGithubResponseMock.default);
    }),
    new Promise((resolve) => {
      resolve(notFoundGithubResponseMock.default);
    }),
    // Should throw RATE LIMIT
    new Promise((resolve) => {
      resolve(rateLimitMock.default.rate_limit);
    }),
    new Promise((resolve) => {
      resolve(rateLimitMock.default.rate_limit);
    }),
    // Should throw RATE LIMIT Exceed
    new Promise((resolve) => {
      resolve(rateLimitMock.default.rate_limit);
    }),
    new Promise((resolve) => {
      resolve(rateLimitMock.default.exceeded);
    }),
  ]),
);

Deno.test("Should get data in first try", async () => {
  const provider = new GithubApiService();

  const data = await provider.requestUserRepository(
    "test",
  ) as GitHubUserRepository;

  assertEquals(data.repositories.totalCount, 128);
});

//Deno.test("Should get data in second Retry", async () => {
//  const provider = new GithubApiService();
//
//  const data = await provider.requestUserRepository(
//    "test",
//  ) as GitHubUserRepository;
//
//  assertEquals(data.repositories.totalCount, 128);
//});

Deno.test("Should throw NOT FOUND", async () => {
  const provider = new GithubApiService();
  let error = null;

  try {
    error = await provider.requestUserInfo("test");
  } catch (e) {
    error = e;
  }

  assertEquals(error.code, 404);
  assertEquals(error instanceof ServiceError, true);
});
Deno.test("Should throw NOT FOUND even if request the user only", async () => {
  const provider = new GithubApiService();
  let error = null;

  try {
    error = await provider.requestUserRepository("test");
  } catch (e) {
    error = e;
  }

  assertEquals(error.code, 404);
  assertEquals(error instanceof ServiceError, true);
});

// The assertRejects() assertion is a little more complicated
// mainly because it deals with Promises.
// https://docs.deno.com/runtime/manual/basics/testing/assertions#throws
Deno.test("Should throw RATE LIMIT", async () => {
  const provider = new GithubApiService();
  let error = null;

  try {
    error = await provider.requestUserRepository("test");
  } catch (e) {
    error = e;
  }

  assertEquals(error.code, 419);
  assertEquals(error instanceof ServiceError, true);
});

Deno.test("Should throw RATE LIMIT Exceed", async () => {
  const provider = new GithubApiService();
  let error = null;

  try {
    error = await provider.requestUserRepository("test");
  } catch (e) {
    error = e;
  }

  assertEquals(error.code, 419);
  assertEquals(error instanceof ServiceError, true);
});


================================================
FILE: src/Services/request.ts
================================================
import { soxa } from "../../deps.ts";
import {
  EServiceKindError,
  GithubError,
  GithubErrorResponse,
  GithubExceedError,
  QueryDefaultResponse,
  ServiceError,
} from "../Types/index.ts";

export async function requestGithubData<T = unknown>(
  query: string,
  variables: { [key: string]: string },
  token = "",
) {
  const response = await soxa.post("", {}, {
    data: { query, variables },
    headers: {
      Authorization: `bearer ${token}`,
    },
  }) as QueryDefaultResponse<{ user: T }>;
  const responseData = response.data;

  if (responseData?.data?.user) {
    return responseData.data.user;
  }

  throw handleError(responseData);
}

function handleError(
  responseData: {
    data?: unknown;
    errors?: GithubError[];
    message?: string;
    documentation_url?: string;
  },
): ServiceError {
  let isRateLimitExceeded = false;
  const arrayErrors = responseData?.errors || [];

  if (Array.isArray(arrayErrors) && arrayErrors.length > 0) {
    isRateLimitExceeded = arrayErrors.some((error) =>
      error.type.includes(EServiceKindError.RATE_LIMIT)
    );
  }

  if (responseData?.message) {
    isRateLimitExceeded = responseData.message.toLowerCase().includes(
      "rate limit",
    );
  }

  if (isRateLimitExceeded) {
    throw new ServiceError(
      "Rate limit exceeded",
      EServiceKindError.RATE_LIMIT,
    );
  }

  throw new ServiceError(
    "unknown error",
    EServiceKindError.NOT_FOUND,
  );
}


================================================
FILE: src/StaticRenderRegeneration/cache_manager.ts
================================================
import { Logger } from "../Helpers/Logger.ts";
import { existsSync } from "./utils.ts";

export class CacheManager {
  constructor(private revalidateTime: number, private cacheFile: string) {}

  // Reason to use /tmp/:
  // https://github.com/orgs/vercel/discussions/314
  get cacheFilePath(): string {
    return `/tmp/${this.cacheFile}`;
  }
  get cacheFileExists(): boolean {
    return existsSync(this.cacheFilePath);
  }

  get cacheFileLastModified(): Date | null {
    if (!this.cacheFileExists) {
      return null;
    }
    const fileInfo = Deno.statSync(this.cacheFilePath);
    return fileInfo.mtime ?? null;
  }

  get cacheFileLastModifiedGetTime(): number | null {
    const lastModified = this.cacheFileLastModified;
    if (lastModified === null) {
      return null;
    }
    return lastModified.getTime();
  }

  get isCacheValid(): boolean {
    if (this.cacheFileLastModifiedGetTime === null) {
      return false;
    }
    const currentTime = new Date().getTime();
    return currentTime - this.cacheFileLastModifiedGetTime <
      this.revalidateTime;
  }

  async save(response: Response): Promise<void> {
    if (response === null) return;
    // Prevent TypeError: ReadableStream is locked
    const text = await response.clone().text();
    const data = new TextEncoder().encode(text);

    Deno.writeFile(this.cacheFilePath, data, { create: true }).catch(() => {
      Logger.warn("Failed to save cache file");
    });
  }
}


================================================
FILE: src/StaticRenderRegeneration/index.ts
================================================
import { CacheManager } from "./cache_manager.ts";
import { StaticRegenerationOptions } from "./types.ts";
import { getUrl, hashString, readCache } from "./utils.ts";

export async function staticRenderRegeneration(
  request: Request,
  options: StaticRegenerationOptions,
  render: (request: Request) => Promise<Response>,
) {
  // avoid TypeError: Invalid URL at deno:core
  const url = getUrl(request);

  // if more conditions are added, make sure to create a variable to skipCache
  if (url.pathname === "/favicon.ico") {
    return await render(request);
  }

  const cacheFile = await hashString(url.pathname + (url.search ?? ""));
  const cacheManager = new CacheManager(options.revalidate ?? 0, cacheFile);
  if (cacheManager.isCacheValid) {
    const cache = readCache(cacheManager.cacheFilePath);
    if (cache !== null) {
      return new Response(cache, {
        headers: options.headers ?? new Headers({}),
      });
    }
  }

  const response = await render(request);

  if (response.status >= 200 && response.status < 300) {
    void cacheManager.save(response);
  }

  return response;
}


================================================
FILE: src/StaticRenderRegeneration/types.ts
================================================
export interface StaticRegenerationOptions {
  // The number of milliseconds before the page should be revalidated
  revalidate?: number;
  // The headers to be sent with the response
  headers?: Headers;
}


================================================
FILE: src/StaticRenderRegeneration/utils.ts
================================================
export function getUrl(request: Request) {
  try {
    return new URL(request.url);
  } catch {
    return {
      pathname: request.url,
      search: request.url,
    };
  }
}

export function readCache(cacheFilePath: string): Uint8Array | null {
  try {
    return Deno.readFileSync(cacheFilePath);
  } catch {
    return null;
  }
}

// https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest
export async function hashString(message: string): Promise<string> {
  const encoder = new TextEncoder();
  const data = encoder.encode(message);
  const hashBuffer = await crypto.subtle.digest("SHA-256", data);

  const hashArray = Array.from(new Uint8Array(hashBuffer));
  const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join(
    "",
  );

  return hashHex;
}

export const existsSync = (filename: string): boolean => {
  try {
    Deno.statSync(filename);
    // successful, file or directory must exist
    return true;
  } catch {
    return false;
  }
};


================================================
FILE: src/Types/EServiceKindError.ts
================================================
export const enum EServiceKindError {
  RATE_LIMIT = "RATE_LIMITED",
  NOT_FOUND = "NOT_FOUND",
}


================================================
FILE: src/Types/Request.ts
================================================
export type GithubError = {
  message: string;
  type: string;
};

export type GithubErrorResponse = {
  errors: GithubError[];
};

export type GithubExceedError = {
  documentation_url: string;
  message: string;
};

export type QueryDefaultResponse<T = unknown> = {
  data: {
    data: T;
    errors?: GithubError[];
    message?: string;
    documentation_url?: string;
  };
};


================================================
FILE: src/Types/ServiceError.ts
================================================
import { EServiceKindError } from "./EServiceKindError.ts";

export class ServiceError extends Error {
  constructor(message: string, kind: EServiceKindError) {
    super(message);
    this.message = message;
    this.name = "ServiceError";
    this.cause = kind;
  }

  get code(): number {
    switch (this.cause) {
      case EServiceKindError.RATE_LIMIT:
        return 419;
      case EServiceKindError.NOT_FOUND:
        return 404;
      default:
        return 400;
    }
  }
}


================================================
FILE: src/Types/index.ts
================================================
export * from "./Request.ts";
export * from "./ServiceError.ts";
export * from "./EServiceKindError.ts";


================================================
FILE: src/card.ts
================================================
import { UserInfo } from "./user_info.ts";
import { TrophyList } from "./trophy_list.ts";
import { Trophy } from "./trophy.ts";
import { Theme } from "./theme.ts";

export class Card {
  private width = 0;
  private height = 0;
  constructor(
    private titles: Array<string>,
    private ranks: Array<string>,
    private maxColumn: number,
    private maxRow: number,
    private panelSize: number,
    private marginWidth: number,
    private marginHeight: number,
    private noBackground: boolean,
    private noFrame: boolean,
  ) {
    this.width = panelSize * this.maxColumn +
      this.marginWidth * (this.maxColumn - 1);
  }
  render(
    userInfo: UserInfo,
    theme: Theme,
  ): string {
    const trophyList = new TrophyList(userInfo);

    trophyList.filterByHidden();

    if (this.titles.length != 0) {
      const includeTitles = this.titles.filter((title) =>
        !title.startsWith("-")
      );
      if (includeTitles.length > 0) {
        trophyList.filterByTitles(includeTitles);
      }
      trophyList.filterByExclusionTitles(this.titles);
    }

    if (this.ranks.length != 0) {
      trophyList.filterByRanks(this.ranks);
    }

    trophyList.sortByRank();

    if (this.maxColumn == -1) {
      this.maxColumn = trophyList.length;
      this.width = this.panelSize * this.maxColumn +
        this.marginWidth * (this.maxColumn - 1);
    }

    const row = this.getRow(trophyList);
    this.height = this.getHeight(row);

    return `
    <svg
      width="${this.width}"
      height="${this.height}"
      viewBox="0 0 ${this.width} ${this.height}"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
    >
      ${this.renderTrophy(trophyList, theme)}
    </svg>`;
  }
  private getRow(trophyList: TrophyList) {
    let row = Math.floor((trophyList.length - 1) / this.maxColumn) + 1;
    if (row > this.maxRow) {
      row = this.maxRow;
    }
    return row;
  }
  private getHeight(row: number) {
    // Calculate the height of the card from turns
    return this.panelSize * row + this.marginHeight * (row - 1);
  }

  private renderTrophy(trophyList: TrophyList, theme: Theme) {
    return trophyList.getArray.reduce(
      (sum: string, trophy: Trophy, i: number) => {
        const currentColumn = i % this.maxColumn;
        const currentRow = Math.floor(i / this.maxColumn);
        const x = this.panelSize * currentColumn +
          this.marginWidth * currentColumn;
        const y = this.panelSize * currentRow + this.marginHeight * currentRow;
        return sum +
          trophy.render(
            theme,
            x,
            y,
            this.panelSize,
            this.noBackground,
            this.noFrame,
          );
      },
      "",
    );
  }
}


================================================
FILE: src/config/cache.ts
================================================
import { Bulk, connect, Redis } from "../../deps.ts";
import { Logger } from "../Helpers/Logger.ts";
import { CONSTANTS } from "../utils.ts";

const enableCache: boolean = Deno.env.get("ENABLE_REDIS") === "true";

// https://developer.redis.com/develop/deno/
class CacheProvider {
  private static instance: CacheProvider;
  public client: Redis | null = null;

  private constructor() {}

  static getInstance(): CacheProvider {
    if (!CacheProvider.instance) {
      CacheProvider.instance = new CacheProvider();
    }
    return CacheProvider.instance;
  }

  async connect(): Promise<void> {
    if (!enableCache) return;
    this.client = await connect({
      hostname: Deno.env.get("REDIS_HOST") || "",
      port: Number(Deno.env.get("REDIS_PORT")) || 6379,
      username: Deno.env.get("REDIS_USERNAME") || undefined,
      password: Deno.env.get("REDIS_PASSWORD") || undefined,
    });
  }

  async get(key: string): Promise<Bulk | undefined> {
    if (!enableCache) return undefined;

    try {
      if (!this.client) {
        await this.connect();
      }

      return await this.client?.get(key);
    } catch {
      return undefined;
    }
  }

  async set(key: string, value: string): Promise<void> {
    if (!enableCache) return;

    try {
      if (!this.client) {
        await this.connect();
      }
      await this.client?.set(key, value, {
        px: CONSTANTS.REDIS_TTL,
      });
    } catch (e) {
      Logger.error(`Failed to set cache: ${e.message}`);
    }
  }

  async del(key: string): Promise<void> {
    if (!enableCache) return;

    try {
      if (!this.client) {
        await this.connect();
      }
      await this.client?.del(key);
    } catch (e) {
      Logger.error(`Failed to delete cache: ${e.message}`);
    }
  }
}

export const cacheProvider = CacheProvider.getInstance();


================================================
FILE: src/error_page.ts
================================================
abstract class BaseError {
  readonly status!: number;
  readonly message!: string;
  constructor(readonly content?: string) {}
  render() {
    return this.renderPage();
  }

  private renderPage() {
    return `<!DOCTYPE html>
    <html lang="en"><head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>GitHub Profile Trophy</title>
      <meta name="description" content="🏆 Add dynamically generated GitHub Stat Trophies on your readme">
      <style>
        body {
          font-family: Arial, sans-serif;
          margin: 0;
          padding: 0;
          background-color: #f4f4f4;
        }
        h1,
        h2 {
          color: #333;
        }
        p {
          color: #666;
        }
        #back-link {
          display: flex;
          justify-content: center;
          text-decoration: none;
        }
        #back-link:hover {
          text-decoration: underline;
        }
        section {
          width: 80%;
          margin: 0 auto;
          padding: 20px;
        }
        div {
          background-color: #fff;
          border-radius: 5px;
          padding: 20px;
          margin-bottom: 20px;
          box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1);
        }
        form {
          display: flex;
          flex-direction: column;
        }
        label {
          margin-bottom: 10px;
        }
        input {
          padding: 12px;
          margin-bottom: 20px;
          border-radius: 5px;
          border: 1px solid #ddd;
        }
        button {
          padding: 10px 20px;
          background-color: #333;
          color: #fff;
          border: none;
          border-radius: 5px;
          cursor: pointer;
        }
        #base-show {
          font-size: 16px;
          color: #333;
          background-color: #f4f4f4;
          padding: 10px;
          border-radius: 5px;
          text-align: center;
          margin: 10px 0;
        }
        button:hover {
          background-color: #444;
        }
        @media (max-width: 768px) {
          #base-show {
            font-size: 14px;
          }
        }
        @media (max-width: 480px) {
          #base-show {
            font-size: 8px;
          }
        }
        @media (min-width: 768px) {
          section {
            width: 60%;
          }
        }
        @media (min-width: 1024px) {
          section {
            width: 50%;
          }
        }
      </style>
    </head>
    <body>
      <h1 style="text-align: center;">${this.status} - ${this.message}</h1>
      <p style="text-align: center;">${this.content ?? ""}</p>
      ${
      this.content &&
      '<a id="back-link" href="/">Go back</a>'
    }
    </body>
    </html>`;
  }
}

export class Error400 extends BaseError {
  readonly status = 400;
  readonly message = "Bad Request";
}

export class Error419 extends BaseError {
  readonly status = 419;
  readonly message = "Rate Limit Exceeded";
}

export class Error404 extends BaseError {
  readonly status = 404;
  readonly message = "Not Found";
}


================================================
FILE: src/icons.ts
================================================
import { RANK } from "./utils.ts";
import { Theme } from "./theme.ts";

const leafIcon = (laurel: string): string => {
  return `<svg xmlns="http://www.w3.org/2000/svg" width="90pt" height="90pt" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.15, written by Peter Selinger 2001-2017
</metadata>
<g transform="translate(20.000000,60.000000) scale(0.00400000,-0.00400000)" fill="${laurel}" stroke="none">
<path d="M200 5103 c0 -2 18 -40 41 -84 47 -95 62 -132 50 -125 -15 10 -18 -39 -6 -87 31 -121 265 -468 412 -608 124 -119 281 -222 383 -251 36 -10 49 -16 30 -13 -19 3 -78 12 -130 20 -117 17 -353 35 -477 35 l-93 0 53 -82 c72 -112 72 -112 59 -104 -18 11 -26 -41 -13 -84 25 -84 261 -376 404 -502 95 -83 222 -168 304 -205 98 -43 194 -73 242 -74 l46 -1 -45 -8 c-25 -5 -124 -21 -220 -36 -96 -15 -177 -29 -180 -31 -2 -2 0 -7 5 -11 27 -19 138 -144 123 -139 -18 6 -28 -10 -28 -47 0 -38 53 -108 141 -187 349 -313 631 -450 939 -453 63 0 131 2 150 7 19 4 -35 -17 -120 -46 -236 -82 -310 -110 -310 -117 0 -3 29 -28 65 -54 55 -40 102 -84 67 -62 -13 8 -32 -24 -32 -54 0 -99 486 -361 790 -426 125 -27 327 -25 444 4 113 28 261 98 309 145 39 40 56 92 38 124 -8 17 -4 24 29 49 22 16 40 32 40 36 0 4 -26 40 -58 80 -162 203 -368 328 -608 369 -89 15 -368 6 -474 -15 -131 -26 -147 -26 -59 -3 51 13 102 34 122 50 38 29 61 84 51 123 -5 18 1 26 25 39 17 8 31 19 31 23 0 4 -16 38 -35 75 -163 317 -424 501 -781 548 -113 15 -127 19 -91 30 51 14 84 58 89 118 1 20 9 33 20 37 23 7 23 6 -12 114 -108 329 -305 534 -640 662 -41 15 -59 25 -40 21 19 -5 82 -8 140 -8 81 0 113 4 142 18 39 20 73 76 65 107 -3 12 2 20 14 23 23 6 23 21 4 124 -61 320 -249 568 -544 718 -157 79 -394 147 -666 190 -88 13 -170 26 -182 29 -13 2 -23 2 -23 -1z"/>
<path d="M12550 5099 c-232 -36 -334 -55 -445 -84 -484 -122 -761 -346 -880 -712 -26 -79 -57 -242 -48 -255 2 -5 14 -8 26 -9 12 0 16 -3 10 -6 -17 -6 -16 -38 2 -72 25 -49 75 -66 200 -66 61 0 124 4 140 8 17 5 -13 -9 -66 -31 -136 -55 -250 -126 -341 -211 -128 -120 -217 -263 -272 -439 -32 -101 -32 -110 -3 -122 12 -5 17 -9 11 -9 -8 -1 -9 -12 -5 -34 15 -66 41 -95 94 -107 31 -7 31 -7 7 -12 -14 -3 -72 -13 -130 -22 -322 -51 -553 -206 -714 -479 -25 -42 -52 -92 -60 -111 -14 -33 -14 -33 23 -54 20 -12 31 -22 26 -22 -15 0 -17 -39 -4 -78 14 -42 76 -88 130 -97 22 -4 39 -9 36 -11 -2 -2 -55 3 -118 12 -154 22 -395 15 -494 -13 -216 -62 -391 -184 -545 -380 l-41 -52 40 -32 c34 -27 39 -35 30 -51 -17 -33 -1 -85 38 -125 48 -47 196 -117 309 -145 117 -29 319 -31 444 -4 300 64 790 328 790 425 0 29 -18 63 -31 56 -5 -4 -9 -4 -9 -2 0 3 34 29 75 59 41 29 75 56 75 59 0 3 -21 13 -47 22 -349 120 -422 146 -388 140 59 -12 241 -8 310 6 208 43 437 158 636 322 241 199 314 293 265 342 -13 12 -6 24 51 86 36 40 64 73 62 75 -4 2 -107 20 -359 60 -70 12 -77 14 -39 15 48 1 144 31 241 74 139 62 318 202 451 352 104 117 225 279 249 333 21 47 21 99 0 94 -16 -3 -10 9 58 116 l50 77 -72 3 c-91 4 -362 -14 -488 -33 -179 -26 -179 -26 -116 -9 93 26 244 120 365 230 193 174 467 605 443 696 -2 10 -8 15 -13 12 -12 -7 3 30 51 126 23 45 40 85 38 89 -2 4 -23 4 -48 0z m-933 -1185 c-3 -3 -12 -4 -19 -1 -8 3 -5 6 6 6 11 1 17 -2 13 -5z m-1290 -1860 c-3 -3 -12 -4 -19 -1 -8 3 -5 6 6 6 11 1 17 -2 13 -5z m40 -10 c-3 -3 -12 -4 -19 -1 -8 3 -5 6 6 6 11 1 17 -2 13 -5z"/>
<path d="M10242 4632 c-46 -140 -92 -319 -118 -457 -18 -94 -28 -519 -12 -509 4 3 5 -8 2 -25 -8 -40 21 -179 58 -274 75 -195 297 -437 400 -437 57 0 124 70 177 185 53 112 67 224 65 510 -1 120 -5 166 -23 233 -66 252 -206 515 -423 795 -39 51 -74 95 -77 99 -4 4 -26 -50 -49 -120z"/>
<path d="M2407 4612 c-305 -408 -443 -757 -422 -1067 3 -55 8 -140 10 -190 5 -113 30 -204 83 -293 44 -74 88 -118 128 -128 102 -26 339 218 422 433 34 87 67 235 58 258 -3 9 -1 39 5 68 5 28 10 117 10 197 1 155 -15 284 -57 455 -33 137 -117 405 -126 405 -5 0 -55 -62 -111 -138z"/>
<path d="M2970 3839 c-189 -385 -254 -632 -248 -941 2 -86 6 -151 10 -145 5 7 5 -2 2 -19 -19 -98 77 -354 181 -487 76 -96 141 -120 210 -77 70 43 195 240 239 375 20 63 46 197 40 207 -3 4 0 54 6 111 26 257 -51 553 -240 922 -42 83 -87 165 -98 184 l-21 35 -81 -165z"/>
<path d="M9711 3929 c-124 -219 -230 -466 -276 -642 -39 -151 -47 -234 -41 -426 7 -237 39 -357 136 -517 136 -225 233 -251 355 -97 94 121 171 313 181 453 2 36 8 111 13 167 26 288 -56 600 -266 1017 l-60 120 -42 -75z"/>
<path d="M3645 3278 c-2 -7 -14 -78 -27 -158 -19 -123 -22 -188 -23 -430 -2 -309 7 -401 50 -555 66 -232 204 -430 388 -552 120 -80 189 -70 245 34 36 69 74 205 78 283 1 30 5 109 9 175 9 135 -1 213 -41 339 -66 208 -198 406 -429 645 -147 152 -244 237 -250 219z"/>
<path d="M8994 3143 c-289 -284 -435 -492 -514 -732 -32 -100 -55 -261 -45 -330 3 -25 8 -91 10 -146 7 -168 66 -347 129 -387 77 -48 196 11 347 170 94 98 156 199 205 331 37 99 67 234 59 262 -4 11 -2 19 5 19 8 0 10 8 7 21 -3 11 1 77 10 147 10 89 13 172 9 277 -7 191 -48 515 -65 515 -3 0 -74 -66 -157 -147z"/>
<path d="M4501 2358 c52 -129 69 -179 59 -173 -6 4 -10 -12 -10 -42 0 -78 115 -313 252 -514 215 -317 529 -509 832 -509 124 0 166 27 180 112 1 11 8 23 15 27 10 7 11 27 2 98 -66 545 -401 836 -1164 1012 -97 23 -179 41 -182 41 -3 0 5 -24 16 -52z"/>
<path d="M8159 2375 c-609 -138 -940 -344 -1096 -683 -59 -127 -110 -377 -88 -429 5 -10 10 -29 12 -42 13 -75 59 -101 179 -101 316 0 625 196 854 543 110 165 222 395 228 465 3 40 2 53 -7 48 -11 -7 -3 15 53 153 28 68 31 81 19 80 -5 0 -74 -16 -154 -34z"/>
<path d="M4032 1479 c-193 -25 -435 -124 -667 -274 -108 -69 -314 -218 -315 -226 0 -4 28 -16 63 -29 66 -24 92 -40 65 -40 -10 0 -19 -12 -23 -30 -14 -65 45 -105 226 -154 572 -155 982 -93 1270 194 75 74 101 131 81 174 -9 21 -6 31 29 77 21 29 39 55 39 58 0 3 -25 24 -55 46 -229 167 -469 236 -713 204z"/>
<path d="M8513 1476 c-155 -30 -317 -101 -446 -196 l-67 -50 40 -54 c37 -49 40 -56 29 -80 -21 -46 4 -101 80 -176 288 -287 698 -349 1270 -194 180 49 240 89 226 153 -3 16 -14 31 -23 33 -9 2 18 18 61 35 l79 30 -126 92 c-304 223 -550 347 -780 395 -113 23 -257 28 -343 12z"/>
<path d="M6324 1249 c-48 -14 -120 -83 -139 -134 -13 -34 -16 -60 -11 -112 4 -37 6 -84 6 -103 -2 -95 62 -193 145 -220 138 -46 285 52 292 195 1 28 8 71 14 97 10 37 10 60 0 101 -31 139 -167 217 -307 176z"/>
<path d="M5255 1054 c-276 -46 -587 -227 -935 -541 l-54 -50 74 -23 c41 -13 67 -25 58 -27 -23 -6 -34 -48 -20 -78 16 -35 75 -61 188 -84 568 -115 968 -37 1250 243 69 69 110 130 114 171 5 42 0 66 -12 59 -12 -8 -12 -7 39 73 18 29 33 56 33 62 0 15 -49 46 -147 95 -207 104 -386 135 -588 100z"/>
<path d="M7249 1054 c-42 -7 -109 -25 -150 -40 -76 -27 -226 -101 -267 -133 l-24 -18 41 -66 c22 -37 36 -67 31 -67 -18 0 -11 -80 11 -121 11 -22 53 -73 92 -113 279 -282 682 -361 1251 -245 113 23 172 49 188 84 14 30 3 72 -20 77 -9 3 18 15 61 29 l78 24 -93 81 c-362 313 -622 460 -902 509 -106 18 -194 18 -297 -1z"/>
</g>
</svg>`;
};

export const getNextRankBar = (
  title: string,
  percentage: number,
  color: string,
): string => {
  const maxWidth = 80;
  return `
    <style>
    @keyframes ${title}RankAnimation {
      from {
        width: 0px;
      }
      to {
        width: ${maxWidth * percentage}px;
      }
    }
    #${title}-rank-progress{
      animation: ${title}RankAnimation 1s forwards ease-in-out;
    }
    </style>
    <rect
      x="15"
      y="101"
      rx="1"
      width="${maxWidth}"
      height="3.2"
      opacity="0.3"
      fill="${color}"
    />
    <rect
      id="${title}-rank-progress"
      x="15"
      y="101"
      rx="1"
      height="3.2"
      fill="${color}"
    />
  `;
};

const getSmallTrophyIcon = (
  icon: string,
  color: string,
  count: number,
): string => {
  const leftXPosition = 7;
  const rightXPosition = 68;
  const getIcon = (x: number) => {
    return `<svg x="${x}" y="35" width="65" height="65" viewBox="0 0 30 30" fill="${color}" xmlns="http://www.w3.org/2000/svg">
      ${icon}
    </svg>`;
  };
  if (count == 1) {
    // Double Rank
    return getIcon(rightXPosition);
  } else if (count == 2) {
    // Triple Rank
    return `${getIcon(leftXPosition)}${getIcon(rightXPosition)}`;
  }
  // Single Rank
  return "";
};
export const getTrophyIcon = (theme: Theme, rank = RANK.UNKNOWN) => {
  let color = theme.DEFAULT_RANK_BASE;
  let rankColor = theme.DEFAULT_RANK_TEXT;
  let backgroundIcon = "";
  let gradationColor = `
      <stop offset="0%" stop-color="${theme.DEFAULT_RANK_BASE}"/>
      <stop offset="50%" stop-color="${theme.DEFAULT_RANK_BASE}"/>
      <stop offset="100%" stop-color="${theme.DEFAULT_RANK_SHADOW}"/>
  `;
  const { ICON_CIRCLE } = theme;
  if (rank === RANK.SECRET) {
    rankColor = theme.SECRET_RANK_TEXT;
    gradationColor = `
    <stop offset="0%" stop-color="${theme.SECRET_RANK_1}"/>
    <stop offset="50%" stop-color="${theme.SECRET_RANK_2}"/>
    <stop offset="100%" stop-color="${theme.SECRET_RANK_3}"/>
    `;
  } else if (rank.slice(0, 1) === RANK.S) {
    color = theme.S_RANK_BASE;
    rankColor = theme.S_RANK_TEXT;
    backgroundIcon = leafIcon(theme.LAUREL);
    gradationColor = `
    <stop offset="0%" stop-color="${color}"/>
    <stop offset="70%" stop-color="${color}"/>
    <stop offset="100%" stop-color="${theme.S_RANK_SHADOW}"/>
    `;
  } else if (rank.slice(0, 1) === RANK.A) {
    color = theme.A_RANK_BASE;
    rankColor = theme.A_RANK_TEXT;
    backgroundIcon = leafIcon(theme.LAUREL);
    gradationColor = `
    <stop offset="0%" stop-color="${color}"/>
    <stop offset="70%" stop-color="${color}"/>
    <stop offset="100%" stop-color="${theme.A_RANK_SHADOW}"/>
    `;
  } else if (rank === RANK.B) {
    color = theme.B_RANK_BASE;
    rankColor = theme.B_RANK_TEXT;
    gradationColor = `
    <stop offset="0%" stop-color="${color}"/>
    <stop offset="70%" stop-color="${color}"/>
    <stop offset="100%" stop-color="${theme.B_RANK_SHADOW}"/>
    `;
  }
  const icon = `
    <path d="M7 10h2v4H7v-4z"/>
    <path d="M10 11c0 .552-.895 1-2 1s-2-.448-2-1 .895-1 2-1 2 .448 2 1z"/>
    <path fill-rule="evenodd" d="M12.5 3a2 2 0 1 0 0 4 2 2 0 0 0 0-4zm-3 2a3 3 0 1 1 6 0 3 3 0 0 1-6 0zm-6-2a2 2 0 1 0 0 4 2 2 0 0 0 0-4zm-3 2a3 3 0 1 1 6 0 3 3 0 0 1-6 0z"/>
    <path d="M3 1h10c-.495 3.467-.5 10-5 10S3.495 4.467 3 1zm0 15a1 1 0 0 1 1-1h8a1 1 0 0 1 1 1H3zm2-1a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1H5z"/>
    <circle cx="8" cy="6" r="4" fill="${ICON_CIRCLE}" />
    <text x="6" y="8" font-family="Courier, Monospace" font-size="7" fill="${rankColor}">${
    rank.slice(0, 1)
  }</text>
  `;
  const optionRankIcon = getSmallTrophyIcon(icon, color, rank.length - 1);
  return `
  ${backgroundIcon}
  ${optionRankIcon}
  <defs>
    <linearGradient id="${rank}" gradientTransform="rotate(45)">
    ${gradationColor}
    </linearGradient>
  </defs>
  <svg x="28" y="20" width="100" height="100" viewBox="0 0 30 30" fill="url(#${rank})" xmlns="http://www.w3.org/2000/svg">
    ${icon}
  </svg>
  `;
};


================================================
FILE: src/pages/Error.ts
================================================
import { EServiceKindError, ServiceError } from "../Types/index.ts";
import { Error400, Error404, Error419 } from "../error_page.ts";

interface ErrorPageProps {
  error: ServiceError;
}

export function ErrorPage({ error }: ErrorPageProps) {
  let cause: Error400 | Error404 | Error419 = new Error400();

  if (error.cause === EServiceKindError.RATE_LIMIT) {
    cause = new Error419();
  }

  if (error.cause === EServiceKindError.NOT_FOUND) {
    cause = new Error404(
      "Sorry, the user you are looking for was not found.",
    );
  }

  return cause;
}


================================================
FILE: src/theme.ts
================================================
export const COLORS: { [name: string]: Theme } = {
  default: {
    BACKGROUND: "#FFF",
    TITLE: "#000",
    ICON_CIRCLE: "#FFF",
    TEXT: "#666",
    LAUREL: "#009366",
    SECRET_RANK_1: "red",
    SECRET_RANK_2: "fuchsia",
    SECRET_RANK_3: "blue",
    SECRET_RANK_TEXT: "fuchsia",
    NEXT_RANK_BAR: "#0366d6",
    S_RANK_BASE: "#FAD200",
    S_RANK_SHADOW: "#C8A090",
    S_RANK_TEXT: "#886000",
    A_RANK_BASE: "#B0B0B0",
    A_RANK_SHADOW: "#9090C0",
    A_RANK_TEXT: "#505050",
    B_RANK_BASE: "#A18D66",
    B_RANK_SHADOW: "#816D96",
    B_RANK_TEXT: "#412D06",
    DEFAULT_RANK_BASE: "#777",
    DEFAULT_RANK_SHADOW: "#333",
    DEFAULT_RANK_TEXT: "#333",
  },
  dracula: {
    BACKGROUND: "#282a36",
    TITLE: "#ff79c6",
    ICON_CIRCLE: "#f8f8f2",
    TEXT: "#f8f8f2",
    LAUREL: "#50fa7b",
    SECRET_RANK_1: "#ff5555",
    SECRET_RANK_2: "#ff79c6",
    SECRET_RANK_3: "#bd93f9",
    SECRET_RANK_TEXT: "#bd93f9",
    NEXT_RANK_BAR: "#ff79c6",
    S_RANK_BASE: "#ffb86c",
    S_RANK_SHADOW: "#ffb86c",
    S_RANK_TEXT: "#6272a4",
    A_RANK_BASE: "#8be9fd",
    A_RANK_SHADOW: "#8be9fd",
    A_RANK_TEXT: "#6272a4",
    B_RANK_BASE: "#ff5555",
    B_RANK_SHADOW: "#ff5555",
    B_RANK_TEXT: "#6272a4",
    DEFAULT_RANK_BASE: "#6272a4",
    DEFAULT_RANK_SHADOW: "#6272a4",
    DEFAULT_RANK_TEXT: "#6272a4",
  },
  flat: {
    BACKGROUND: "#FFF",
    TITLE: "#000",
    ICON_CIRCLE: "#FFF",
    TEXT: "#666",
    LAUREL: "#009366",
    SECRET_RANK_1: "red",
    SECRET_RANK_2: "fuchsia",
    SECRET_RANK_3: "blue",
    SECRET_RANK_TEXT: "fuchsia",
    NEXT_RANK_BAR: "#0366d6",
    S_RANK_BASE: "#eac200",
    S_RANK_SHADOW: "#eac200",
    S_RANK_TEXT: "#886000",
    A_RANK_BASE: "#B0B0B0",
    A_RANK_SHADOW: "#B0B0B0",
    A_RANK_TEXT: "#505050",
    B_RANK_BASE: "#A18D66",
    B_RANK_SHADOW: "#A18D66",
    B_RANK_TEXT: "#412D06",
    DEFAULT_RANK_BASE: "#777",
    DEFAULT_RANK_SHADOW: "#777",
    DEFAULT_RANK_TEXT: "#333",
  },
  onedark: {
    BACKGROUND: "#282c34",
    TITLE: "#e5c07b",
    ICON_CIRCLE: "#FFF",
    TEXT: "#e06c75",
    LAUREL: "#98c379",
    SECRET_RANK_1: "#e06c75",
    SECRET_RANK_2: "#c678dd",
    SECRET_RANK_3: "#61afef",
    SECRET_RANK_TEXT: "#c678dd",
    NEXT_RANK_BAR: "#e5c07b",
    S_RANK_BASE: "#e5c07b",
    S_RANK_SHADOW: "#e5c07b",
    S_RANK_TEXT: "#282c34",
    A_RANK_BASE: "#56b6c2",
    A_RANK_SHADOW: "#56b6c2",
    A_RANK_TEXT: "#282c34",
    B_RANK_BASE: "#c678dd",
    B_RANK_SHADOW: "#c678dd",
    B_RANK_TEXT: "#282c34",
    DEFAULT_RANK_BASE: "#abb2bf",
    DEFAULT_RANK_SHADOW: "#abb2bf",
    DEFAULT_RANK_TEXT: "#282c34",
  },
  gruvbox: {
    BACKGROUND: "#282828",
    TITLE: "#ebdbb2",
    ICON_CIRCLE: "#ebdbb2",
    TEXT: "#98971a",
    LAUREL: "#689d6a",
    SECRET_RANK_1: "#fb4934",
    SECRET_RANK_2: "#d3869b",
    SECRET_RANK_3: "#458588",
    SECRET_RANK_TEXT: "#b16286",
    NEXT_RANK_BAR: "#fabd26",
    S_RANK_BASE: "#fabd2f",
    S_RANK_SHADOW: "#fabd2f",
    S_RANK_TEXT: "#322301",
    A_RANK_BASE: "#83a598",
    A_RANK_SHADOW: "#83a598",
    A_RANK_TEXT: "#151e1a",
    B_RANK_BASE: "#d65d0e",
    B_RANK_SHADOW: "#d65d0e",
    B_RANK_TEXT: "#301503",
    DEFAULT_RANK_BASE: "#928374",
    DEFAULT_RANK_SHADOW: "#928374",
    DEFAULT_RANK_TEXT: "#282828",
  },
  monokai: {
    BACKGROUND: "#272822",
    TITLE: "#f92672",
    ICON_CIRCLE: "#fff",
    TEXT: "#fff",
    LAUREL: "#a6e22e",
    SECRET_RANK_1: "#f92672",
    SECRET_RANK_2: "#ae81ff",
    SECRET_RANK_3: "#66d9ef",
    SECRET_RANK_TEXT: "#b16286",
    NEXT_RANK_BAR: "#f92672",
    S_RANK_BASE: "#e6db74",
    S_RANK_SHADOW: "#e6db74",
    S_RANK_TEXT: "#272822",
    A_RANK_BASE: "#66d9ef",
    A_RANK_SHADOW: "#66d9ef",
    A_RANK_TEXT: "#272822",
    B_RANK_BASE: "#fd971f",
    B_RANK_SHADOW: "#fd971f",
    B_RANK_TEXT: "#272822",
    DEFAULT_RANK_BASE: "#75715e",
    DEFAULT_RANK_SHADOW: "#75715e",
    DEFAULT_RANK_TEXT: "#282828",
  },
  nord: {
    BACKGROUND: "#2E3440",
    TITLE: "#81A1C1",
    ICON_CIRCLE: "#D8DEE9",
    TEXT: "#ECEFF4",
    LAUREL: "#A3BE8C",
    SECRET_RANK_1: "#BF616A",
    SECRET_RANK_2: "#B48EAD",
    SECRET_RANK_3: "#81A1C1",
    SECRET_RANK_TEXT: "#B48EAD",
    NEXT_RANK_BAR: "#81A1C1",
    S_RANK_BASE: "#EBCB8B",
    S_RANK_SHADOW: "#EBCB8B",
    S_RANK_TEXT: "#3B4252",
    A_RANK_BASE: "#8FBCBB",
    A_RANK_SHADOW: "#8FBCBB",
    A_RANK_TEXT: "#3B4252",
    B_RANK_BASE: "#D08770",
    B_RANK_SHADOW: "#D08770",
    B_RANK_TEXT: "#3B4252",
    DEFAULT_RANK_BASE: "#5E81AC",
    DEFAULT_RANK_SHADOW: "#5E81AC",
    DEFAULT_RANK_TEXT: "#3B4252",
  },
  discord: {
    BACKGROUND: "#23272A",
    TITLE: "#7289DA",
    ICON_CIRCLE: "#FFFFFF",
    TEXT: "#FFFFFF",
    LAUREL: "#57F287",
    SECRET_RANK_1: "#ED4245",
    SECRET_RANK_2: "#57F287",
    SECRET_RANK_3: "#5865F2",
    SECRET_RANK_TEXT: "#000000",
    NEXT_RANK_BAR: "#5865F2",
    S_RANK_BASE: "#FEE75C",
    S_RANK_SHADOW: "#FEE75C",
    S_RANK_TEXT: "#000000",
    A_RANK_BASE: "#EB459E",
    A_RANK_SHADOW: "#ED4245",
    A_RANK_TEXT: "#000000",
    B_RANK_BASE: "#ED4245",
    B_RANK_SHADOW: "#ED4245",
    B_RANK_TEXT: "#000000",
    DEFAULT_RANK_BASE: "#5865F2",
    DEFAULT_RANK_SHADOW: "#5865F2",
    DEFAULT_RANK_TEXT: "#000000",
  },
  chalk: {
    BACKGROUND: "#2d2d2d",
    TITLE: "#fed37e",
    ICON_CIRCLE: "#e4e4e4",
    TEXT: "#d4d4d4",
    LAUREL: "#a9d3ab",
    SECRET_RANK_1: "#f58e8e",
    SECRET_RANK_2: "#d6add5",
    SECRET_RANK_3: "#66d9ef",
    SECRET_RANK_TEXT: "#f58e8e",
    NEXT_RANK_BAR: "#7aabd4",
    S_RANK_BASE: "#fed37e",
    S_RANK_SHADOW: "#fed37e",
    S_RANK_TEXT: "#2d2d2d",
    A_RANK_BASE: "#79D4D5",
    A_RANK_SHADOW: "#79D4D5",
    A_RANK_TEXT: "#2d2d2d",
    B_RANK_BASE: "#f58e8e",
    B_RANK_SHADOW: "#f58e8e",
    B_RANK_TEXT: "#2d2d2d",
    DEFAULT_RANK_BASE: "#75715e",
    DEFAULT_RANK_SHADOW: "#75715e",
    DEFAULT_RANK_TEXT: "#2d2d2d",
  },
  alduin: {
    BACKGROUND: "#1c1c1c",
    TITLE: "#dfd7af",
    ICON_CIRCLE: "#e3e3e3",
    TEXT: "#dfd7af",
    LAUREL: "#a9d3ab",
    SECRET_RANK_1: "#f58e8e",
    SECRET_RANK_2: "#d6add5",
    SECRET_RANK_3: "#66d9ef",
    SECRET_RANK_TEXT: "#f58e8e",
    NEXT_RANK_BAR: "#dfd7af",
    S_RANK_BASE: "#fed37e",
    S_RANK_SHADOW: "#fed37e",
    S_RANK_TEXT: "#2d2d2d",
    A_RANK_BASE: "#79D4D5",
    A_RANK_SHADOW: "#79D4D5",
    A_RANK_TEXT: "#2d2d2d",
    B_RANK_BASE: "#f58e8e",
    B_RANK_SHADOW: "#f58e8e",
    B_RANK_TEXT: "#2d2d2d",
    DEFAULT_RANK_BASE: "#75715e",
    DEFAULT_RANK_SHADOW: "#75715e",
    DEFAULT_RANK_TEXT: "#2d2d2d",
  },
  darkhub: {
    BACKGROUND: "#0d1117",
    TITLE: "#c9d1d9",
    ICON_CIRCLE: "#f0f6fb",
    TEXT: "#8b949e",
    LAUREL: "#178600",
    SECRET_RANK_1: "#ff5555",
    SECRET_RANK_2: "#ff79c6",
    SECRET_RANK_3: "#388bfd",
    SECRET_RANK_TEXT: "#ff79c6",
    NEXT_RANK_BAR: "#ff79c6",
    S_RANK_BASE: "#ffb86c",
    S_RANK_SHADOW: "#ffb86c",
    S_RANK_TEXT: "#0d1117",
    A_RANK_BASE: "#8be9fd",
    A_RANK_SHADOW: "#8be9fd",
    A_RANK_TEXT: "#0d1117",
    B_RANK_BASE: "#ff5555",
    B_RANK_SHADOW: "#ff5555",
    B_RANK_TEXT: "#0d1117",
    DEFAULT_RANK_BASE: "#6272a4",
    DEFAULT_RANK_SHADOW: "#6272a4",
    DEFAULT_RANK_TEXT: "#0d1117",
  },
  juicyfresh: {
    BACKGROUND: "#0d0c15",
    TITLE: "#f7d745",
    ICON_CIRCLE: "#FFF",
    TEXT: "#b2d76c",
    LAUREL: "#8bb071",
    SECRET_RANK_1: "#a8d937",
    SECRET_RANK_2: "#f7e662",
    SECRET_RANK_3: "#4d9b1c",
    SECRET_RANK_TEXT: "#ff5700",
    NEXT_RANK_BAR: "#6562af",
    S_RANK_BASE: "#f7d644",
    S_RANK_SHADOW: "#f69e44",
    S_RANK_TEXT: "#ff5700",
    A_RANK_BASE: "#f69e44",
    A_RANK_SHADOW: "#f46d5a",
    A_RANK_TEXT: "#ff5700",
    B_RANK_BASE: "#f46d5a",
    B_RANK_SHADOW: "#f73155",
    B_RANK_TEXT: "#ff5700",
    DEFAULT_RANK_BASE: "#f0d7d6",
    DEFAULT_RANK_SHADOW: "#f58867",
    DEFAULT_RANK_TEXT: "#ff5700",
  },
  oldie: {
    BACKGROUND: "#F0F0F0",
    TITLE: "#111",
    ICON_CIRCLE: "#FFF",
    TEXT: "#666",
    LAUREL: "#535353",
    SECRET_RANK_1: "#738986",
    SECRET_RANK_2: "#B36154",
    SECRET_RANK_3: "#91A16A",
    SECRET_RANK_TEXT: "#4D4D4D",
    NEXT_RANK_BAR: "#8E8680",
    S_RANK_BASE: "#8E8E8E",
    S_RANK_SHADOW: "#8E8E8E",
    S_RANK_TEXT: "#4D4D4D",
    A_RANK_BASE: "#AFAFAF",
    A_RANK_SHADOW: "#AFAFAF",
    A_RANK_TEXT: "#4D4D4D",
    B_RANK_BASE: "#858585",
    B_RANK_SHADOW: "#858585",
    B_RANK_TEXT: "#4D4D4D",
    DEFAULT_RANK_BASE: "#535353",
    DEFAULT_RANK_SHADOW: "#535353",
    DEFAULT_RANK_TEXT: "#4D4D4D",
  },
  buddhism: {
    BACKGROUND: "#ffc20e",
    TITLE: "#FFF",
    ICON_CIRCLE: "#FFF",
    TEXT: "#FFF",
    LAUREL: "#27c5ff",
    SECRET_RANK_1: "#FFF",
    SECRET_RANK_2: "#f73155",
    SECRET_RANK_3: "#fff",
    SECRET_RANK_TEXT: "#f73155",
    NEXT_RANK_BAR: "#f73155",
    S_RANK_BASE: "#ff8400",
    S_RANK_SHADOW: "#ff8400",
    S_RANK_TEXT: "#ffc20e",
    A_RANK_BASE: "#fff",
    A_RANK_SHADOW: "#fff",
    A_RANK_TEXT: "#ffc20e",
    B_RANK_BASE: "#f73155",
    B_RANK_SHADOW: "#f73155",
    B_RANK_TEXT: "#ffc20e",
    DEFAULT_RANK_BASE: "#27c5ff",
    DEFAULT_RANK_SHADOW: "#27c5ff",
    DEFAULT_RANK_TEXT: "#ffc20e",
  },
  radical: {
    BACKGROUND: "#141321",
    ICON_CIRCLE: "#EEEEEE",
    TITLE: "#fe428e",
    TEXT: "#a9fef7",
    LAUREL: "#50fa7b",
    SECRET_RANK_1: "#ff5555",
    SECRET_RANK_2: "#ff15d9",
    SECRET_RANK_3: "#1E65F5",
    SECRET_RANK_TEXT: "#ff61c6",
    NEXT_RANK_BAR: "#fe428e",
    S_RANK_BASE: "#ffce32",
    S_RANK_SHADOW: "#ffce32",
    S_RANK_TEXT: "#CB8A30",
    A_RANK_BASE: "#8DF7B5",
    A_RANK_SHADOW: "#8DF7B5",
    A_RANK_TEXT: "#3A3A3A",
    B_RANK_BASE: "#EA3F25",
    B_RANK_SHADOW: "#EA3F25",
    B_RANK_TEXT: "#3A3A3A",
    DEFAULT_RANK_BASE: "#1E65F5",
    DEFAULT_RANK_SHADOW: "#1E65F5",
    DEFAULT_RANK_TEXT: "#3A3A3A",
  },
  onestar: {
    BACKGROUND: "#0d1117",
    ICON_CIRCLE: "#EEEEEE",
    TITLE: "#EEEEEE",
    TEXT: "#c7c7c7",
    LAUREL: "#0dbc79",
    SECRET_RANK_1: "#ff5555",
    SECRET_RANK_2: "#d861d8",
    SECRET_RANK_3: "#3b8eea",
    SECRET_RANK_TEXT: "#ff61c6",
    NEXT_RANK_BAR: "#9e9e9e",
    S_RANK_BASE: "#FFD54F",
    S_RANK_SHADOW: "#FFE082",
    S_RANK_TEXT: "#CB8A30",
    A_RANK_BASE: "#23d18b",
    A_RANK_SHADOW: "#8DF7B5",
    A_RANK_TEXT: "#3A3A3A",
    B_RANK_BASE: "#d13b3b",
    B_RANK_SHADOW: "#fa4b4b",
    B_RANK_TEXT: "#3A3A3A",
    DEFAULT_RANK_BASE: "#2472c8",
    DEFAULT_RANK_SHADOW: "#3b8eea",
    DEFAULT_RANK_TEXT: "#3A3A3A",
  },
  algolia: {
    BACKGROUND: "#050f2c",
    TITLE: "#00aeff",
    ICON_CIRCLE: "#f0f6fb",
    TEXT: "#7eace9",
    LAUREL: "#178600",
    SECRET_RANK_1: "#ff5555",
    SECRET_RANK_2: "#ff79c6",
    SECRET_RANK_3: "#388bfd",
    SECRET_RANK_TEXT: "#ff79c6",
    NEXT_RANK_BAR: "#00aeff",
    S_RANK_BASE: "#ffb86c",
    S_RANK_SHADOW: "#ffb86c",
    S_RANK_TEXT: "#0d1117",
    A_RANK_BASE: "#2dde98",
    A_RANK_TEXT: "#0d1117",
    A_RANK_SHADOW: "#2dde98",
    B_RANK_BASE: "#8be9fd",
    B_RANK_SHADOW: "#8be9fd",
    B_RANK_TEXT: "#0d1117",
    DEFAULT_RANK_BASE: "#5c75c3",
    DEFAULT_RANK_SHADOW: "#6272a4",
    DEFAULT_RANK_TEXT: "#0d1117",
  },
  gitdimmed: {
    BACKGROUND: "#333",
    TITLE: "#f0f6fb",
    ICON_CIRCLE: "#f0f6fb",
    TEXT: "#FFF",
    LAUREL: "#178600",
    SECRET_RANK_1: "#ff5555",
    SECRET_RANK_2: "#ff79c6",
    SECRET_RANK_3: "#388bfd",
    SECRET_RANK_TEXT: "#ff79c6",
    NEXT_RANK_BAR: "#00aeff",
    S_RANK_BASE: "#ffb86c",
    S_RANK_SHADOW: "#ffb86c",
    S_RANK_TEXT: "#0d1117",
    A_RANK_BASE: "#2dde98",
    A_RANK_TEXT: "#0d1117",
    A_RANK_SHADOW: "#2dde98",
    B_RANK_BASE: "#8be9fd",
    B_RANK_SHADOW: "#8be9fd",
    B_RANK_TEXT: "#0d1117",
    DEFAULT_RANK_BASE: "#5c75c3",
    DEFAULT_RANK_SHADOW: "#6272a4",
    DEFAULT_RANK_TEXT: "#0d1117",
  },
  tokyonight: {
    BACKGROUND: "#1a1b27",
    TITLE: "#70a5fd",
    ICON_CIRCLE: "#bf91f3",
    TEXT: "#38bdae",
    LAUREL: "#178600",
    SECRET_RANK_1: "#ff5555",
    SECRET_RANK_2: "#ff79c6",
    SECRET_RANK_3: "#388bfd",
    SECRET_RANK_TEXT: "#ff79c6",
    NEXT_RANK_BAR: "#00aeff",
    S_RANK_BASE: "#ffb86c",
    S_RANK_SHADOW: "#ffb86c",
    S_RANK_TEXT: "#0d1117",
    A_RANK_BASE: "#2dde98",
    A_RANK_TEXT: "#0d1117",
    A_RANK_SHADOW: "#2dde98",
    B_RANK_BASE: "#8be9fd",
    B_RANK_SHADOW: "#8be9fd",
    B_RANK_TEXT: "#0d1117",
    DEFAULT_RANK_BASE: "#5c75c3",
    DEFAULT_RANK_SHADOW: "#6272a4",
    DEFAULT_RANK_TEXT: "#0d1117",
  },
  matrix: {
    BACKGROUND: "#000000",
    TITLE: "#00cc00",
    ICON_CIRCLE: "#002200",
    TEXT: "#00cc00",
    LAUREL: "#178600",
    SECRET_RANK_1: "#ffd700",
    SECRET_RANK_2: "#ffffff",
    SECRET_RANK_3: "#ffd700",
    SECRET_RANK_TEXT: "#00ff00",
    NEXT_RANK_BAR: "#00ff00",
    S_RANK_BASE: "#ffd700",
    S_RANK_SHADOW: "#ffd700",
    S_RANK_TEXT: "#00ff00",
    A_RANK_BASE: "#c0c0c0",
    A_RANK_TEXT: "#00ff00",
    A_RANK_SHADOW: "#c0c0c0",
    B_RANK_BASE: "#b08d57",
    B_RANK_SHADOW: "#b08d57",
    B_RANK_TEXT: "#00ff00",
    DEFAULT_RANK_BASE: "#b08d57",
    DEFAULT_RANK_SHADOW: "#b08d57",
    DEFAULT_RANK_TEXT: "#00ff00",
  },
  apprentice: {
    BACKGROUND: "#262626",
    TITLE: "#BCBCBC",
    ICON_CIRCLE: "#BCBCBC",
    TEXT: "#5F875F",
    LAUREL: "#5F8787",
    SECRET_RANK_1: "#FF8700",
    SECRET_RANK_2: "#8787AF",
    SECRET_RANK_3: "#5F87AF",
    SECRET_RANK_TEXT: "#5F5F87",
    NEXT_RANK_BAR: "#FFFFA9",
    S_RANK_BASE: "#FFFFAF",
    S_RANK_SHADOW: "#FFFFAF",
    S_RANK_TEXT: "#87875F",
    A_RANK_BASE: "#8FAFD7",
    A_RANK_SHADOW: "#8FAFD7",
    A_RANK_TEXT: "#5F875F",
    B_RANK_BASE: "#AF5F5F",
    B_RANK_SHADOW: "#AF5F5F",
    B_RANK_TEXT: "#AF5F5F",
    DEFAULT_RANK_BASE: "#6C6C6C",
    DEFAULT_RANK_SHADOW: "#6C6C6C",
    DEFAULT_RANK_TEXT: "#1C1C1C",
  },
  dark_dimmed: {
    BACKGROUND: "#22272e",
    TITLE: "#adbac7",
    ICON_CIRCLE: "#002200",
    TEXT: "#adbac7",
    LAUREL: "#178600",
    SECRET_RANK_1: "red",
    SECRET_RANK_2: "fuchsia",
    SECRET_RANK_3: "blue",
    SECRET_RANK_TEXT: "fuchsia",
    NEXT_RANK_BAR: "#0366d6",
    S_RANK_BASE: "#FAD200",
    S_RANK_SHADOW: "#C8A090",
    S_RANK_TEXT: "#886000",
    A_RANK_BASE: "#B0B0B0",
    A_RANK_SHADOW: "#9090C0",
    A_RANK_TEXT: "#505050",
    B_RANK_BASE: "#A18D66",
    B_RANK_SHADOW: "#816D96",
    B_RANK_TEXT: "#412D06",
    DEFAULT_RANK_BASE: "#777",
    DEFAULT_RANK_SHADOW: "#333",
    DEFAULT_RANK_TEXT: "#333",
  },
  dark_lover: {
    BACKGROUND: "#0d0d0d",
    TITLE: "#e8aa64",
    ICON_CIRCLE: "white",
    TEXT: "#e8aa64",
    LAUREL: "#e86464",
    SECRET_RANK_1: "#e05555",
    SECRET_RANK_2: "#e05555",
    SECRET_RANK_3: "#e05555",
    SECRET_RANK_TEXT: "#e05555",
    NEXT_RANK_BAR: "#e05555",
    S_RANK_BASE: "#f2c635",
    S_RANK_SHADOW: "#e0d7b8",
    S_RANK_TEXT: "#b35707",
    A_RANK_BASE: "#f25755",
    A_RANK_SHADOW: "#e69493",
    A_RANK_TEXT: "#f5352f",
    B_RANK_BASE: "#63db93",
    B_RANK_SHADOW: "#8cd1a8",
    B_RANK_TEXT: "#07b84e",
    DEFAULT_RANK_BASE: "#7f6ceb",
    DEFAULT_RANK_SHADOW: "#a598ed",
    DEFAULT_RANK_TEXT: "#7f6ceb",
  },
  kimbie_dark: {
    BACKGROUND: "#221a0f",
    TITLE: "#d3af86",
    ICON_CIRCLE: "#7e602c",
    TEXT: "#d3af86",
    LAUREL: "#889b4a",
    SECRET_RANK_1: "#f14a68",
    SECRET_RANK_2: "#f14a68",
    SECRET_RANK_3: "#dc3958",
    SECRET_RANK_TEXT: "#dc3958",
    NEXT_RANK_BAR: "#dc3958",
    S_RANK_BASE: "#fcac51",
    S_RANK_SHADOW: "#f79a32",
    S_RANK_TEXT: "#d3af86",
    A_RANK_BASE: "#a3B95a",
    A_RANK_SHADOW: "#889b4a",
    A_RANK_TEXT: "#d3af86",
    B_RANK_BASE: "#4c96a8",
    B_RANK_SHADOW: "#418292",
    B_RANK_TEXT: "#d3af86",
    DEFAULT_RANK_BASE: "#8ab1b0",
    DEFAULT_RANK_SHADOW: "#719190",
    DEFAULT_RANK_TEXT: "#d3af86",
  },
  aura: {
    BACKGROUND: "#1E1D26",
    TITLE: "#FFFFFF",
    ICON_CIRCLE: "#FFFFFF",
    TEXT: "#dbffe6",
    LAUREL: "#a9fcca",
    SECRET_RANK_1: "#c273ff",
    SECRET_RANK_2: "#c273ff",
    SECRET_RANK_3: "#c273ff",
    SECRET_RANK_TEXT: "#bd93f9",
    NEXT_RANK_BAR: "#715df5",
    S_RANK_BASE: "#8e57ff",
    S_RANK_SHADOW: "#2361ad",
    S_RANK_TEXT: "#6272a4",
    A_RANK_BASE: "#7c71f5",
    A_RANK_SHADOW: "#3ae056",
    A_RANK_TEXT: "#6272a4",
    B_RANK_BASE: "#226a80",
    B_RANK_SHADOW: "#226a80",
    B_RANK_TEXT: "#6272a4",
    DEFAULT_RANK_BASE: "#5e8c2a",
    DEFAULT_RANK_SHADOW: "#5e8c2a",
    DEFAULT_RANK_TEXT: "#5e8c2a",
  },
};

export interface Theme {
  BACKGROUND: string;
  TITLE: string;
  ICON_CIRCLE: string;
  TEXT: string;
  LAUREL: string;
  SECRET_RANK_1: string;
  SECRET_RANK_2: string;
  SECRET_RANK_3: string;
  SECRET_RANK_TEXT: string;
  NEXT_RANK_BAR: string;
  S_RANK_BASE: string;
  S_RANK_SHADOW: string;
  S_RANK_TEXT: string;
  A_RANK_BASE: string;
  A_RANK_SHADOW: string;
  A_RANK_TEXT: string;
  B_RANK_BASE: string;
  B_RANK_SHADOW: string;
  B_RANK_TEXT: string;
  DEFAULT_RANK_BASE: string;
  DEFAULT_RANK_SHADOW: string;
  DEFAULT_RANK_TEXT: string;
}


================================================
FILE: src/trophy.ts
================================================
import { getNextRankBar, getTrophyIcon } from "./icons.ts";
import { abridgeScore, CONSTANTS, RANK, RANK_ORDER } from "./utils.ts";
import { Theme } from "./theme.ts";

class RankCondition {
  constructor(
    readonly rank: RANK,
    readonly message: string,
    readonly requiredScore: number,
  ) {}
}

export class Trophy {
  rankCondition: RankCondition | null = null;
  rank: RANK = RANK.UNKNOWN;
  topMessage = "Unknown";
  bottomMessage = "0";
  title = "";
  filterTitles: Array<string> = [];
  hidden = false;
  constructor(
    private score: number,
    private rankConditions: Array<RankCondition>,
  ) {
    this.bottomMessage = abridgeScore(score);
    this.setRank();
  }
  setRank() {
    const sortedRankConditions = this.rankConditions.toSorted((a, b) =>
      RANK_ORDER.indexOf(a.rank) - RANK_ORDER.indexOf(b.rank)
    );
    // Set the rank that hit the first condition
    const rankCondition = sortedRankConditions.find((r) =>
      this.score >= r.requiredScore
    );
    if (rankCondition != null) {
      this.rank = rankCondition.rank;
      this.rankCondition = rankCondition;
      this.topMessage = rankCondition.message;
    }
  }
  private calculateNextRankPercentage() {
    if (this.rank === RANK.UNKNOWN) {
      return 0;
    }
    const nextRankIndex = RANK_ORDER.indexOf(this.rank) - 1;
    // When got the max rank
    if (nextRankIndex < 0 || this.rank === RANK.SSS) {
      return 1;
    }
    const nextRank = RANK_ORDER[nextRankIndex];
    const nextRankCondition = this.rankConditions.find((r) =>
      r.rank == nextRank
    );
    const distance = nextRankCondition!.requiredScore -
      this.rankCondition!.requiredScore;
    const progress = this.score - this.rankCondition!.requiredScore;
    const result = progress / distance;
    return result;
  }
  render(
    theme: Theme,
    x = 0,
    y = 0,
    panelSize = CONSTANTS.DEFAULT_PANEL_SIZE,
    noBackground = CONSTANTS.DEFAULT_NO_BACKGROUND,
    noFrame = CONSTANTS.DEFAULT_NO_FRAME,
  ): string {
    const { BACKGROUND: PRIMARY, TITLE: SECONDARY, TEXT, NEXT_RANK_BAR } =
      theme;
    const nextRankBar = getNextRankBar(
      this.title,
      this.calculateNextRankPercentage(),
      NEXT_RANK_BAR,
    );
    return `
        <svg
          x="${x}"
          y="${y}"
          width="${panelSize}"
          height="${panelSize}"
          viewBox="0 0 ${panelSize} ${panelSize}"
          fill="none"
          xmlns="http://www.w3.org/2000/svg"
        >
          <rect
            x="0.5"
            y="0.5"
            rx="4.5"
            width="${panelSize - 1}"
            height="${panelSize - 1}"
            stroke="#e1e4e8"
            fill="${PRIMARY}"
            stroke-opacity="${noFrame ? "0" : "1"}"
            fill-opacity="${noBackground ? "0" : "1"}"
          />
          ${getTrophyIcon(theme, this.rank)}
          <text x="50%" y="18" text-anchor="middle" font-family="Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji" font-weight="bold" font-size="13" fill="${SECONDARY}">${this.title}</text>
          <text x="50%" y="85" text-anchor="middle" font-family="Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji" font-weight="bold" font-size="10.5" fill="${TEXT}">${this.topMessage}</text>
          <text x="50%" y="97" text-anchor="middle" font-family="Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji" font-weight="bold" font-size="10" fill="${TEXT}">${this.bottomMessage}</text>
          ${nextRankBar}
        </svg>
        `;
  }
}

export class MultipleLangTrophy extends Trophy {
  constructor(score: number) {
    const rankConditions = [
      new RankCondition(
        RANK.SECRET,
        "Rainbow Lang User",
        10,
      ),
    ];
    super(score, rankConditions);
    this.title = "MultiLanguage";
    this.filterTitles = ["MultipleLang", "MultiLanguage"];
    this.hidden = true;
  }
}

export class AllSuperRankTrophy extends Trophy {
  constructor(score: number) {
    const rankConditions = [
      new RankCondition(
        RANK.SECRET,
        "S Rank Hacker",
        1,
      ),
    ];
    super(score, rankConditions);
    this.title = "AllSuperRank";
    this.filterTitles = ["AllSuperRank"];
    this.bottomMessage = "All S Rank";
    this.hidden = true;
  }
}
export class Joined2020Trophy extends Trophy {
  constructor(score: number) {
    const rankConditions = [
      new RankCondition(
        RANK.SECRET,
        "Everything started...",
        1,
      ),
    ];
    super(score, rankConditions);
    this.title = "Joined2020";
    this.filterTitles = ["Joined2020"];
    this.bottomMessage = "Joined 2020";
    this.hidden = true;
  }
}
export class AncientAccountTrophy extends Trophy {
  constructor(score: number) {
    const rankConditions = [
      new RankCondition(
        RANK.SECRET,
        "Ancient User",
        1,
      ),
    ];
    super(score, rankConditions);
    this.title = "AncientUser";
    this.filterTitles = ["AncientUser"];
    this.bottomMessage = "Before 2010";
    this.hidden = true;
  }
}
export class LongTimeAccountTrophy extends Trophy {
  constructor(score: number) {
    const rankConditions = [
      new RankCondition(
        RANK.SECRET,
        "Village Elder",
        10,
      ),
    ];
    super(score, rankConditions);
    this.title = "LongTimeUser";
    this.filterTitles = ["LongTimeUser"];
    this.hidden = true;
  }
}
export class MultipleOrganizationsTrophy extends Trophy {
  constructor(score: number) {
    const rankConditions = [
      new RankCondition(
        RANK.SECRET,
        // or if this doesn't render well: "Factorum"
        "Jack of all Trades",
        3,
      ),
    ];
    super(score, rankConditions);
    this.title = "Organizations";
    this.filterTitles = ["Organizations", "Orgs", "Teams"];
    this.hidden = true;
  }
}

export class OGAccountTrophy extends Trophy {
  constructor(score: number) {
    const rankConditions = [
      new RankCondition(
        RANK.SECRET,
        "OG User",
        1,
      ),
    ];
    super(score, rankConditions);
    this.title = "OGUser";
    this.filterTitles = ["OGUser"];
    this.bottomMessage = "Joined 2008";
    this.hidden = true;
  }
}

export class TotalReviewsTrophy extends Trophy {
  constructor(score: number) {
    const rankConditions = [
      new RankCondition(
        RANK.SSS,
        "God Reviewer",
        70,
      ),
      new RankCondition(
        RANK.SS,
        "Deep Reviewer",
        57,
      ),
      new RankCondition(
        RANK.S,
        "Super Reviewer",
        45,
      ),
      new RankCondition(
        RANK.AAA,
        "Ultra Reviewer",
        30,
      ),
      new RankCondition(
        RANK.AA,
        "Hyper Reviewer",
        20,
      ),
      new RankCondition(
        RANK.A,
        "Active Reviewer",
        8,
      ),
      new RankCondition(
        RANK.B,
        "Intermediate Reviewer",
        3,
      ),
      new RankCondition(
        RANK.C,
        "New Reviewer",
        1,
      ),
    ];
    super(score, rankConditions);
    this.title = "Reviews";
    this.filterTitles = ["Review", "Reviews"];
  }
}

export class AccountDurationTrophy extends Trophy {
  constructor(score: number) {
    const rankConditions = [
      new RankCondition(
        RANK.SSS,
        "Seasoned Veteran",
        70, // 20 years
      ),
      new RankCondition(
        RANK.SS,
        "Grandmaster",
        55, // 15 years
      ),
      new RankCondition(
        RANK.S,
        "Master Dev",
        40, // 10 years
      ),
      new RankCondition(
        RANK.AAA,
        "Expert Dev",
        28, // 7.5 years
      ),
      new RankCondition(
        RANK.AA,
        "Experienced Dev",
        18, // 5 years
      ),
      new RankCondition(
        RANK.A,
        "Intermediate Dev",
        11, // 3 years
      ),
      new RankCondition(
        RANK.B,
        "Junior Dev",
        6, // 1.5 years
      ),
      new RankCondition(
        RANK.C,
        "Newbie",
        2, // 0.5 year
      ),
    ];
    super(score, rankConditions);
    this.title = "Experience";
    this.filterTitles = ["Experience", "Duration", "Since"];
  }
}

export class TotalStarTrophy extends Trophy {
  constructor(score: number) {
    const rankConditions = [
      new RankCondition(
        RANK.SSS,
        "Super Stargazer",
        2000,
      ),
      new RankCondition(
        RANK.SS,
        "High Stargazer",
        700,
      ),
      new RankCondition(
        RANK.S,
        "Stargazer",
        200,
      ),
      new RankCondition(
        RANK.AAA,
        "Super Star",
        100,
      ),
      new RankCondition(
        RANK.AA,
        "High Star",
        50,
      ),
      new RankCondition(
        RANK.A,
        "You are a Star",
        30,
      ),
      new RankCondition(
        RANK.B,
        "Middle Star",
        10,
      ),
      new RankCondition(
        RANK.C,
        "First Star",
        1,
      ),
    ];
    super(score, rankConditions);
    this.title = "Stars";
    this.filterTitles = ["Star", "Stars"];
  }
}

export class TotalCommitTrophy extends Trophy {
  constructor(score: number) {
    const rankConditions = [
      new RankCondition(
        RANK.SSS,
        "God Committer",
        4000,
      ),
      new RankCondition(
        RANK.SS,
        "Deep Committer",
        2000,
      ),
      new RankCondition(
        RANK.S,
        "Super Committer",
        1000,
      ),
      new RankCondition(
        RANK.AAA,
        "Ultra Committer",
        500,
      ),
      new RankCondition(
        RANK.AA,
        "Hyper Committer",
        200,
      ),
      new RankCondition(
        RANK.A,
        "High Committer",
        100,
      ),
      new RankCondition(
        RANK.B,
        "Middle Committer",
        10,
      ),
      new RankCondition(
        RANK.C,
        "First Commit",
        1,
      ),
    ];
    super(score, rankConditions);
    this.title = "Commits";
    this.filterTitles = ["Commit", "Commits"];
  }
}

export class TotalFollowerTrophy extends Trophy {
  constructor(score: number) {
    const rankConditions = [
      new RankCondition(
        RANK.SSS,
        "Super Celebrity",
        1000,
      ),
      new RankCondition(
        RANK.SS,
        "Ultra Celebrity",
        400,
      ),
      new RankCondition(
        RANK.S,
        "Hyper Celebrity",
        200,
      ),
      new RankCondition(
        RANK.AAA,
        "Famous User",
        100,
      ),
      new RankCondition(
        RANK.AA,
        "Active User",
        50,
      ),
      new RankCondition(
        RANK.A,
        "Dynamic User",
        20,
      ),
      new RankCondition(
        RANK.B,
        "Many Friends",
        10,
      ),
      new RankCondition(
        RANK.C,
        "First Friend",
        1,
      ),
    ];
    super(score, rankConditions);
    this.title = "Followers";
    this.filterTitles = ["Follower", "Followers"];
  }
}

export class TotalIssueTrophy extends Trophy {
  constructor(score: number) {
    const rankConditions = [
      new RankCondition(
        RANK.SSS,
        "God Issuer",
        1000,
      ),
      new RankCondition(
        RANK.SS,
        "Deep Issuer",
        500,
      ),
      new RankCondition(
        RANK.S,
        "Super Issuer",
        200,
      ),
      new RankCondition(
        RANK.AAA,
        "Ultra Issuer",
        100,
      ),
      new RankCondition(
        RANK.AA,
        "Hyper Issuer",
        50,
      ),
      new RankCondition(
        RANK.A,
        "High Issuer",
        20,
      ),
      new RankCondition(
        RANK.B,
        "Middle Issuer",
        10,
      ),
      new RankCondition(
        RANK.C,
        "First Issue",
        1,
      ),
    ];
    super(score, rankConditions);
    this.title = "Issues";
    this.filterTitles = ["Issue", "Issues"];
  }
}

export class TotalPullRequestTrophy extends Trophy {
  constructor(score: number) {
    const rankConditions = [
      new RankCondition(
        RANK.SSS,
        "God Puller",
        1000,
      ),
      new RankCondition(
        RANK.SS,
        "Deep Puller",
        500,
      ),
      new RankCondition(
        RANK.S,
        "Super Puller",
        200,
      ),
      new RankCondition(
        RANK.AAA,
        "Ultra Puller",
        100,
      ),
      new RankCondition(
        RANK.AA,
        "Hyper Puller",
        50,
      ),
      new RankCondition(
        RANK.A,
        "High Puller",
        20,
      ),
      new RankCondition(
        RANK.B,
        "Middle Puller",
        10,
      ),
      new RankCondition(
        RANK.C,
        "First Pull",
        1,
      ),
    ];
    super(score, rankConditions);
    this.title = "PullRequest";
    this.filterTitles = ["PR", "PullRequest", "Pulls", "Puller"];
  }
}

export class TotalRepositoryTrophy extends Trophy {
  constructor(score: number) {
    const rankConditions = [
      new RankCondition(
        RANK.SSS,
        "God Repo Creator",
        50,
      ),
      new RankCondition(
        RANK.SS,
        "Deep Repo Creator",
        45,
      ),
      new RankCondition(
        RANK.S,
        "Super Repo Creator",
        40,
      ),
      new RankCondition(
        RANK.AAA,
        "Ultra Repo Creator",
        35,
      ),
      new RankCondition(
        RANK.AA,
        "Hyper Repo Creator",
        30,
      ),
      new RankCondition(
        RANK.A,
        "High Repo Creator",
        20,
      ),
      new RankCondition(
        RANK.B,
        "Middle Repo Creator",
        10,
      ),
      new RankCondition(
        RANK.C,
        "First Repository",
        1,
      ),
    ];
    super(score, rankConditions);
    this.title = "Repositories";
    this.filterTitles = ["Repo", "Repository", "Repositories"];
  }
}


================================================
FILE: src/trophy_list.ts
================================================
import {
  AccountDurationTrophy,
  AllSuperRankTrophy,
  AncientAccountTrophy,
  Joined2020Trophy,
  LongTimeAccountTrophy,
  MultipleLangTrophy,
  MultipleOrganizationsTrophy,
  OGAccountTrophy,
  TotalCommitTrophy,
  TotalFollowerTrophy,
  TotalIssueTrophy,
  TotalPullRequestTrophy,
  TotalRepositoryTrophy,
  TotalReviewsTrophy,
  TotalStarTrophy,
  Trophy,
} from "./trophy.ts";
import { UserInfo } from "./user_info.ts";
import { RANK, RANK_ORDER } from "./utils.ts";

export class TrophyList {
  private trophies = new Array<Trophy>();
  constructor(userInfo: UserInfo) {
    // Base trophies
    this.trophies.push(
      new TotalStarTrophy(userInfo.totalStargazers),
      new TotalCommitTrophy(userInfo.totalCommits),
      new TotalFollowerTrophy(userInfo.totalFollowers),
      new TotalIssueTrophy(userInfo.totalIssues),
      new TotalPullRequestTrophy(userInfo.totalPullRequests),
      new TotalRepositoryTrophy(userInfo.totalRepositories),
      new TotalReviewsTrophy(userInfo.totalReviews),
    );
    // Secret trophies
    this.trophies.push(
      new AllSuperRankTrophy(this.isAllSRank),
      new MultipleLangTrophy(userInfo.languageCount),
      new LongTimeAccountTrophy(userInfo.durationYear),
      new AncientAccountTrophy(userInfo.ancientAccount),
      new OGAccountTrophy(userInfo.ogAccount),
      new Joined2020Trophy(userInfo.joined2020),
      new MultipleOrganizationsTrophy(userInfo.totalOrganizations),
      new AccountDurationTrophy(userInfo.durationDays),
    );
  }
  get length() {
    return this.trophies.length;
  }
  get getArray() {
    return this.trophies;
  }
  private get isAllSRank() {
    return this.trophies.every((trophy) => trophy.rank.slice(0, 1) == RANK.S)
      ? 1
      : 0;
  }
  filterByHidden() {
    this.trophies = this.trophies.filter((trophy) =>
      !trophy.hidden || trophy.rank !== RANK.UNKNOWN
    );
  }
  filterByTitles(titles: Array<string>) {
    this.trophies = this.trophies.filter((trophy) => {
      return trophy.filterTitles.some((title) => titles.includes(title));
    });
  }
  filterByRanks(ranks: Array<string>) {
    if (ranks.filter((rank) => rank.includes("-")).length !== 0) {
      this.trophies = this.trophies.filter((trophy) =>
        !ranks.map((rank) => rank.substring(1)).includes(trophy.rank)
      );
      return;
    }
    this.trophies = this.trophies.filter((trophy) =>
      ranks.includes(trophy.rank)
    );
  }
  filterByExclusionTitles(titles: Array<string>) {
    const excludeTitles = titles.filter((title) => title.startsWith("-")).map(
      (title) => title.substring(1),
    );
    if (excludeTitles.length > 0) {
      this.trophies = this.trophies.filter((trophy) =>
        !excludeTitles.includes(trophy.title)
      );
    }
  }
  sortByRank() {
    this.trophies = this.trophies.toSorted((a: Trophy, b: Trophy) =>
      RANK_ORDER.indexOf(a.rank) - RANK_ORDER.indexOf(b.rank)
    );
  }
}


================================================
FILE: src/user_info.ts
================================================
type Language = { name: string };
type Stargazers = { totalCount: number };
type Repository = {
  languages: { nodes: Language[] };
  stargazers: Stargazers;
  createdAt: string;
};
export type GitHubUserRepository = {
  repositories: {
    totalCount: number;
    nodes: Repository[];
  };
};

export type GitHubUserIssue = {
  openIssues: {
    totalCount: number;
  };
  closedIssues: {
    totalCount: number;
  };
};

export type GitHubUserPullRequest = {
  pullRequests: {
    totalCount: number;
  };
};

export type GitHubUserActivity = {
  createdAt: string;
  contributionsCollection: {
    totalCommitContributions: number;
    restrictedContributionsCount: number;
    totalPullRequestReviewContributions: number;
  };
  organizations: {
    totalCount: number;
  };
  followers: {
    totalCount: number;
  };
};

export type GitHubUserAll =
  & GitHubUserActivity
  & GitHubUserIssue
  & GitHubUserPullRequest
  & GitHubUserRepository;
export class UserInfo {
  public readonly totalCommits: number;
  public readonly totalFollowers: number;
  public readonly totalIssues: number;
  public readonly totalOrganizations: number;
  public readonly totalPullRequests: number;
  public readonly totalReviews: number;
  public readonly totalStargazers: number;
  public readonly totalRepositories: number;
  public readonly languageCount: number;
  public readonly durationYear: number;
  public readonly durationDays: number;
  public readonly ancientAccount: number;
  public readonly joined2020: number;
  public readonly ogAccount: number;

  static fromCombined(data: GitHubUserAll): UserInfo {
    return new UserInfo(data, data, data, data);
  }

  constructor(
    userActivity: GitHubUserActivity,
    userIssue: GitHubUserIssue,
    userPullRequest: GitHubUserPullRequest,
    userRepository: GitHubUserRepository,
  ) {
    const totalCommits =
      userActivity.contributionsCollection.restrictedContributionsCount +
      userActivity.contributionsCollection.totalCommitContributions;
    const totalStargazers = userRepository.repositories.nodes.reduce(
      (prev: number, node: Repository) => {
        return prev + node.stargazers.totalCount;
      },
      0,
    );

    const languages = new Set<string>();
    userRepository.repositories.nodes.forEach((node: Repository) => {
      if (node.languages.nodes != undefined) {
        node.languages.nodes.forEach((node: Language) => {
          if (node != undefined) {
            languages.add(node.name);
          }
        });
      }
    });

    // Find the earliest repository creation date
    let earliestRepoDate = userActivity.createdAt; // start with the oldest possible

    earliestRepoDate = userRepository.repositories.nodes.reduce(
      (earliest, node) => {
        return new Date(node.createdAt).getTime() < new Date(earliest).getTime()
          ? node.createdAt
          : earliest;
      },
      earliestRepoDate,
    );

    const durationTime = new Date().getTime() -
      new Date(earliestRepoDate).getTime();
    const durationYear = new Date(durationTime).getUTCFullYear() - 1970;
    const durationDays = Math.floor(
      durationTime / (1000 * 60 * 60 * 24) / 100,
    );
    const ancientAccount = new Date(earliestRepoDate).getFullYear() <= 2010
      ? 1
      : 0;
    const joined2020 = new Date(earliestRepoDate).getFullYear() == 2020 ? 1 : 0;
    const ogAccount = new Date(earliestRepoDate).getFullYear() <= 2008 ? 1 : 0;

    this.totalCommits = totalCommits;
    this.totalFollowers = userActivity.followers.totalCount;
    this.totalIssues = userIssue.openIssues.totalCount +
      userIssue.closedIssues.totalCount;
    this.totalOrganizations = userActivity.organizations.totalCount;
    this.totalPullRequests = userPullRequest.pullRequests.totalCount;
    this.totalReviews =
      userActivity.contributionsCollection.totalPullRequestReviewContributions;
    this.totalStargazers = totalStargazers;
    this.totalRepositories = userRepository.repositories.totalCount;
    this.languageCount = languages.size;
    this.durationYear = durationYear;
    this.durationDays = durationDays;
    this.ancientAccount = ancientAccount;
    this.joined2020 = joined2020;
    this.ogAccount = ogAccount;
  }
}


================================================
FILE: src/utils.ts
================================================
export class CustomURLSearchParams extends URLSearchParams {
  getStringValue(key: string, defaultValue: string): string {
    if (super.has(key)) {
      const param = super.get(key);
      if (param !== null) {
        return param.toString();
      }
    }
    return defaultValue.toString();
  }
  getNumberValue(key: string, defaultValue: number): number {
    if (super.has(key)) {
      const param = super.get(key);
      if (param !== null) {
        const parsedValue = parseInt(param);
        if (isNaN(parsedValue)) {
          return defaultValue;
        }
        return parsedValue;
      }
    }
    return defaultValue;
  }
  getBooleanValue(key: string, defaultValue: boolean): boolean {
    if (super.has(key)) {
      const param = super.get(key);
      return param !== null && param.toString() === "true";
    }
    return defaultValue;
  }
}

export function parseParams(req: Request): CustomURLSearchParams {
  const splittedURL = req.url.split("?");
  if (splittedURL.length < 2) {
    return new CustomURLSearchParams();
  }
  return new CustomURLSearchParams(splittedURL[1]);
}

export function abridgeScore(score: number): string {
  if (Math.abs(score) < 1) {
    return "0pt";
  }
  if (Math.abs(score) > 999) {
    return (Math.sign(score) * (Math.abs(score) / 1000)).toFixed(1) + "kpt";
  }
  return (Math.sign(score) * Math.abs(score)).toString() + "pt";
}

const HOUR_IN_MILLISECONDS = 60 * 60 * 1000;

export const CONSTANTS = {
  CACHE_MAX_AGE: 18800,
  CDN_CACHE_MAX_AGE: 28800, // 8 hours for CDN edge cache
  STALE_WHILE_REVALIDATE: 86400, // 24 hours - serve stale while revalidating
  DEFAULT_PANEL_SIZE: 110,
  DEFAULT_MAX_COLUMN: 8,
  DEFAULT_MAX_ROW: 3,
  DEFAULT_MARGIN_W: 0,
  DEFAULT_MARGIN_H: 0,
  DEFAULT_NO_BACKGROUND: false,
  DEFAULT_NO_FRAME: false,
  DEFAULT_GITHUB_API: "https://api.github.com/graphql",
  DEFAULT_GITHUB_RETRY_DELAY: 500,
  REVALIDATE_TIME: HOUR_IN_MILLISECONDS * 6,
  REDIS_TTL: HOUR_IN_MILLISECONDS * 4,
};

export enum RANK {
  SECRET = "SECRET",
  SSS = "SSS",
  SS = "SS",
  S = "S",
  AAA = "AAA",
  AA = "AA",
  A = "A",
  B = "B",
  C = "C",
  UNKNOWN = "?",
}

export const RANK_ORDER = Object.values(RANK);


================================================
FILE: test/test.ts
================================================


================================================
FILE: vercel.json
================================================
{
  "public": true,
  "functions": {
    "api/**/*.[jt]s": {
      "runtime": "vercel-deno@3.1.1",
      "maxDuration": 5
    }
  },
  "rewrites": [
    {
      "source": "/(.*)",
      "destination": "/api/$1"
    }
  ]
}
Download .txt
gitextract_uvsbr8fm/

├── .editorconfig
├── .github/
│   ├── FUNDING.yml
│   └── workflows/
│       ├── close-old-issues.yml
│       ├── test-repository-action.yml
│       └── testing.yml
├── .gitignore
├── .vercelignore
├── CONTRIBUTING.md
├── Dockerfile
├── LICENSE
├── README.md
├── action.yml
├── api/
│   └── index.ts
├── deno.json
├── deps.ts
├── docker-compose.yml
├── env-example
├── main.ts
├── render_svg.ts
├── src/
│   ├── Helpers/
│   │   ├── Logger.ts
│   │   ├── Retry.ts
│   │   └── __tests__/
│   │       └── Retry.test.ts
│   ├── Repository/
│   │   └── GithubRepository.ts
│   ├── Schemas/
│   │   └── index.ts
│   ├── Services/
│   │   ├── GithubApiService.ts
│   │   ├── __mocks__/
│   │   │   ├── notFoundUserMock.json
│   │   │   ├── rateLimitMock.json
│   │   │   └── successGithubResponse.json
│   │   ├── __tests__/
│   │   │   └── githubApiService.test.ts
│   │   └── request.ts
│   ├── StaticRenderRegeneration/
│   │   ├── cache_manager.ts
│   │   ├── index.ts
│   │   ├── types.ts
│   │   └── utils.ts
│   ├── Types/
│   │   ├── EServiceKindError.ts
│   │   ├── Request.ts
│   │   ├── ServiceError.ts
│   │   └── index.ts
│   ├── card.ts
│   ├── config/
│   │   └── cache.ts
│   ├── error_page.ts
│   ├── icons.ts
│   ├── pages/
│   │   └── Error.ts
│   ├── theme.ts
│   ├── trophy.ts
│   ├── trophy_list.ts
│   ├── user_info.ts
│   └── utils.ts
├── test/
│   └── test.ts
└── vercel.json
Download .txt
SYMBOL INDEX (137 symbols across 24 files)

FILE: api/index.ts
  function app (line 39) | async function app(req: Request): Promise<Response> {

FILE: render_svg.ts
  function main (line 18) | async function main() {

FILE: src/Helpers/Logger.ts
  class Logger (line 3) | class Logger {
    method log (line 4) | public static log(message: unknown): void {
    method error (line 9) | public static error(message: unknown): void {
    method warn (line 14) | public static warn(message: unknown): void {

FILE: src/Helpers/Retry.ts
  type RetryCallbackProps (line 4) | type RetryCallbackProps = {
  type callbackType (line 8) | type callbackType<T = unknown> = (data: RetryCallbackProps) => Promise<T...
  class Retry (line 34) | class Retry {
    method constructor (line 35) | constructor(private maxRetries = 2, private retryDelay = 1000) {}
    method fetch (line 36) | async fetch<T = unknown>(

FILE: src/Helpers/__tests__/Retry.test.ts
  type MockResponse (line 9) | type MockResponse = {

FILE: src/Repository/GithubRepository.ts
  class GithubRepositoryService (line 30) | class GithubRepositoryService {
    method constructor (line 31) | constructor(public repository: GithubRepository) {}

FILE: src/Services/GithubApiService.ts
  constant TOKENS (line 24) | const TOKENS = [
  class GithubApiService (line 29) | class GithubApiService extends GithubRepository {
    method requestUserAll (line 30) | async requestUserAll(
    method requestUserRepository (line 37) | async requestUserRepository(
    method requestUserActivity (line 44) | async requestUserActivity(
    method requestUserIssue (line 51) | async requestUserIssue(
    method requestUserPullRequest (line 58) | async requestUserPullRequest(
    method requestUserInfo (line 66) | async requestUserInfo(username: string): Promise<UserInfo | ServiceErr...
    method executeQuery (line 80) | async executeQuery<T = unknown>(

FILE: src/Services/request.ts
  function requestGithubData (line 11) | async function requestGithubData<T = unknown>(
  function handleError (line 31) | function handleError(

FILE: src/StaticRenderRegeneration/cache_manager.ts
  class CacheManager (line 4) | class CacheManager {
    method constructor (line 5) | constructor(private revalidateTime: number, private cacheFile: string) {}
    method cacheFilePath (line 9) | get cacheFilePath(): string {
    method cacheFileExists (line 12) | get cacheFileExists(): boolean {
    method cacheFileLastModified (line 16) | get cacheFileLastModified(): Date | null {
    method cacheFileLastModifiedGetTime (line 24) | get cacheFileLastModifiedGetTime(): number | null {
    method isCacheValid (line 32) | get isCacheValid(): boolean {
    method save (line 41) | async save(response: Response): Promise<void> {

FILE: src/StaticRenderRegeneration/index.ts
  function staticRenderRegeneration (line 5) | async function staticRenderRegeneration(

FILE: src/StaticRenderRegeneration/types.ts
  type StaticRegenerationOptions (line 1) | interface StaticRegenerationOptions {

FILE: src/StaticRenderRegeneration/utils.ts
  function getUrl (line 1) | function getUrl(request: Request) {
  function readCache (line 12) | function readCache(cacheFilePath: string): Uint8Array | null {
  function hashString (line 21) | async function hashString(message: string): Promise<string> {

FILE: src/Types/EServiceKindError.ts
  type EServiceKindError (line 1) | const enum EServiceKindError {

FILE: src/Types/Request.ts
  type GithubError (line 1) | type GithubError = {
  type GithubErrorResponse (line 6) | type GithubErrorResponse = {
  type GithubExceedError (line 10) | type GithubExceedError = {
  type QueryDefaultResponse (line 15) | type QueryDefaultResponse<T = unknown> = {

FILE: src/Types/ServiceError.ts
  class ServiceError (line 3) | class ServiceError extends Error {
    method constructor (line 4) | constructor(message: string, kind: EServiceKindError) {
    method code (line 11) | get code(): number {

FILE: src/card.ts
  class Card (line 6) | class Card {
    method constructor (line 9) | constructor(
    method render (line 23) | render(
    method getRow (line 67) | private getRow(trophyList: TrophyList) {
    method getHeight (line 74) | private getHeight(row: number) {
    method renderTrophy (line 79) | private renderTrophy(trophyList: TrophyList, theme: Theme) {

FILE: src/config/cache.ts
  class CacheProvider (line 8) | class CacheProvider {
    method constructor (line 12) | private constructor() {}
    method getInstance (line 14) | static getInstance(): CacheProvider {
    method connect (line 21) | async connect(): Promise<void> {
    method get (line 31) | async get(key: string): Promise<Bulk | undefined> {
    method set (line 45) | async set(key: string, value: string): Promise<void> {
    method del (line 60) | async del(key: string): Promise<void> {

FILE: src/error_page.ts
  method constructor (line 4) | constructor(readonly content?: string) {}
  method render (line 5) | render() {
  method renderPage (line 9) | private renderPage() {
  class Error400 (line 117) | class Error400 extends BaseError {
  class Error419 (line 122) | class Error419 extends BaseError {
  class Error404 (line 127) | class Error404 extends BaseError {

FILE: src/pages/Error.ts
  type ErrorPageProps (line 4) | interface ErrorPageProps {
  function ErrorPage (line 8) | function ErrorPage({ error }: ErrorPageProps) {

FILE: src/theme.ts
  constant COLORS (line 1) | const COLORS: { [name: string]: Theme } = {
  type Theme (line 604) | interface Theme {

FILE: src/trophy.ts
  class RankCondition (line 5) | class RankCondition {
    method constructor (line 6) | constructor(
  class Trophy (line 13) | class Trophy {
    method constructor (line 21) | constructor(
    method setRank (line 28) | setRank() {
    method calculateNextRankPercentage (line 42) | private calculateNextRankPercentage() {
    method render (line 61) | render(
  class MultipleLangTrophy (line 107) | class MultipleLangTrophy extends Trophy {
    method constructor (line 108) | constructor(score: number) {
  class AllSuperRankTrophy (line 123) | class AllSuperRankTrophy extends Trophy {
    method constructor (line 124) | constructor(score: number) {
  class Joined2020Trophy (line 139) | class Joined2020Trophy extends Trophy {
    method constructor (line 140) | constructor(score: number) {
  class AncientAccountTrophy (line 155) | class AncientAccountTrophy extends Trophy {
    method constructor (line 156) | constructor(score: number) {
  class LongTimeAccountTrophy (line 171) | class LongTimeAccountTrophy extends Trophy {
    method constructor (line 172) | constructor(score: number) {
  class MultipleOrganizationsTrophy (line 186) | class MultipleOrganizationsTrophy extends Trophy {
    method constructor (line 187) | constructor(score: number) {
  class OGAccountTrophy (line 203) | class OGAccountTrophy extends Trophy {
    method constructor (line 204) | constructor(score: number) {
  class TotalReviewsTrophy (line 220) | class TotalReviewsTrophy extends Trophy {
    method constructor (line 221) | constructor(score: number) {
  class AccountDurationTrophy (line 270) | class AccountDurationTrophy extends Trophy {
    method constructor (line 271) | constructor(score: number) {
  class TotalStarTrophy (line 320) | class TotalStarTrophy extends Trophy {
    method constructor (line 321) | constructor(score: number) {
  class TotalCommitTrophy (line 370) | class TotalCommitTrophy extends Trophy {
    method constructor (line 371) | constructor(score: number) {
  class TotalFollowerTrophy (line 420) | class TotalFollowerTrophy extends Trophy {
    method constructor (line 421) | constructor(score: number) {
  class TotalIssueTrophy (line 470) | class TotalIssueTrophy extends Trophy {
    method constructor (line 471) | constructor(score: number) {
  class TotalPullRequestTrophy (line 520) | class TotalPullRequestTrophy extends Trophy {
    method constructor (line 521) | constructor(score: number) {
  class TotalRepositoryTrophy (line 570) | class TotalRepositoryTrophy extends Trophy {
    method constructor (line 571) | constructor(score: number) {

FILE: src/trophy_list.ts
  class TrophyList (line 22) | class TrophyList {
    method constructor (line 24) | constructor(userInfo: UserInfo) {
    method length (line 47) | get length() {
    method getArray (line 50) | get getArray() {
    method isAllSRank (line 53) | private get isAllSRank() {
    method filterByHidden (line 58) | filterByHidden() {
    method filterByTitles (line 63) | filterByTitles(titles: Array<string>) {
    method filterByRanks (line 68) | filterByRanks(ranks: Array<string>) {
    method filterByExclusionTitles (line 79) | filterByExclusionTitles(titles: Array<string>) {
    method sortByRank (line 89) | sortByRank() {

FILE: src/user_info.ts
  type Language (line 1) | type Language = { name: string };
  type Stargazers (line 2) | type Stargazers = { totalCount: number };
  type Repository (line 3) | type Repository = {
  type GitHubUserRepository (line 8) | type GitHubUserRepository = {
  type GitHubUserIssue (line 15) | type GitHubUserIssue = {
  type GitHubUserPullRequest (line 24) | type GitHubUserPullRequest = {
  type GitHubUserActivity (line 30) | type GitHubUserActivity = {
  type GitHubUserAll (line 45) | type GitHubUserAll =
  class UserInfo (line 50) | class UserInfo {
    method fromCombined (line 66) | static fromCombined(data: GitHubUserAll): UserInfo {
    method constructor (line 70) | constructor(

FILE: src/utils.ts
  class CustomURLSearchParams (line 1) | class CustomURLSearchParams extends URLSearchParams {
    method getStringValue (line 2) | getStringValue(key: string, defaultValue: string): string {
    method getNumberValue (line 11) | getNumberValue(key: string, defaultValue: number): number {
    method getBooleanValue (line 24) | getBooleanValue(key: string, defaultValue: boolean): boolean {
  function parseParams (line 33) | function parseParams(req: Request): CustomURLSearchParams {
  function abridgeScore (line 41) | function abridgeScore(score: number): string {
  constant HOUR_IN_MILLISECONDS (line 51) | const HOUR_IN_MILLISECONDS = 60 * 60 * 1000;
  constant CONSTANTS (line 53) | const CONSTANTS = {
  type RANK (line 70) | enum RANK {
  constant RANK_ORDER (line 83) | const RANK_ORDER = Object.values(RANK);
Condensed preview — 50 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (165K chars).
[
  {
    "path": ".editorconfig",
    "chars": 219,
    "preview": "root = true\n\n[*]\nindent_style = space\nindent_size = 2\nend_of_line = LF\ncharset = utf-8\ntrim_trailing_whitespace = true\ni"
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 64,
    "preview": "# These are supported funding model platforms\n\ngithub: [ryo-ma]\n"
  },
  {
    "path": ".github/workflows/close-old-issues.yml",
    "chars": 705,
    "preview": "name: Close inactive issues\non:\n  schedule:\n    - cron: \"30 1 * * *\"\n\njobs:\n  close-issues:\n    runs-on: ubuntu-latest\n "
  },
  {
    "path": ".github/workflows/test-repository-action.yml",
    "chars": 686,
    "preview": "name: Test GitHub Profile Trophy Action\non: [push, pull_request, workflow_dispatch]\n\njobs:\n  test-trophy:\n    runs-on: u"
  },
  {
    "path": ".github/workflows/testing.yml",
    "chars": 1001,
    "preview": "name: Check PR Test\n\non:\n  push:\n    branches:\n      - master\n  pull_request:\n    branches:\n      - master\n  workflow_di"
  },
  {
    "path": ".gitignore",
    "chars": 47,
    "preview": ".vscode\n.env\n.idea\ndeno.lock\n*.sh\n**/.DS_Store\n"
  },
  {
    "path": ".vercelignore",
    "chars": 45,
    "preview": ".gitignore\n.github\nREADME.md\nLICENSE\ndebug.ts"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 1403,
    "preview": "# Contribution Guide\n\n## Environment\n\n- Deno >= v1.36.1\n- [Vercel](https://vercel.com/)\n- GitHub API v4\n- Docker and Doc"
  },
  {
    "path": "Dockerfile",
    "chars": 190,
    "preview": "FROM denoland/deno:latest\n\n# Create working directory\nWORKDIR /app\n\n# Copy source\nCOPY . .\n\n# Compile the main app\nRUN d"
  },
  {
    "path": "LICENSE",
    "chars": 1050,
    "preview": "Copyright (c) 2020 ryo-ma\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software"
  },
  {
    "path": "README.md",
    "chars": 20915,
    "preview": "<div align=\"center\">\n  <img width=\"140\" src=\"https://user-images.githubusercontent.com/6661165/91657958-61b4fd00-eb00-11"
  },
  {
    "path": "action.yml",
    "chars": 909,
    "preview": "name: Generate GitHub Profile Trophy SVG\ndescription: Run the local generator script to produce an SVG.\ninputs:\n  userna"
  },
  {
    "path": "api/index.ts",
    "chars": 5541,
    "preview": "import { Card } from \"../src/card.ts\";\nimport { CONSTANTS, parseParams } from \"../src/utils.ts\";\nimport { COLORS, Theme "
  },
  {
    "path": "deno.json",
    "chars": 208,
    "preview": "{\n  \"tasks\": {\n    \"start\": \"deno run -A main.ts\",\n    \"debug\": \"deno run --inspect-brk -A main.ts\",\n    \"format\": \"deno"
  },
  {
    "path": "deps.ts",
    "chars": 760,
    "preview": "import { Soxa as ServiceProvider } from \"https://deno.land/x/soxa@1.4/src/core/Soxa.ts\";\nimport { defaults } from \"https"
  },
  {
    "path": "docker-compose.yml",
    "chars": 305,
    "preview": "version: \"3\"\nservices:\n  redis:\n    container_name: trophy-redis\n    image: redis:latest\n    ports:\n      - \"6379:6379\"\n"
  },
  {
    "path": "env-example",
    "chars": 156,
    "preview": "PORT=8080\nGITHUB_TOKEN1=\nGITHUB_TOKEN2=\nGITHUB_API=https://api.github.com/graphql\nENABLE_REDIS=\nREDIS_PORT=6379\nREDIS_HO"
  },
  {
    "path": "main.ts",
    "chars": 187,
    "preview": "import { serve } from \"https://deno.land/std@0.125.0/http/server.ts\";\nimport requestHandler from \"./api/index.ts\";\n\nserv"
  },
  {
    "path": "render_svg.ts",
    "chars": 1840,
    "preview": "import \"https://deno.land/x/dotenv@v0.5.0/load.ts\";\n\nconst username = Deno.args[0];\nconst outputPath = Deno.args[1] ?? \""
  },
  {
    "path": "src/Helpers/Logger.ts",
    "chars": 417,
    "preview": "const enableLogging = Deno.env.get(\"ENV_TYPE\") !== \"test\";\n\nexport class Logger {\n  public static log(message: unknown):"
  },
  {
    "path": "src/Helpers/Retry.ts",
    "chars": 1401,
    "preview": "import { ServiceError } from \"../Types/index.ts\";\nimport { Logger } from \"./Logger.ts\";\n\nexport type RetryCallbackProps "
  },
  {
    "path": "src/Helpers/__tests__/Retry.test.ts",
    "chars": 1334,
    "preview": "import { Retry } from \"../Retry.ts\";\nimport {\n  assertEquals,\n  assertRejects,\n  assertSpyCalls,\n  spy,\n} from \"../../.."
  },
  {
    "path": "src/Repository/GithubRepository.ts",
    "chars": 933,
    "preview": "import { ServiceError } from \"../Types/index.ts\";\nimport {\n  GitHubUserActivity,\n  GitHubUserAll,\n  GitHubUserIssue,\n  G"
  },
  {
    "path": "src/Schemas/index.ts",
    "chars": 2323,
    "preview": "export const queryUserActivity = `\n    query userInfo($username: String!) {\n      user(login: $username) {\n        creat"
  },
  {
    "path": "src/Services/GithubApiService.ts",
    "chars": 3200,
    "preview": "import { GithubRepository } from \"../Repository/GithubRepository.ts\";\nimport {\n  GitHubUserActivity,\n  GitHubUserAll,\n  "
  },
  {
    "path": "src/Services/__mocks__/notFoundUserMock.json",
    "chars": 357,
    "preview": "{\n  \"data\": {\n    \"data\": {\n      \"user\": null\n    },\n    \"errors\": [\n      {\n        \"type\": \"NOT_FOUND\",\n        \"path"
  },
  {
    "path": "src/Services/__mocks__/rateLimitMock.json",
    "chars": 642,
    "preview": "{\n  \"exceeded\": {\n    \"data\": {\n      \"documentation_url\": \"https://docs.github.com/en/free-pro-team@latest/rest/overvie"
  },
  {
    "path": "src/Services/__mocks__/successGithubResponse.json",
    "chars": 36502,
    "preview": "{\n  \"data\": {\n    \"data\": {\n      \"user\": {\n        \"repositories\": {\n          \"totalCount\": 128,\n          \"nodes\": [\n"
  },
  {
    "path": "src/Services/__tests__/githubApiService.test.ts",
    "chars": 3801,
    "preview": "import { GithubApiService } from \"../GithubApiService.ts\";\nimport { assertEquals, returnsNext, soxa, stub } from \"../../"
  },
  {
    "path": "src/Services/request.ts",
    "chars": 1448,
    "preview": "import { soxa } from \"../../deps.ts\";\nimport {\n  EServiceKindError,\n  GithubError,\n  GithubErrorResponse,\n  GithubExceed"
  },
  {
    "path": "src/StaticRenderRegeneration/cache_manager.ts",
    "chars": 1456,
    "preview": "import { Logger } from \"../Helpers/Logger.ts\";\nimport { existsSync } from \"./utils.ts\";\n\nexport class CacheManager {\n  c"
  },
  {
    "path": "src/StaticRenderRegeneration/index.ts",
    "chars": 1108,
    "preview": "import { CacheManager } from \"./cache_manager.ts\";\nimport { StaticRegenerationOptions } from \"./types.ts\";\nimport { getU"
  },
  {
    "path": "src/StaticRenderRegeneration/types.ts",
    "chars": 207,
    "preview": "export interface StaticRegenerationOptions {\n  // The number of milliseconds before the page should be revalidated\n  rev"
  },
  {
    "path": "src/StaticRenderRegeneration/utils.ts",
    "chars": 994,
    "preview": "export function getUrl(request: Request) {\n  try {\n    return new URL(request.url);\n  } catch {\n    return {\n      pathn"
  },
  {
    "path": "src/Types/EServiceKindError.ts",
    "chars": 98,
    "preview": "export const enum EServiceKindError {\n  RATE_LIMIT = \"RATE_LIMITED\",\n  NOT_FOUND = \"NOT_FOUND\",\n}\n"
  },
  {
    "path": "src/Types/Request.ts",
    "chars": 381,
    "preview": "export type GithubError = {\n  message: string;\n  type: string;\n};\n\nexport type GithubErrorResponse = {\n  errors: GithubE"
  },
  {
    "path": "src/Types/ServiceError.ts",
    "chars": 486,
    "preview": "import { EServiceKindError } from \"./EServiceKindError.ts\";\n\nexport class ServiceError extends Error {\n  constructor(mes"
  },
  {
    "path": "src/Types/index.ts",
    "chars": 105,
    "preview": "export * from \"./Request.ts\";\nexport * from \"./ServiceError.ts\";\nexport * from \"./EServiceKindError.ts\";\n"
  },
  {
    "path": "src/card.ts",
    "chars": 2728,
    "preview": "import { UserInfo } from \"./user_info.ts\";\nimport { TrophyList } from \"./trophy_list.ts\";\nimport { Trophy } from \"./trop"
  },
  {
    "path": "src/config/cache.ts",
    "chars": 1829,
    "preview": "import { Bulk, connect, Redis } from \"../../deps.ts\";\nimport { Logger } from \"../Helpers/Logger.ts\";\nimport { CONSTANTS "
  },
  {
    "path": "src/error_page.ts",
    "chars": 3091,
    "preview": "abstract class BaseError {\n  readonly status!: number;\n  readonly message!: string;\n  constructor(readonly content?: str"
  },
  {
    "path": "src/icons.ts",
    "chars": 10714,
    "preview": "import { RANK } from \"./utils.ts\";\nimport { Theme } from \"./theme.ts\";\n\nconst leafIcon = (laurel: string): string => {\n "
  },
  {
    "path": "src/pages/Error.ts",
    "chars": 562,
    "preview": "import { EServiceKindError, ServiceError } from \"../Types/index.ts\";\nimport { Error400, Error404, Error419 } from \"../er"
  },
  {
    "path": "src/theme.ts",
    "chars": 16844,
    "preview": "export const COLORS: { [name: string]: Theme } = {\n  default: {\n    BACKGROUND: \"#FFF\",\n    TITLE: \"#000\",\n    ICON_CIRC"
  },
  {
    "path": "src/trophy.ts",
    "chars": 13883,
    "preview": "import { getNextRankBar, getTrophyIcon } from \"./icons.ts\";\nimport { abridgeScore, CONSTANTS, RANK, RANK_ORDER } from \"."
  },
  {
    "path": "src/trophy_list.ts",
    "chars": 2917,
    "preview": "import {\n  AccountDurationTrophy,\n  AllSuperRankTrophy,\n  AncientAccountTrophy,\n  Joined2020Trophy,\n  LongTimeAccountTro"
  },
  {
    "path": "src/user_info.ts",
    "chars": 4230,
    "preview": "type Language = { name: string };\ntype Stargazers = { totalCount: number };\ntype Repository = {\n  languages: { nodes: La"
  },
  {
    "path": "src/utils.ts",
    "chars": 2191,
    "preview": "export class CustomURLSearchParams extends URLSearchParams {\n  getStringValue(key: string, defaultValue: string): string"
  },
  {
    "path": "test/test.ts",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "vercel.json",
    "chars": 223,
    "preview": "{\n  \"public\": true,\n  \"functions\": {\n    \"api/**/*.[jt]s\": {\n      \"runtime\": \"vercel-deno@3.1.1\",\n      \"maxDuration\": "
  }
]

About this extraction

This page contains the full source code of the ryo-ma/github-profile-trophy GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 50 files (149.1 KB), approximately 42.3k tokens, and a symbol index with 137 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!