1+ import React , { useState , useEffect } from 'react' ;
2+ import styles from './GitHubStarButton.module.css' ;
3+ import { translate } from '@docusaurus/Translate' ;
4+
5+ // 配置信息
6+ const GITHUB_CLIENT_ID = 'Ov23libkfFAeZk8u5wFL' ; // 您的GitHub OAuth应用Client ID
7+ const TARGET_REPO = 'LianjiaTech/bella-openapi' ; // 您的目标仓库
8+ const GATEKEEPER_URL = 'https://api.bella.top/gatekeeper' ; // http://gatekeeper.yourdomain.com
9+ interface TokenResponse {
10+ token : string ;
11+ }
12+
13+ const GitHubStarButton : React . FC = ( ) => {
14+ const [ isAuthenticated , setIsAuthenticated ] = useState < boolean > ( false ) ;
15+ const [ isStarred , setIsStarred ] = useState < boolean > ( false ) ;
16+ const [ isLoading , setIsLoading ] = useState < boolean > ( false ) ;
17+ const [ error , setError ] = useState < string | null > ( null ) ;
18+
19+ const errorStr = translate ( {
20+ id : 'project.GitHub.Stared.Failed' ,
21+ message : '点赞操作失败,请刷新后重试' ,
22+ description : 'Github Star Failed'
23+ } ) ;
24+
25+ // 当组件加载时检查认证状态和是否已点赞
26+ useEffect ( ( ) => {
27+ // 检查是否已登录(本地存储的token)
28+ const token = localStorage . getItem ( 'github_token' ) ;
29+ if ( token ) {
30+ setIsAuthenticated ( true ) ;
31+ // 检查用户是否已经为仓库点赞
32+ checkIfStarred ( token ) ;
33+ }
34+
35+ // 检查URL中是否有授权码
36+ if ( typeof window !== 'undefined' ) {
37+ const urlParams = new URLSearchParams ( window . location . search ) ;
38+ const code = urlParams . get ( 'code' ) ;
39+
40+ if ( code ) {
41+ // 有授权码,通过Gatekeeper交换获取token
42+ exchangeCodeForToken ( code ) ;
43+ }
44+ }
45+ } , [ ] ) ;
46+
47+ // 检查用户是否已经为仓库点赞
48+ const checkIfStarred = async ( token : string ) : Promise < void > => {
49+ try {
50+ const response = await fetch ( `https://api.github.com/user/starred/${ TARGET_REPO } ` , {
51+ headers : {
52+ Authorization : `token ${ token } ` ,
53+ Accept : 'application/vnd.github.v3+json' ,
54+ } ,
55+ } ) ;
56+
57+ // 204表示找到了,404表示没有找到(未点赞)
58+ setIsStarred ( response . status === 204 ) ;
59+ } catch ( err ) {
60+ console . error ( '检查星标状态失败:' , err ) ;
61+ setIsStarred ( false ) ;
62+ }
63+ } ;
64+
65+ // 使用授权码通过Gatekeeper交换token
66+ const exchangeCodeForToken = async ( code : string ) : Promise < void > => {
67+ setIsLoading ( true ) ;
68+ setError ( null ) ;
69+
70+ try {
71+ // 使用Gatekeeper交换token
72+ const response = await fetch ( `${ GATEKEEPER_URL } /authenticate/${ code } ` , {
73+ method : 'GET' ,
74+ headers : {
75+ Accept : 'application/json'
76+ }
77+ } ) ;
78+
79+ if ( response . ok ) {
80+ const data = await response . json ( ) as TokenResponse ;
81+ const token = data . token ; // Gatekeeper返回的格式
82+
83+ if ( token ) {
84+ // 存储token到本地
85+ localStorage . setItem ( 'github_token' , token ) ;
86+ setIsAuthenticated ( true ) ;
87+
88+ // 检查是否已点赞
89+ await checkIfStarred ( token ) ;
90+
91+ // 清除URL中的code参数
92+ if ( typeof window !== 'undefined' && window . history . replaceState ) {
93+ const newUrl = window . location . pathname +
94+ ( window . location . search ? window . location . search . replace ( / [ ? & ] c o d e = [ ^ & ] + / , '' ) : '' ) ;
95+ window . history . replaceState ( { } , document . title , newUrl ) ;
96+ }
97+
98+ // 自动执行点赞操作
99+ await performStar ( token ) ;
100+ } else {
101+ throw new Error ( '响应中没有token' ) ;
102+ }
103+ } else {
104+ throw new Error ( '获取token失败' ) ;
105+ }
106+ } catch ( err ) {
107+ setError ( errorStr ) ;
108+ console . error ( 'Token交换失败:' , err ) ;
109+ } finally {
110+ setIsLoading ( false ) ;
111+ }
112+ } ;
113+
114+ // 执行点赞操作
115+ const performStar = async ( token : string ) : Promise < void > => {
116+ // 如果已经点赞了,什么都不做
117+ if ( isStarred ) {
118+ return ;
119+ }
120+
121+ try {
122+ const response = await fetch ( `https://api.github.com/user/starred/${ TARGET_REPO } ` , {
123+ method : 'PUT' ,
124+ headers : {
125+ Authorization : `token ${ token } ` ,
126+ Accept : 'application/vnd.github.v3+json' ,
127+ 'Content-Length' : '0' ,
128+ } ,
129+ } ) ;
130+
131+ if ( response . status === 204 ) {
132+ setIsStarred ( true ) ;
133+ } else {
134+ throw new Error ( '点赞操作失败' ) ;
135+ }
136+ } catch ( err ) {
137+ setError ( errorStr ) ;
138+ console . error ( 'Star操作失败:' , err ) ;
139+ }
140+ } ;
141+
142+ // 处理登录
143+ const handleLogin = ( ) : void => {
144+ const redirectUri = window . location . href . split ( '?' ) [ 0 ] ; // 移除现有的查询参数
145+ const authUrl = `https://github.com/login/oauth/authorize?client_id=${ GITHUB_CLIENT_ID } &redirect_uri=${ encodeURIComponent ( redirectUri ) } &scope=public_repo` ;
146+
147+ // 跳转到GitHub授权页面
148+ window . location . href = authUrl ;
149+ } ;
150+
151+ // 处理点赞操作
152+ const handleStar = async ( ) : Promise < void > => {
153+ if ( ! isAuthenticated ) {
154+ handleLogin ( ) ;
155+ return ;
156+ }
157+
158+ // 如果已经点赞了,什么都不做
159+ if ( isStarred ) {
160+ return ;
161+ }
162+
163+ setIsLoading ( true ) ;
164+ const token = localStorage . getItem ( 'github_token' ) ;
165+
166+ if ( ! token ) {
167+ setError ( errorStr ) ;
168+ setIsLoading ( false ) ;
169+ return ;
170+ }
171+
172+ try {
173+ await performStar ( token ) ;
174+ } finally {
175+ setIsLoading ( false ) ;
176+ }
177+ } ;
178+
179+ return (
180+ < div className = { styles . container } >
181+ { error && < div className = { styles . error } > { error } </ div > }
182+
183+ < button
184+ onClick = { handleStar }
185+ className = { `${ styles . starButton } ${ isStarred ? styles . starred : '' } ` }
186+ disabled = { isLoading }
187+ >
188+ < svg className = { styles . icon } viewBox = "0 0 16 16" width = "16" height = "16" aria-hidden = "true" >
189+ < path fillRule = "evenodd" d = "M8 .25a.75.75 0 01.673.418l1.882 3.815 4.21.612a.75.75 0 01.416 1.279l-3.046 2.97.719 4.192a.75.75 0 01-1.088.791L8 12.347l-3.766 1.98a.75.75 0 01-1.088-.79l.72-4.194L.818 6.374a.75.75 0 01.416-1.28l4.21-.611L7.327.668A.75.75 0 018 .25zm0 2.445L6.615 5.5a.75.75 0 01-.564.41l-3.097.45 2.24 2.184a.75.75 0 01.216.664l-.528 3.084 2.769-1.456a.75.75 0 01.698 0l2.77 1.456-.53-3.084a.75.75 0 01.216-.664l2.24-2.183-3.096-.45a.75.75 0 01-.564-.41L8 2.694v.001z" > </ path >
190+ </ svg >
191+ { isStarred ? translate ( {
192+ id : 'project.GitHub.Stared' ,
193+ message : '已点赞' ,
194+ description : 'Github Stared'
195+ } ) : translate ( {
196+ id : 'project.GitHub.Star' ,
197+ message : '点赞' ,
198+ description : 'Github Star'
199+ } ) }
200+ </ button >
201+ </ div >
202+ ) ;
203+ } ;
204+
205+ export default GitHubStarButton ;
0 commit comments