package main import ( "encoding/base64" "fmt" "net/http" "log" "os" "strings" "strconv" "sync" "time" "github.com/bitfield/script" "github.com/gin-gonic/gin" "github.com/spf13/cobra" cc "github.com/ivanpirog/coloredcobra" ) func main() { Execute() } var snipcartapikey string func init() { RootCmd.CompletionOptions.DisableDefaultCmd = true RootCmd.AddCommand( RunCmd, ) } var sitename string = "magnetosphere" var website string = sitename+".net" var sitedesc string = "electronic surplus" var tagline string = "we have the technology" var RootCmd = & cobra.Command{ Use: "m2", Short: website+" website (re)implementation", } // Execute executes root CLI command. func Execute() { cc.Init(&cc.Config{ RootCmd: RootCmd, Headings: cc.HiBlue + cc.Bold, //+ cc.Underline, Commands: cc.HiBlue + cc.Bold, CmdShortDescr: cc.HiBlue, Example: cc.HiBlue + cc.Italic, ExecName: cc.HiBlue + cc.Bold, Flags: cc.HiBlue + cc.Bold, //FlagsDataType: cc.HiBlue, FlagsDescr: cc.HiBlue, NoExtraNewlines: true, NoBottomNewline: true, }) if err := RootCmd.Execute(); err != nil { log.Fatal("Failed to execute command: ", err) } } var webPort int var brb bool var kill bool var logofile string var logohtml []byte func init() { defaultport, err := strconv.Atoi(os.Getenv("WEBPORT")) if err != nil { defaultport=8080 } RunCmd.Flags().StringVarP(&snipcartapikey, "apikey", "a", os.Getenv("SNIPCARTAPIKEY"), "snipcart public api key - env SNIPCARTAPIKEY="+os.Getenv("SNIPCARTAPIKEY")) RunCmd.Flags().StringVarP(&logofile, "logofile", "l", "logo.html", "logo file") RunCmd.Flags().IntVarP(&webPort, "port", "p", defaultport, "port to serve on - env WEBPORT="+os.Getenv("WEBPORT")) RunCmd.Flags().BoolVarP(&brb, "brb", "b", false, "'the website is down but will return soon'") RunCmd.Flags().BoolVarP(&kill, "kill", "k", false, "kill any process already running on the specified port") } var RunCmd = &cobra.Command{ Use: "run", Short: "run the web application", PreRun : func(_ *cobra.Command, _ []string) { inithtml() }, Run: func(_ *cobra.Command, _ []string) { if kill { _, _ = script.Exec(fmt.Sprintf(`%s "lsof -ti tcp:%d | xargs kill -9"`, shcmd, webPort)).Stdout() } Server() }, } func Server() { cats = getcats() prods = getprods() subcats = getsubcats() wg := new(sync.WaitGroup) wg.Add(1) r := gin.Default() //gin.SetMode(gin.ReleaseMode) //r.SetTrustedProxies(nil) r.NoRoute(func(c *gin.Context) { c.Writer.Header().Set("Server", "") c.Writer.WriteHeader(http.StatusNotFound) return }) if brb { r.GET("/", func(c *gin.Context) { c.Writer.Header().Set("Server", "") c.Writer.WriteHeader(http.StatusOK) c.Writer.Write([]byte(brbhtml)) return }) } else { r.GET("/about", func(c *gin.Context) { c.Writer.Header().Set("Server", "") c.Writer.WriteHeader(http.StatusOK) c.Writer.Write([]byte(abouthtmlfunc())) return }) r.GET("/m2.go", func(c *gin.Context) { c.Writer.Header().Set("Server", "") c.Writer.WriteHeader(http.StatusOK) c.Writer.Write([]byte(sex("cat m2.go"))) return }) r.GET("/m2.sh", func(c *gin.Context) { c.Writer.Header().Set("Server", "") c.Writer.WriteHeader(http.StatusOK) c.Writer.Write([]byte(sex("cat m2.sh"))) return }) r.GET("/logo.html", func(c *gin.Context) { c.Writer.Header().Set("Server", "") c.Writer.WriteHeader(http.StatusOK) c.Writer.Write([]byte(htmlstart+htmlstyle+bodystyle+sex("cat logo.html")+htmlend)) return }) //router.StaticFile("/favicon.ico", "./favicon.ico") faviconBuffer, _ := base64.StdEncoding.DecodeString(faviconBase64) r.GET("/favicon.ico", func(c *gin.Context) { _, _ = c.Writer.Write(faviconBuffer) return }) r.GET("/robots.txt", func(c *gin.Context) { _, _ = c.Writer.Write([]byte("User-Agent: *\n\nSitemap: https://"+website+"/sitemap")) return }) //r.Static("/img", "./img") //set expires header on images r.Group("/img").Use(func(c *gin.Context) { if strings.HasSuffix(c.Request.URL.Path, ".jpg") || strings.HasSuffix(c.Request.URL.Path, ".png") { c.Header("Expires", time.Now().Add(24 * time.Hour).UTC().Format(http.TimeFormat)) }}).Static("/", "./img") r.GET("/sitemap", func(c *gin.Context) { c.Writer.Header().Set("Server", "") c.Writer.WriteHeader(http.StatusOK) c.Writer.Write([]byte(sex("source m2.sh ; _sitemapxml"))) return }) r.GET("/", func(c *gin.Context) { c.Writer.Header().Set("Server", "") c.Writer.WriteHeader(http.StatusOK) c.Writer.Write([]byte(mainhtml)) return }) r.GET("/post/:slug", handleprod) r.GET("/p/:slug", handleprod) r.GET("/cats", func(c *gin.Context) { c.Writer.Header().Set("Server", "") c.Writer.WriteHeader(http.StatusOK) c.Writer.Write([]byte(fmt.Sprintf("len(cats)=%d\n",len(cats)))) for _, cat := range cats { c.Writer.Write([]byte(cat+"\n")) } return }) r.GET("/subcats", func(c *gin.Context) { c.Writer.Header().Set("Server", "") c.Writer.WriteHeader(http.StatusOK) c.Writer.Write([]byte(fmt.Sprintf("len(subcats)=%d\n",len(subcats)))) for _, subcat := range subcats { c.Writer.Write([]byte(subcat+"\n")) } return }) r.GET("/cat/:cat", handlecat) r.GET("/cat/:cat/:subcat", handlecat) r.HEAD("/", func(c *gin.Context) { c.Writer.Header().Set("Server", "") c.Writer.WriteHeader(http.StatusOK) return }) r.HEAD("/post/:slug", handleprod) r.HEAD("/p/:slug", handleprod) r.HEAD("/cat/:cat", handlecat) r.HEAD("/cat/:cat/:subcat", handlecat) } r.GET("/date", func(c *gin.Context) { c.Writer.Header().Set("Server", "") c.Writer.WriteHeader(http.StatusOK) c.Writer.Write([]byte(datehtmlfunc())) return }) go func() { fmt.Printf("listening on http://127.0.0.1:%d using gin router\n", webPort) r.Run(fmt.Sprintf(":%d", webPort)) wg.Done() }() wg.Wait() } func abouthtmlfunc() string { hhead := "\n\nabout "+website+"\n\n"+htmlstyle+"\n" return htmlstart+hhead+bodystyle+navlinks+fmt.Sprintf("

