Skip to content

Commit

Permalink
Merge branch 'master' of github.com:liuhuapiaoyuan/chatgpt-mirror-server
Browse files Browse the repository at this point in the history
  • Loading branch information
liuhuapiaoyuan committed Jan 19, 2024
2 parents fcb5752 + 0d38e0b commit 59e2daa
Show file tree
Hide file tree
Showing 24 changed files with 133 additions and 13 deletions.
3 changes: 2 additions & 1 deletion api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ func init() {
group.GET("/gpts/editor", Editor)
group.GET("/gpts/editor/:slug", Slug)
group.GET("/g/:gizmoId/c/:convId", GC)
group.GET(("/gpts/mine"), Mine)
group.GET("/gpts/mine", Mine)
group.GET("/gpts", Gpts)

group.GET("/login", Login)
group.GET("/share/:shareId", Share)
Expand Down
69 changes: 61 additions & 8 deletions api/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func Index(r *ghttp.Request) {
if model != "" {
propsJson.Set("query.model", model)
}
propsJson.Set("buildId", config.CacheBuildId)
propsJson.Set("buildId", config.BuildId)
propsJson.Set("assetPrefix", config.AssetPrefix)

r.Response.WriteTpl(config.CacheBuildId+"/chat.html", g.Map{
Expand Down Expand Up @@ -117,7 +117,7 @@ func C(r *ghttp.Request) {

propsJson := gjson.New(props)
propsJson.Set("query.default.1", convId)
propsJson.Set("buildId", config.CacheBuildId)
propsJson.Set("buildId", config.BuildId)
propsJson.Set("assetPrefix", config.AssetPrefix)

r.Response.WriteTpl(config.CacheBuildId+"/chat.html", g.Map{
Expand All @@ -128,6 +128,59 @@ func C(r *ghttp.Request) {
})
}

// Gpts
func Gpts(r *ghttp.Request) {

if r.Session.MustGet("offical-session").IsEmpty() {
r.Session.RemoveAll()
r.Response.RedirectTo("/login")
return
}
props := `
{
"props": {
"pageProps": {
"user": {
"id": "user-xyhelper",
"name": "[email protected]",
"email": "[email protected]",
"image": "/avatars.png",
"picture": "/avatars.png",
"idp": "auth0",
"iat": 2699699364,
"mfa": false,
"groups": []
},
"serviceStatus": {},
"userCountry": "US",
"serviceAnnouncement": { "public": {}, "paid": {} },
"serverPrimedAllowBrowserStorageValue": true,
"canManageBrowserStorage": false,
"ageVerificationDeadline": null,
"showCookieConsentBanner": false
},
"__N_SSP": true
},
"page": "/gpts",
"query": {},
"buildId": "wtXFegAXt6bfbujLr1e7S",
"assetPrefix": "",
"isFallback": false,
"gssp": true,
"scriptLoader": []
}
`
propsJson := gjson.New(props)
propsJson.Set("buildId", config.BuildId)

r.Response.WriteTpl(config.CacheBuildId+"/gpts.html", g.Map{
"arkoseUrl": config.ArkoseUrl,
"props": propsJson,
"assetPrefix": config.AssetPrefix,
"envScript": config.GetEnvScript(r.GetCtx()),
})
}

// Discovery 发现
func Discovery(r *ghttp.Request) {

Expand Down Expand Up @@ -171,7 +224,7 @@ func Discovery(r *ghttp.Request) {
}
`
propsJson := gjson.New(props)
propsJson.Set("buildId", config.CacheBuildId)
propsJson.Set("buildId", config.BuildId)

r.Response.WriteTpl(config.CacheBuildId+"/discovery.html", g.Map{
"arkoseUrl": config.ArkoseUrl,
Expand Down Expand Up @@ -225,7 +278,7 @@ func Editor(r *ghttp.Request) {
}
`
propsJson := gjson.New(props)
propsJson.Set("buildId", config.CacheBuildId)
propsJson.Set("buildId", config.BuildId)
propsJson.Set("assetPrefix", config.AssetPrefix)

// if slug != "" {
Expand Down Expand Up @@ -289,7 +342,7 @@ func Slug(r *ghttp.Request) {
propsJson := gjson.New(props)

propsJson.Set("query.slug", slug)
propsJson.Set("buildId", config.CacheBuildId)
propsJson.Set("buildId", config.BuildId)
propsJson.Set("assetPrefix", config.AssetPrefix)

r.Response.WriteTpl(config.CacheBuildId+"/slug.html", g.Map{
Expand Down Expand Up @@ -347,7 +400,7 @@ func G(r *ghttp.Request) {
`
propsJson := gjson.New(props)
propsJson.Set("query.gizmoId", gizmoId)
propsJson.Set("buildId", config.CacheBuildId)
propsJson.Set("buildId", config.BuildId)
propsJson.Set("assetPrefix", config.AssetPrefix)

r.Response.WriteTpl(config.CacheBuildId+"/g.html", g.Map{
Expand Down Expand Up @@ -409,7 +462,7 @@ func GC(r *ghttp.Request) {
propsJson := gjson.New(props)
propsJson.Set("query.gizmoId", gizmoId)
propsJson.Set("query.convId", convId)
propsJson.Set("buildId", config.CacheBuildId)
propsJson.Set("buildId", config.BuildId)

r.Response.WriteTpl(config.CacheBuildId+"/gc.html", g.Map{
"arkoseUrl": config.ArkoseUrl,
Expand Down Expand Up @@ -464,7 +517,7 @@ func Mine(r *ghttp.Request) {
"scriptLoader": []
}`
propsJson := gjson.New(props)
propsJson.Set("buildId", config.CacheBuildId)
propsJson.Set("buildId", config.BuildId)
propsJson.Set("assetPrefix", config.AssetPrefix)

r.Response.WriteTpl(config.CacheBuildId+"/mine.html", g.Map{
Expand Down
14 changes: 11 additions & 3 deletions backend-api/backend-api.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ var (
func init() {
s := g.Server()
s.BindHandler("/backend-api/*any", ProxyAll)
s.BindHandler("/_next/data/*any", NextDataGptsFixed)
// s.BindHandler("/public-api/*any", ProxyAll)
// s.BindHandler("/_next/data/*any", NextDataGptsFixed)
backendGroup := s.Group("/backend-api")
backendGroup.POST("/accounts/data_export", NotFound) // 禁用导出
backendGroup.POST("/payments/checkout", NotFound) // 禁用支付
Expand All @@ -49,8 +50,13 @@ func ProxyAll(r *ghttp.Request) {

ctx := r.GetCtx()
// 获取header中的token Authorization: Bearer xxx 去掉Bearer
userToken := ""
Authorization := r.Header.Get("Authorization")
if Authorization != "" {
userToken = r.Header.Get("Authorization")[7:]
}
g.Log().Debug(ctx, "userToken", userToken)

userToken := r.Header.Get("Authorization")[7:]
isStream := strings.Contains(r.Header.Get("accept"), "text/event-stream")
// 获得当前的请求域名
// g.Log().Debug(ctx, "ProxyAll", r.URL.Path, r.Header.Get("accept"), isStream)
Expand Down Expand Up @@ -78,10 +84,12 @@ func ProxyAll(r *ghttp.Request) {
newreq.URL.Scheme = u.Scheme
newreq.Host = u.Host
newreq.Header.Set("authkey", config.AUTHKEY(ctx))
newreq.Header.Set("Authorization", "Bearer "+accessToken)

newreq.Header.Set("Host", "chat.openai.com")
newreq.Header.Set("Origin", "https://chat.openai.com/chat")
if accessToken != "" {
newreq.Header.Set("Authorization", "Bearer "+accessToken)
}

// g.Dump(newreq.URL)
cdnhost := config.CDNHOST(ctx)
Expand Down
1 change: 1 addition & 0 deletions backend-api/me.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ func Me(r *ghttp.Request) {
res, err := g.Client().SetHeaderMap(map[string]string{
"Authorization": "Bearer " + AccessToken,
"User-Agent": r.Header.Get("User-Agent"),
"authKey": config.AUTHKEY(ctx),
}).Get(ctx, UpStream+"/backend-api/me")
if err != nil {
r.Response.WriteStatus(http.StatusUnauthorized)
Expand Down
2 changes: 1 addition & 1 deletion config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ modules:
enable: 1

# 接入网关地址
CHATPROXY: "https://chatgpt.ggss.club/gateway"
CHATPROXY: "https://chatgpt.ggss.club/gateway"
ARKOSE_URL: "https://chatgpt.ggss.club/arkose/v2/"
# 接入网关的authkey
AUTHKEY: "maidou"
Expand Down
1 change: 1 addition & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
_ "chatgpt-mirror-server/api"
_ "chatgpt-mirror-server/backend-api"
_ "chatgpt-mirror-server/modules"
_ "chatgpt-mirror-server/public-api"

"github.com/gogf/gf/v2/os/gctx"

Expand Down
31 changes: 31 additions & 0 deletions public-api/proxypublic.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package publicapi

import (
"chatgpt-mirror-server/config"
"net/http"
"net/http/httputil"
"net/url"

"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
)

func ProxyPublic(r *ghttp.Request) {
ctx := r.GetCtx()
u, _ := url.Parse(config.CHATPROXY(ctx))
proxy := httputil.NewSingleHostReverseProxy(u)
proxy.ErrorHandler = func(writer http.ResponseWriter, request *http.Request, e error) {
g.Log().Error(ctx, e)
writer.WriteHeader(http.StatusBadGateway)
}
newreq := r.Request.Clone(ctx)
newreq.URL.Host = u.Host
newreq.URL.Scheme = u.Scheme
newreq.Host = u.Host
newreq.Header.Set("authkey", config.AUTHKEY(ctx))

// newreq.Header.Set("Cookie", "__Secure-next-auth.session-token="+carinfo.RefreshCookie)
// // 去除header 中的 压缩
// newreq.Header.Del("Accept-Encoding")
proxy.ServeHTTP(r.Response.Writer.RawWriter(), newreq)
}
9 changes: 9 additions & 0 deletions public-api/public-api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package publicapi

import "github.com/gogf/gf/v2/frame/g"

func init() {
s := g.Server()
publicApiGroup := s.Group("/public-api")
publicApiGroup.ALL("/*", ProxyPublic)
}
1 change: 1 addition & 0 deletions resource/template/Rp5H7oULko6u7cJEjMucd/chat.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<!DOCTYPE html><html><head>{{.envScript}}<meta charSet="utf-8" /><link rel="apple-touch-icon" sizes="180x180" href="{{.assetPrefix}}/_next/static/media/apple-touch-icon.59f2e898.png" /><link rel="icon" type="image/png" sizes="32x32" href="{{.assetPrefix}}/_next/static/media/favicon-32x32.be48395e.png" /><link rel="icon" type="image/png" sizes="16x16" href="{{.assetPrefix}}/_next/static/media/favicon-16x16.9b8dbb69.png" /><link rel="preconnect" href="{{.assetPrefix}}" /><title>ChatGPT</title><meta name="title" content="ChatGPT: Get instant answers, find inspiration, learn something new" /><meta name="description" content="ChatGPT is a free-to-use AI system. Use it for engaging conversations, gain insights, automate tasks, and witness the future of AI, all in one place." /><meta name="keywords" content="ai chat,ai,chap gpt,chat gbt,chat gpt 3,chat gpt login,chat gpt website,chat gpt,chat gtp,chat openai,chat,chatai,chatbot gpt,chatg,chatgpt login,chatgpt,gpt chat,open ai,openai chat,openai chatgpt,openai" /><meta property="og:description" content="A conversational AI system that listens, learns, and challenges" /><meta name="robots" content="index, follow" /><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><meta property="og:title" content="ChatGPT" /><meta property="og:image" content="{{.assetPrefix}}/_next/static/media/chatgpt-share-og.49cbbffe.png" /><meta property="og:url" content="https://chat.openai.com" /><meta name="apple-itunes-app" content="app-id=6448311069" /><meta name="next-head-count" content="17" /><link rel="preload" href="{{.assetPrefix}}/_next/static/media/soehne-buch.13189857.woff2" as="font" crossorigin /><link rel="preload" href="{{.assetPrefix}}/_next/static/media/soehne-halbfett.977f1845.woff2" as="font" crossorigin /><link rel="preload" href="{{.assetPrefix}}/_next/static/media/soehne-mono-buch.de677af1.woff2" as="font" crossorigin /><link rel="preload" href="{{.assetPrefix}}/_next/static/media/soehne-mono-halbfett.b082e8e2.woff2" as="font" crossorigin /><link data-next-font rel="preconnect" href="/" crossorigin="anonymous" /><link rel="preload" href="{{.assetPrefix}}/_next/static/css/36edb078e559dff3.css" as="style" /><link rel="stylesheet" href="{{.assetPrefix}}/_next/static/css/36edb078e559dff3.css" data-n-g /><noscript data-n-css></noscript><script defer nomodule src="{{.assetPrefix}}/_next/static/chunks/polyfills-c67a75d1b6f99dc8.js"></script><script src="{{.assetPrefix}}/_next/static/chunks/webpack-dcda336ac9faabba.js" defer></script><script src="{{.assetPrefix}}/_next/static/chunks/framework-31ffa51d236da229.js" defer></script><script src="{{.assetPrefix}}/_next/static/chunks/main-abb0536823058f9d.js" defer></script><script src="{{.assetPrefix}}/_next/static/chunks/pages/_app-81907334446d37b6.js" defer></script><script src="{{.assetPrefix}}/_next/static/chunks/pages/%5B%5B...default%5D%5D-12836b0c1baa69d7.js" defer></script><script src="{{.assetPrefix}}/_next/static/Rp5H7oULko6u7cJEjMucd/_buildManifest.js" defer></script><script src="{{.assetPrefix}}/_next/static/Rp5H7oULko6u7cJEjMucd/_ssgManifest.js" defer></script></head><body class="antialiased"><div id="__next"><script>!function(){try{var d=document.documentElement,c=d.classList;c.remove('light','dark');var e=localStorage.getItem('theme');if('system'===e||(!e&&true)){var t='(prefers-color-scheme: dark)',m=window.matchMedia(t);if(m.media!==t||m.matches){d.style.colorScheme = 'dark';c.add('dark')}else{d.style.colorScheme = 'light';c.add('light')}}else if(e){c.add(e|| '')}if(e==='light'||e==='dark')d.style.colorScheme=e}catch(e){}}()</script><div class="relative z-0 flex h-full w-full overflow-hidden"><div class="relative flex h-full max-w-full flex-1 flex-col overflow-hidden"><main class="relative h-full w-full flex-1 overflow-auto transition-width"></main></div></div><div class="absolute left-0 right-0 top-0 z-[2]"></div></div><script id="__NEXT_DATA__" type="application/json">{{.props}}</script><script></script></body></html>
1 change: 1 addition & 0 deletions resource/template/Rp5H7oULko6u7cJEjMucd/discovery.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<!DOCTYPE html><html><head>{{.envScript}}<meta charSet="utf-8" /><link rel="apple-touch-icon" sizes="180x180" href="{{.assetPrefix}}/_next/static/media/apple-touch-icon.59f2e898.png" /><link rel="icon" type="image/png" sizes="32x32" href="{{.assetPrefix}}/_next/static/media/favicon-32x32.be48395e.png" /><link rel="icon" type="image/png" sizes="16x16" href="{{.assetPrefix}}/_next/static/media/favicon-16x16.9b8dbb69.png" /><link rel="preconnect" href="{{.assetPrefix}}" /><title>ChatGPT</title><meta name="title" content="ChatGPT: Get instant answers, find inspiration, learn something new" /><meta name="description" content="ChatGPT is a free-to-use AI system. Use it for engaging conversations, gain insights, automate tasks, and witness the future of AI, all in one place." /><meta name="keywords" content="ai chat,ai,chap gpt,chat gbt,chat gpt 3,chat gpt login,chat gpt website,chat gpt,chat gtp,chat openai,chat,chatai,chatbot gpt,chatg,chatgpt login,chatgpt,gpt chat,open ai,openai chat,openai chatgpt,openai" /><meta property="og:description" content="A conversational AI system that listens, learns, and challenges" /><meta name="robots" content="index, follow" /><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><meta property="og:title" content="ChatGPT" /><meta property="og:image" content="{{.assetPrefix}}/_next/static/media/chatgpt-share-og.49cbbffe.png" /><meta property="og:url" content="https://chat.openai.com" /><meta name="apple-itunes-app" content="app-id=6448311069" /><meta name="next-head-count" content="17" /><link rel="preload" href="{{.assetPrefix}}/_next/static/media/soehne-buch.13189857.woff2" as="font" crossorigin /><link rel="preload" href="{{.assetPrefix}}/_next/static/media/soehne-halbfett.977f1845.woff2" as="font" crossorigin /><link rel="preload" href="{{.assetPrefix}}/_next/static/media/soehne-mono-buch.de677af1.woff2" as="font" crossorigin /><link rel="preload" href="{{.assetPrefix}}/_next/static/media/soehne-mono-halbfett.b082e8e2.woff2" as="font" crossorigin /><link data-next-font rel="preconnect" href="/" crossorigin="anonymous" /><link rel="preload" href="{{.assetPrefix}}/_next/static/css/36edb078e559dff3.css" as="style" /><link rel="stylesheet" href="{{.assetPrefix}}/_next/static/css/36edb078e559dff3.css" data-n-g /><noscript data-n-css></noscript><script defer nomodule src="{{.assetPrefix}}/_next/static/chunks/polyfills-c67a75d1b6f99dc8.js"></script><script src="{{.assetPrefix}}/_next/static/chunks/webpack-dcda336ac9faabba.js" defer></script><script src="{{.assetPrefix}}/_next/static/chunks/framework-31ffa51d236da229.js" defer></script><script src="{{.assetPrefix}}/_next/static/chunks/main-abb0536823058f9d.js" defer></script><script src="{{.assetPrefix}}/_next/static/chunks/pages/_app-81907334446d37b6.js" defer></script><script src="{{.assetPrefix}}/_next/static/chunks/6276-788aa034bc74dd94.js" defer></script><script src="{{.assetPrefix}}/_next/static/chunks/pages/gpts-feac991ac32a51df.js" defer></script><script src="{{.assetPrefix}}/_next/static/Rp5H7oULko6u7cJEjMucd/_buildManifest.js" defer></script><script src="{{.assetPrefix}}/_next/static/Rp5H7oULko6u7cJEjMucd/_ssgManifest.js" defer></script></head><body class="antialiased"><div id="__next"><script>!function(){try{var d=document.documentElement,c=d.classList;c.remove('light','dark');var e=localStorage.getItem('theme');if('system'===e||(!e&&true)){var t='(prefers-color-scheme: dark)',m=window.matchMedia(t);if(m.media!==t||m.matches){d.style.colorScheme = 'dark';c.add('dark')}else{d.style.colorScheme = 'light';c.add('light')}}else if(e){c.add(e|| '')}if(e==='light'||e==='dark')d.style.colorScheme=e}catch(e){}}()</script><div class="absolute left-0 right-0 top-0 z-[2]"></div></div><script id="__NEXT_DATA__" type="application/json">{{.props}}</script><script></script></body></html>
1 change: 1 addition & 0 deletions resource/template/Rp5H7oULko6u7cJEjMucd/editor.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<!DOCTYPE html><html><head>{{.envScript}}<meta charSet="utf-8" /><link rel="apple-touch-icon" sizes="180x180" href="{{.assetPrefix}}/_next/static/media/apple-touch-icon.59f2e898.png" /><link rel="icon" type="image/png" sizes="32x32" href="{{.assetPrefix}}/_next/static/media/favicon-32x32.be48395e.png" /><link rel="icon" type="image/png" sizes="16x16" href="{{.assetPrefix}}/_next/static/media/favicon-16x16.9b8dbb69.png" /><link rel="preconnect" href="{{.assetPrefix}}" /><title>ChatGPT</title><meta name="title" content="ChatGPT: Get instant answers, find inspiration, learn something new" /><meta name="description" content="ChatGPT is a free-to-use AI system. Use it for engaging conversations, gain insights, automate tasks, and witness the future of AI, all in one place." /><meta name="keywords" content="ai chat,ai,chap gpt,chat gbt,chat gpt 3,chat gpt login,chat gpt website,chat gpt,chat gtp,chat openai,chat,chatai,chatbot gpt,chatg,chatgpt login,chatgpt,gpt chat,open ai,openai chat,openai chatgpt,openai" /><meta property="og:description" content="A conversational AI system that listens, learns, and challenges" /><meta name="robots" content="index, follow" /><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><meta property="og:title" content="ChatGPT" /><meta property="og:image" content="{{.assetPrefix}}/_next/static/media/chatgpt-share-og.49cbbffe.png" /><meta property="og:url" content="https://chat.openai.com" /><meta name="apple-itunes-app" content="app-id=6448311069" /><meta name="next-head-count" content="17" /><link rel="preload" href="{{.assetPrefix}}/_next/static/media/soehne-buch.13189857.woff2" as="font" crossorigin /><link rel="preload" href="{{.assetPrefix}}/_next/static/media/soehne-halbfett.977f1845.woff2" as="font" crossorigin /><link rel="preload" href="{{.assetPrefix}}/_next/static/media/soehne-mono-buch.de677af1.woff2" as="font" crossorigin /><link rel="preload" href="{{.assetPrefix}}/_next/static/media/soehne-mono-halbfett.b082e8e2.woff2" as="font" crossorigin /><link data-next-font rel="preconnect" href="/" crossorigin="anonymous" /><link rel="preload" href="{{.assetPrefix}}/_next/static/css/36edb078e559dff3.css" as="style" /><link rel="stylesheet" href="{{.assetPrefix}}/_next/static/css/36edb078e559dff3.css" data-n-g /><noscript data-n-css></noscript><script defer nomodule src="{{.assetPrefix}}/_next/static/chunks/polyfills-c67a75d1b6f99dc8.js"></script><script src="{{.assetPrefix}}/_next/static/chunks/webpack-dcda336ac9faabba.js" defer></script><script src="{{.assetPrefix}}/_next/static/chunks/framework-31ffa51d236da229.js" defer></script><script src="{{.assetPrefix}}/_next/static/chunks/main-abb0536823058f9d.js" defer></script><script src="{{.assetPrefix}}/_next/static/chunks/pages/_app-81907334446d37b6.js" defer></script><script src="{{.assetPrefix}}/_next/static/chunks/192-63297905761f4832.js" defer></script><script src="{{.assetPrefix}}/_next/static/chunks/pages/gpts/editor-072951efa1e8a25c.js" defer></script><script src="{{.assetPrefix}}/_next/static/Rp5H7oULko6u7cJEjMucd/_buildManifest.js" defer></script><script src="{{.assetPrefix}}/_next/static/Rp5H7oULko6u7cJEjMucd/_ssgManifest.js" defer></script></head><body class="antialiased"><div id="__next"><script>!function(){try{var d=document.documentElement,c=d.classList;c.remove('light','dark');var e=localStorage.getItem('theme');if('system'===e||(!e&&true)){var t='(prefers-color-scheme: dark)',m=window.matchMedia(t);if(m.media!==t||m.matches){d.style.colorScheme = 'dark';c.add('dark')}else{d.style.colorScheme = 'light';c.add('light')}}else if(e){c.add(e|| '')}if(e==='light'||e==='dark')d.style.colorScheme=e}catch(e){}}()</script><div class="absolute left-0 right-0 top-0 z-[2]"></div></div><script id="__NEXT_DATA__" type="application/json">{{.props}}</script><script></script></body></html>
Loading

0 comments on commit 59e2daa

Please sign in to comment.