├── .gitignore ├── package.json ├── vercel.json ├── LICENSE ├── README.md ├── api └── rating.ts └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .vercel 3 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "codeforces-rating", 3 | "author": "Baoshuo ", 4 | "license": "MIT", 5 | "dependencies": { 6 | "@vercel/node": "^1.14.0", 7 | "node-fetch": "2" 8 | }, 9 | "devDependencies": { 10 | "typescript": "^4.6.3" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "routes": [ 3 | { 4 | "src": "/rating", 5 | "dest": "/api/rating.ts" 6 | }, 7 | { 8 | "src": "/", 9 | "status": 302, 10 | "headers": { 11 | "Location": "https://baoshuo.ren/services/codeforces-rating-badge/" 12 | } 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Baoshuo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Codeforces Rating Badge 2 | 3 | ## Demo 4 | 5 | ![](https://cfrating.baoshuo.dev/rating?username=baoshuo) 6 | 7 | ## Deploy 8 | 9 | [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Frenbaoshuo%2Fcodeforces-rating) 10 | 11 | ## Usage 12 | 13 | Just insert the link into your website as an image, like this: `` 14 | 15 | Do not forget to replace the default configuration with your own! 16 | 17 | --- 18 | 19 | Parameter list: 20 | 21 | | Name | Value | Required | Description | 22 | | -------- | ----------------------------------------------- | -------- | -------------------------------------------------- | 23 | | username | string | Yes | Your Codeforces username | 24 | | style | [shields.io styles](https://shields.io/#styles) | No | The style of the badge, default is `for-the-badge` | 25 | 26 | ## Author 27 | 28 | **Codeforces Rating Badge** © [Baoshuo](https://github.com/renbaoshuo), Released under the [MIT](./LICENSE) License.
29 | Authored and maintained by Baoshuo with help from contributors ([list](https://github.com/renbaoshuo/codeforces-rating/graphs/contributors)). 30 | 31 | > [Personal Website](https://baoshuo.ren) · [Blog](https://blog.baoshuo.ren) · GitHub [@renbaoshuo](https://github.com/renbaoshuo) · Twitter [@renbaoshuo](https://twitter.com/renbaoshuo) 32 | -------------------------------------------------------------------------------- /api/rating.ts: -------------------------------------------------------------------------------- 1 | import fetch from 'node-fetch'; 2 | import type { VercelRequest, VercelResponse } from '@vercel/node'; 3 | 4 | interface UserRatingInfo { 5 | rating: number; 6 | text: string; 7 | } 8 | 9 | function escape(username: string) { 10 | return encodeURIComponent(username.replace(/-/g, '--').replace(/_/g, '__')); 11 | } 12 | 13 | function getRatingColor(rating: number) { 14 | if (rating >= 2400) return 'ff0000'; 15 | if (rating >= 2100) return 'ff8c00'; 16 | if (rating >= 1900) return 'aa00aa'; 17 | if (rating >= 1600) return '0000ff'; 18 | if (rating >= 1400) return '03a89e'; 19 | if (rating >= 1200) return '008000'; 20 | return '808080'; 21 | } 22 | 23 | async function fetchData(username: string): Promise { 24 | const res = await fetch(`https://codeforces.com/api/user.info?handles=${username}`); 25 | 26 | if (!res.ok) return { rating: 0, text: 'N/A' }; 27 | 28 | const data = await res.json(); 29 | const { rank, rating } = data.result[0]; 30 | const text = rank && rating ? `${rank} ${rating}` : 'Unrated'; 31 | 32 | return { rating, text }; 33 | } 34 | 35 | async function getBadgeImage(username: string, data: UserRatingInfo, style: string) { 36 | const color = getRatingColor(data.rating); 37 | const escapedUsername = escape(username); 38 | const escapedRatingText = escape(data.text); 39 | 40 | const params = new URLSearchParams({ 41 | longCache: 'true', 42 | style, 43 | logo: 'codeforces', 44 | link: `https://codeforces.com/profile/${username}`, 45 | }); 46 | 47 | const res = await fetch( 48 | `https://img.shields.io/badge/${escapedUsername}-${escapedRatingText}-${color}.svg?${params.toString()}` 49 | ); 50 | 51 | if (!res.ok) throw 'error'; 52 | return await res.text(); 53 | } 54 | 55 | export default async (request: VercelRequest, response: VercelResponse) => { 56 | let { username = 'baoshuo', style = 'for-the-badge' } = request.query; 57 | 58 | if (Array.isArray(username)) username = username[0]; 59 | if (Array.isArray(style)) style = style[0]; 60 | 61 | const data = await fetchData(username as string).catch(() => ({ rating: 0, text: 'N/A' })); 62 | getBadgeImage(username as string, data, style as string) 63 | .then((data) => { 64 | response 65 | .status(200) 66 | .setHeader('Content-Type', 'image/svg+xml;charset=utf-8') 67 | .setHeader('Cache-Control', 'public, max-age=43200') // 43200s(12h) cache 68 | .send(data); 69 | }) 70 | .catch(() => { 71 | response.status(500).send('error'); 72 | }); 73 | }; 74 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@types/node@*": 6 | version "17.0.23" 7 | resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.23.tgz#3b41a6e643589ac6442bdbd7a4a3ded62f33f7da" 8 | integrity sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw== 9 | 10 | "@vercel/node-bridge@2.2.0": 11 | version "2.2.0" 12 | resolved "https://registry.yarnpkg.com/@vercel/node-bridge/-/node-bridge-2.2.0.tgz#9f77e68733d782bb6edd1e86c2d2eae8e4ca7a3c" 13 | integrity sha512-ydYlZyIQfsuriF6qTt/F4vaAt+nb4ZKhLEl2o5AQFa5ED7LoPS5w01Xbujy+25pqS1ODu8/bsqOCUSX8y/+tSQ== 14 | 15 | "@vercel/node@^1.14.0": 16 | version "1.14.0" 17 | resolved "https://registry.yarnpkg.com/@vercel/node/-/node-1.14.0.tgz#93307fbb99950a3a1caf1183b815c50ee64e6e1a" 18 | integrity sha512-UAb1qQMeTkSXz95yljJpz0MyTzcj++B/1XR0vKFZOs2CEfSkBO1DjzX6BkaKriaihBdbGM/X9UoMJfWfUBBn8Q== 19 | dependencies: 20 | "@types/node" "*" 21 | "@vercel/node-bridge" "2.2.0" 22 | ts-node "8.9.1" 23 | typescript "4.3.4" 24 | 25 | arg@^4.1.0: 26 | version "4.1.3" 27 | resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" 28 | integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== 29 | 30 | buffer-from@^1.0.0: 31 | version "1.1.2" 32 | resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" 33 | integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== 34 | 35 | diff@^4.0.1: 36 | version "4.0.2" 37 | resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" 38 | integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== 39 | 40 | make-error@^1.1.1: 41 | version "1.3.6" 42 | resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" 43 | integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== 44 | 45 | node-fetch@2: 46 | version "2.6.7" 47 | resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" 48 | integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== 49 | dependencies: 50 | whatwg-url "^5.0.0" 51 | 52 | source-map-support@^0.5.17: 53 | version "0.5.21" 54 | resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" 55 | integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== 56 | dependencies: 57 | buffer-from "^1.0.0" 58 | source-map "^0.6.0" 59 | 60 | source-map@^0.6.0: 61 | version "0.6.1" 62 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" 63 | integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== 64 | 65 | tr46@~0.0.3: 66 | version "0.0.3" 67 | resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" 68 | integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= 69 | 70 | ts-node@8.9.1: 71 | version "8.9.1" 72 | resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.9.1.tgz#2f857f46c47e91dcd28a14e052482eb14cfd65a5" 73 | integrity sha512-yrq6ODsxEFTLz0R3BX2myf0WBCSQh9A+py8PBo1dCzWIOcvisbyH6akNKqDHMgXePF2kir5mm5JXJTH3OUJYOQ== 74 | dependencies: 75 | arg "^4.1.0" 76 | diff "^4.0.1" 77 | make-error "^1.1.1" 78 | source-map-support "^0.5.17" 79 | yn "3.1.1" 80 | 81 | typescript@4.3.4: 82 | version "4.3.4" 83 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.4.tgz#3f85b986945bcf31071decdd96cf8bfa65f9dcbc" 84 | integrity sha512-uauPG7XZn9F/mo+7MrsRjyvbxFpzemRjKEZXS4AK83oP2KKOJPvb+9cO/gmnv8arWZvhnjVOXz7B49m1l0e9Ew== 85 | 86 | typescript@^4.6.3: 87 | version "4.6.3" 88 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.3.tgz#eefeafa6afdd31d725584c67a0eaba80f6fc6c6c" 89 | integrity sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw== 90 | 91 | webidl-conversions@^3.0.0: 92 | version "3.0.1" 93 | resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" 94 | integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= 95 | 96 | whatwg-url@^5.0.0: 97 | version "5.0.0" 98 | resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" 99 | integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0= 100 | dependencies: 101 | tr46 "~0.0.3" 102 | webidl-conversions "^3.0.0" 103 | 104 | yn@3.1.1: 105 | version "3.1.1" 106 | resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" 107 | integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== 108 | --------------------------------------------------------------------------------