About %s

\n", website)+sex("cat about.txt")+sex("[[ ! -f aboutascii.html ]] && source m2.sh && _aboutascii || cat aboutascii.html")+htmlend } func prodhtmlfunc(a string) string { return htmlstart+fmt.Sprintf("%s\n%s\n", sex(fmt.Sprintf("source m2.sh ; _prodhead %s", a)),htmlstyle)+bodystyle+snipcartscript+navlinks+catslinks+fmt.Sprintf("

Product: %s

\n", a)+fmt.Sprintf("%s\n", sex(fmt.Sprintf("source m2.sh ; _prod %s", a)))+htmlend } func cathtmlfunc(a string) string { return htmlstart+fmt.Sprintf(""+sitename+" "+sitedesc+" | category: %s%s",a,htmlstyle)+bodystyle+snipcartscript+navlinks+subcatslinks(a)+fmt.Sprintf("

Category: %s

\n", a)+fmt.Sprintf("%s\n", sex(fmt.Sprintf("source m2.sh ; _cat %s", a)))+htmlend } func subcathtmlfunc(a, b string) string { return htmlstart+fmt.Sprintf(""+sitename+" "+sitedesc+" | category: %s | subategory: %s%s",a,b,htmlstyle)+bodystyle+snipcartscript+navlinks+subcatslinks(a)+fmt.Sprintf("

Category: %s | Subcategory %s

\n", a, b)+fmt.Sprintf("%s\n", sex(fmt.Sprintf("source m2.sh ; _subcat %s %s", a, b)))+htmlend } func subcatslinks(a string) string { return "
"+snipcartbutton+"
Categories:
\n"+fmt.Sprintf("%s", sex("source m2.sh ; _catslinks"))+"
Subcategories:
\n"+fmt.Sprintf("%s", sex("source m2.sh ; _subcatslinks "+a))+"
" } const shcmd =`/usr/bin/bash -c` var cmd string func sex(cmd string) string { return scriptExecHTML(fmt.Sprintf(`%s "%s"`, shcmd, cmd)) } func scriptExecHTML(cmd string) string { res, err := script.Exec(cmd).String() if err != nil { res += fmt.Sprintf("

error during script.Exec:\n
%v\n

command:\n
\n%s\n
\n%s", err, cmd, res) } return res } var prods []string func getprods() []string { prods, err := script.Exec(`bash -c 'source m2.sh ; _prods'`).Slice() if err != nil { fmt.Printf("error getting products with script.Exec:\n %v\nResult:\n%s\n", err, prods) } return prods } var cats []string func getcats() []string { cats, err := script.Exec(`bash -c 'source m2.sh ; _cats'`).Slice() if err != nil { fmt.Printf("error getting categories with script.Exec:\n %v\nResult:\n%s\n", err, cats) } return cats } var subcats []string func getsubcats() []string { subcats, err := script.Exec(`bash -c 'source m2.sh ; _subcats'`).Slice() if err != nil { fmt.Printf("error getting subcategories with script.Exec:\n %v\nResult:\n%s\n", err, subcats) } return subcats } func handleprod(c *gin.Context) { c.Writer.Header().Set("Server", "") var prodexists bool prodexists = false for _, prod := range prods { if prod == c.Param("slug") { prodexists = true break } } if prodexists { c.Writer.WriteHeader(http.StatusOK) if c.Request.Method == http.MethodGet { c.Writer.Write([]byte(prodhtmlfunc(string(c.Param("slug"))))) return } } else { fmt.Printf("product %s does not match any existing product\n",c.Param("slug")) c.Writer.WriteHeader(http.StatusNotFound) return } } func handlecat(c *gin.Context) { c.Writer.Header().Set("Server", "") var catexists bool var subcatexists bool catexists = false for _, cat := range cats { if cat == c.Param("cat") { catexists = true break } } subcatexists = false if c.Param("subcat") != "" { for _, subcat := range subcats { if subcat == c.Param("subcat") { subcatexists = true break } } } if c.Param("subcat") != "" && !subcatexists { fmt.Printf("subcategory %s does not match any existing subcategory\n",c.Param("subcat")) c.Writer.WriteHeader(http.StatusNotFound) return } if !catexists { fmt.Printf("category %s does not match any existing category\n",c.Param("cat")) c.Writer.WriteHeader(http.StatusNotFound) return } if catexists && subcatexists { c.Writer.WriteHeader(http.StatusOK) if c.Request.Method == http.MethodGet { c.Writer.Write([]byte(subcathtmlfunc(c.Param("cat"),c.Param("subcat")))) return } } if catexists { c.Writer.WriteHeader(http.StatusOK) if c.Request.Method == http.MethodGet { c.Writer.Write([]byte(cathtmlfunc(string(c.Param("cat"))))) return } } } func datehtmlfunc() string { return htmlstart+fmt.Sprintf("date\n%s\n",htmlstyle)+bodystyle+navlinks+fmt.Sprintf("\n\n
\n%s
\n\n",sex("source m2.sh ; _dayscalc "))+fmt.Sprintf("\n\n%s\n",sex("source m2.sh ; _rainbowcal "))+fmt.Sprintf("\n\n%s\n",sex("source m2.sh ; _yearcalendar "))+htmlend } var snipcartscript string var htmlstart string var htmlhead string var htmlstyle string var bodystyle string var snipcartbutton string var n0 string var n1 string var navlinks string var htmltoplink string var catslinks string var htmlend string var mainhtml string var brbhtml string func inithtml() { snipcartscript = ` ` htmlstart = fmt.Sprintf("\n\n") htmlhead = "\n\n"+sitename+" "+sitedesc+"\n\n\n\n"+htmlstyle+"\n" htmlstyle = "\n" bodystyle = "\n" snipcartbutton = "
cart:
" n0 = fmt.Sprintf("") n1 = "
Home About
" navlinks = n0+n1+"\n
\n" htmltoplink = fmt.Sprintf("top of page\n") catslinks = "
"+snipcartbutton+"
Categories:
\n"+fmt.Sprintf("%s", sex("source m2.sh ; _catslinks"))+"
" var htmlend = fmt.Sprintf("") logohtml, err := os.ReadFile(logofile) if err != nil { fmt.Printf("%v\n",err) } mainhtml = htmlstart+htmlhead+bodystyle+snipcartscript+navlinks+"

"+sitename+" "+sitedesc+"

"+tagline+"

"+string(logohtml)+catslinks+htmlend brbhtml = ` Technical Difficulties

This website is down right now, but will return as soon as possible.

` } var faviconBase64 string = `AAABAAEAICAAAAEAIACoEAAAFgAAACgAAAAgAAAAQAAAAAEAIAAAAAAAABAAACIuAAAiLgAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAC4BIwAAAAoBnVRgGb5mdEjQcH9d2HeCXdl5gV7Mcnhoy3J3a9B2emjc gnxg34d7XsJ1bUGiZGAq0YVyXOGQdV7ikXVd5JJ3XtSKcVy6fWtKmGdmNIBYXhP///8AAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACZVWEAUDg1Ardhc1bRboLA13KF69x1iPXeeIfv3XmF 6d99hN/gf4Pb4oGD2+WIgd3jiH/p1oN2yK1tZFbIgGyE24h1yeCPdODVim6ty4RtfcOAbmK1dmlq xoNpcN6VbXP0pnZy5ptyaceFYh7//+AAAAAAAAAAAAAAAAAAAAAAAJVQXgBRMDMDtl9zXMRne6K8 ZHWKtmJwgLZjcHS2YHBuumVycMVvdmzJdHRqx3dxZrtvam63cWtoomtkHo5aSQOnYWAcsG1gIalx YkrQhnGc65h52PqigOz8pID2/Kh/+v6tfvr8r3n676Zyvd+YbS//140AwoVkAFtKNQBJQysCXU40 A3c7TwWtV29YxmV+otVribfcb43J4HON2eN2juXedon54HqI/eF+hf3fgYH21358ybBraXKaY1wT mFpbAKBiXhPKgG9bwHdtcsl/bWnYi3CA5ZV0rfOfe+L7p374/qt+//+xe//+sXr/8qhzvsuNXyTk n2sA/8PzAK9ZdFC7XHx4pVNsOc5kh9LibJT/6HCY/+tzl//td5X/73qV//F+lP/xgJT/5n6K9NJ3 fK2+bW9qzXl0kcp9cH6ybWUyrm1jS9uJdLrzlIT07pOA3d+Ld7TYiW+ByoJoaM+IZ4Hqm3S39aZ4 7P6ve//9sHr+6qNuk6pyWgqMQF4ex1mIwNRfkP61VXh7y2GGx+Zqmf/pbJv/6m+Z/+t0l//teJX/ 63qR/tp1hdm4Zm+AwWp1f+J+h9DqhIn+z3l3ndN8eaHbhHq0yX1tcfOUhPP9mYn//puI//qcg/7x mXzj55V3scyEa3zPiWlo55pwm/eodeLrom+wtn9fHLJSeknTWJPp312c/8NZhLa1V3eJ3WeU/Ohq nP/obJr/6nKX/+R0kf3LbH65rFtrdcxsf7jofI/18YSR/+qDivi1aGmM4IN/w+2LhfnLeXJ33Yd6 uvmXh//9m4j//56H//+ghv/+oYT/+aF/9+ybd9PXjW6JxoRqU7l8YSi+jmECtEx9bdZXl/3hWqD/ 01uS7aNMbXzNYYjY5WqZ/+Zsmf/ebZD7vmJ3sK1bbHvXb4fU63uS//B+lP/zgZX/5H6I6bNlaXXp hIfh946N/9qAfMjFdW568pKE9PyXif/+mon//52H//6ghP/6oIH68Z1649qPcKq9fmZAq3FdCrF5 XwCvR3x91lOZ/+BWof/bWpr/ulN/rLFUdZrbZZL83GaS+bhaeaWmU2yS1myJ5uZzk//kdY//43iM /+Z7jP/UdoDByHN1cumDiPv0io7/64iG87pta33hh33I+ZOK//mVif/xln/t7JV7utSHcIHKgmll 2YxvgeeWdbvfkXGoxoNiRqxIeG3QUZX831Oh/99WoP/OWI7mpU1uXK9QdYK2WHd4jUpeQKhYbaG+ X3mdxWR9hMlofnfDaXd4w2x1erdpbUm3bWtByXN2nNZ6fLbhgoHPyXV2g816dEXdhH2b24V6msN3 a3PEeGt95Y18qvKZfd76oID+/6SE//ykgP/ckXGkp01yQctQkuLbUZ//1lOZ/cBShcWcSGcuAAAA AXY4TRG3W3dfvlt+lcZfga3IY4HE0GmFyNZuhsvNbX7ItmZuUMVueIjNb32pyG55j8J1cGCiXmAe /6GWAIxeTAi3dWVd3IV81PSUhvj8mof//5+F//+hhP//o4X/+aR7/dySbHeDPFkVu0yGqb5Lic2t R3uKnEZraZNLXxwAAAAAl0lkRNFgjPDhZJn/5Wia/+hrmv/pbpn/7HOZ/+Bxj+e2YXBx3naI6Ox+ kP/gfIfovXBuhJ9iWhx0Rz4Cr29hF7NvY1vNfm+H4453zPWYgvf9m4b//6GD//+ig//xnHnbzodo OP+CuACGP1slnkRwcrlJhsi8ToXbm0dpY6tOdH6dSWlqxlqGxeBhmv/mZpz/52ic/+lsnP/pb5n/ 2myMwbVfcYLkdY/23HWGz7lmbnPIb3eR1Hh9xrtrb1nEdG+L4oWA6Nd+erDEeGtwyXxuf+aPfMn3 moL9+ZyB/N+QdIN+UUQDjENfAFk4NgWqSXeGx0+P+7BHfK6rR3ig01SX/cdUis+jS2x1y12I0+Jl mv/lZpv/52qa/+Vsl//KZoGnwWZ4m9Btgc6yYGxqzG5+uOh9jfzwg5D/0nV+sMdydZLxiov/9Y6L /+yLhe/bhHmswnZrataGcpLZiHSnv3dnHtKDcQBZITcArk6AAIo7YRalSXVnl0JpeMZRjOfcU6D/ 2lWc/8FUhsebR2iDy1yJ0+Jkmv/mZp3/4WmW+LJccYutXW2XrFtsc85sf8bqeZH+8H2V//GBk//Z eYHPynN2ceuEiv33i4//946N//SQiP/ih3/bt3BqR5hfUwySX04BmWFTAAAAAAAAAAAAOE8UAKBE cACZRGsvtkqBscpOkufSUpfy0laU/7tSgc6eS2h6yFyHxd5imP/YZpDroFJpUpFPX0TIZn+85XSR /+13l//vepb/8H2U/996hua1ZWt35oGI7u+Hi/bpiITn3oR8utB8dme0a2kUx3dyAAAAAAAAAAAA AAAAAAAAAAAAAAAAJgAgAA4AEAGFOVsNnD1wKaFDcVC9S4fJ2FSa/8NTh9iiSW2Bull6mrtdenyN SVwNwWF+feBrk/vrcZn/7HWY/+13lv/vepb/5nqN6rdlbXPUd33Vw210Z8RxcSiybGUQb0ZAA3ZK RAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACNOWIAAAAAArtJh4DZU53/3Fad /81WkN+fS2pTAAAAAQAAAAHKZISe5GqY/+htmf/qcZf/7HSX/+54l//oeZDtuGdveMNvc7/Fb3Qa yHF2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFsbQgDi YqIArUZ8VNBRlfXeVaD/2leb/7lTf5d6PFIHFxkDA7hedWPUZI3f42uW/+lvmf/rcpj/7XWY/+Z2 kfOyYm6BrGVoeqtkZxCtZmkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAJZDZgCRQmIdwk2LwtpPn//cU5//yFOMyJ9PZ0rDW4KavFl8lKVSa3e6XHig 0GaH4eFskvrocZb/23GK8aZeaGaJVlgdhFFUAohUVwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAEHAP+B4wCqSXZWzE6T7tdTmv+7T4KutU58odpa mP/cXpn8zl2L5bZVebGiTmmJsFhxkcxpga+2YHGKg0dRFJxTYAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAo0hvAIA/Uwa1TH5+ xVGL6KVIcYzGU4rJ3lme/+Jdn//jYJ3/4GKZ/9Zij+/OY4e8sVpyi4tNWCWNTloAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAizxeAIo7XQyaSWpRoUhwWctTkODcVp3/3lid/+Fenf/iYJz/4GSZ/9djkPC8XHuckVFc HJVSXwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKRKcgCjSnEVrkp5c71OhLvCUojcyFWL3cZahsTB XX+QuVl5S49GXA6rVG8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG4zSAAvFh0Dj0Jg D5NAZBuYQWccnlBmEYBMSwaSUVsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//// //////////////////AAAH/gAAAH4AAAA4AAQAGAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAQA AgAAAIAAAACAAAABwAAAAfAAAAfwAAAP/gAAf/8AAH//AAB//4AB//+AA///wAP///AH///4H/// //////////////8=`