├── .gitignore ├── .vscode └── launch.json ├── conf └── app.conf.example ├── controllers ├── controllerArticle.go ├── controllerAuth.go ├── controllerBase.go ├── controllerError.go ├── controllerFantasy.go ├── controllerHome.go ├── controllerRenRen.go ├── controllerTag.go ├── controllerVideo.go ├── controllerWxApi.go └── imageController.go ├── main.go ├── models ├── cachConst.go ├── initialize.go ├── modelArticle.go ├── modelEpisode.go ├── modelImage.go ├── modelImgur.go ├── modelQuote.go ├── modelShow.go ├── modelSubtitle.go ├── modelTag.go ├── modelUser.go └── modelVote.go ├── routers └── router.go ├── ssl ├── 214011752730233.key └── 214011752730233.pem ├── static ├── css │ └── my.css ├── img │ ├── 16.png │ ├── 180.png │ ├── 32.png │ ├── 450_300.jpg │ ├── favicon.ico │ ├── icon │ │ ├── 108_108.png │ │ ├── 1200_240.png │ │ ├── 28_28.png │ │ ├── mojo-01.jpg │ │ ├── mojo-01.psd │ │ ├── mojo-02-2.jpg │ │ ├── mojo-02-eric.psd │ │ ├── mojo-02.jpg │ │ ├── mojo-02.psd │ │ ├── mojo-35-long.png │ │ ├── mojo-long-960-238.jpg │ │ ├── mojo-trans-35x35.png │ │ ├── mojo-trans-50x50.png │ │ ├── mojo-trans.png │ │ ├── mojo.ai │ │ └── mojotv-690-238.jpg │ ├── logo56x56.png │ ├── logo_60_60.png │ ├── qq-video.png │ ├── youku.ico │ └── youku.png └── js │ └── my.js ├── tasks └── fetchEztv.go ├── tests └── default_test.go ├── tools └── temple.go ├── trytv_2017-11-30.sql └── views ├── article ├── _links.html ├── _video.html ├── index.html └── view.html ├── auth └── register.html ├── error └── 404.html ├── home ├── index.html └── toutiaoAd.html ├── image └── cropper_modal.html ├── layout ├── _breadcrumb.html ├── _footer.html ├── _head.html ├── _nav.html ├── _script.html ├── _share_comment.html ├── _tags.html ├── base_index.html └── base_view.html └── tag ├── _allTags.html └── view.html /.gitignore: -------------------------------------------------------------------------------- 1 | my_go_web 2 | debug 3 | .* 4 | main 5 | *.log 6 | test.log 7 | test.*.log 8 | nohup.out 9 | conf/app.conf 10 | .vscode 11 | 12 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | 5 | { 6 | "name": "Launch", 7 | "type": "go", 8 | "request": "launch", 9 | "mode": "debug", 10 | "remotePath": "", 11 | "port": 2345, 12 | "host": "127.0.0.1", 13 | "program": "${workspaceRoot}", 14 | "env": {}, 15 | "args": [], 16 | "showLog": true 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /conf/app.conf.example: -------------------------------------------------------------------------------- 1 | appname=mojotv 2 | httpport=9999 3 | runmode=prod 4 | flashname=trytv 5 | FlashSeperator=ez 6 | ServerName=mojotvAwesomeServer 7 | EnableGzip=true 8 | StaticExtensionsToGzip=.css, .js 9 | EnableXSRF=true 10 | XSRFKEY=not_for_jerk 11 | xsrfexpire = 1200 12 | sessionon=true 13 | sessionprovider=memory 14 | sessionname=session 15 | 16 | mysqlport=3306 17 | mysqldb=trytv 18 | #imageCdnHost=https://oeveb4zm9.qnssl.com/ 19 | imageCdnHost=http://img.trytv.org/ 20 | keyword=mojotv.cn| 21 | description=欧美娱乐资讯 22 | #cdnhost=https://oeveb4zm9.qnssl.com/ 23 | [dev] 24 | mysqluser=** 25 | mysqlpass=** 26 | mysqlurls=** 27 | [prod] 28 | mysqluser=** 29 | mysqlpass=** 30 | mysqlurls=** 31 | -------------------------------------------------------------------------------- /controllers/controllerArticle.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "www.mojotv.cn/models" 7 | "time" 8 | ) 9 | 10 | type ArticleController struct { 11 | BaseController 12 | } 13 | 14 | func (c *ArticleController) View() { 15 | articleID, _ := c.GetInt(":id") 16 | //浏览计数 17 | vote := models.Vote{} 18 | models.Gorm.Where("article_id = ?", articleID).Find(&vote) 19 | vote.Visit++ 20 | models.Gorm.Model(&vote).Update("visit") 21 | 22 | var article models.Article 23 | cacheKey := fmt.Sprint("mojotv.article_detail.", articleID) 24 | //fmt.Println(cacheKey) 25 | if x, found := models.CacheManager.Get(cacheKey); found { 26 | //x 就是 []byte 27 | buffer := x.([]byte) 28 | json.Unmarshal(buffer, &article) 29 | } else { 30 | if models.Gorm.Preload("Tags").Preload("Images").Preload("Shows").First(&article, articleID).RecordNotFound() { 31 | c.Abort("404") 32 | } 33 | buffer, _ := json.Marshal(article) 34 | var expireTime time.Duration 35 | if article.UpdatedAt.After(time.Now().Add(-time.Hour * 24)) { 36 | expireTime = time.Minute * 2 37 | } else if article.UpdatedAt.After(time.Now().Add(-time.Hour * 48)) { 38 | expireTime = time.Hour * 6 39 | } else if article.UpdatedAt.After(time.Now().Add(-time.Hour * 24 * 3)) { 40 | expireTime = models.C_EXPIRE_TIME_FOREVER 41 | } 42 | models.CacheManager.Set(cacheKey, buffer, expireTime) 43 | } 44 | 45 | //设置head seo参数 46 | //设置breadcrumb 47 | //设置side bar 48 | //设置head navigation bar 49 | url := fmt.Sprint("/article/", articleID) 50 | tagUrl := fmt.Sprint("/tag/", article.FirstTagID) 51 | tagName := fmt.Sprint(article.FirstTagName, article.FirstTagNameEn) 52 | c.Data["BreadCrumbs"] = []Crumb{{"/", "fa fa-home", "首页"}, {tagUrl, "fa fa-book", tagName}, {url, "fa fa-cloud", article.Title}} 53 | c.Data["Article"] = article 54 | c.Data["Vote"] = vote 55 | c.Data["Title"] = article.Title 56 | c.Data["Description"] = article.Description 57 | 58 | if json, err := json.Marshal(article.Images); err == nil { 59 | strrrr := string(json) 60 | c.Data["JsonImages"] = strrrr 61 | } else { 62 | c.Data["JsonImages"] = "" 63 | } 64 | 65 | c.Layout = "layout/base_view.html" 66 | c.TplName = "article/view.html" 67 | } 68 | 69 | func (c *ArticleController) LoadMore() { 70 | offset, _ := c.GetInt("offset") 71 | //tagId, _ := c.GetInt("tagId") 72 | articles := models.GetBatchArticles(offset, 6) 73 | c.JsonRetrun("success", "欢迎访问我们的小站", articles) 74 | } 75 | 76 | //评分ajax 77 | func (c *ArticleController) VoteScore() { 78 | voteID, _ := c.GetInt("voteID") 79 | score, _ := c.GetFloat("score") 80 | var vote models.Vote 81 | models.Gorm.First(&vote, voteID) 82 | count := float32(vote.VoteCount) 83 | vote.Score = (vote.Score*count + float32(score)) / (count + 1) 84 | vote.VoteCount = vote.VoteCount + 1 85 | models.Gorm.Model(&vote).Update("vote_count", "score") 86 | c.JsonRetrun("success", "rate score successed", vote) 87 | } 88 | -------------------------------------------------------------------------------- /controllers/controllerAuth.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "www.mojotv.cn/models" 7 | "net/http" 8 | "net/url" 9 | 10 | "github.com/astaxie/beego" 11 | "github.com/astaxie/beego/logs" 12 | 13 | "golang.org/x/crypto/bcrypt" 14 | ) 15 | 16 | type AuthController struct { 17 | beego.Controller //集成beego controller 18 | 19 | } 20 | type WeibAuth2Response struct { 21 | Access_token string `json:"access_token"` 22 | Uid uint `json:"uid,string"` 23 | } 24 | 25 | //http://open.weibo.com/wiki/2/users/show 26 | type WeiboUser struct { 27 | Id uint `json:"id"` 28 | Screen_name string `json:"screen_name"` 29 | Name string `json:"name"` 30 | Description string `json:"description"` 31 | Avatar_large string `json:"avatar_large"` 32 | } 33 | 34 | //sign up 35 | func (c *AuthController) GetRegister() { 36 | user := models.User{} 37 | //weibo auth2 回调 38 | weiboCode := c.GetString("code") 39 | if weiboCode != "" { 40 | resp, _ := http.PostForm("https://api.weibo.com/oauth2/access_token", url.Values{ 41 | "client_id": {WeiboAppId}, 42 | "client_secret": {WeiboAppSecret}, 43 | "grant_type": {"authorization_code"}, 44 | "code": {weiboCode}, 45 | "redirect_uri": {"https://www.mojotv.cn/auth/register"}, 46 | }) 47 | defer resp.Body.Close() 48 | 49 | //解析json 获取token和uid 50 | 51 | var weiboResponseJson WeibAuth2Response 52 | json.NewDecoder(resp.Body).Decode(&weiboResponseJson) 53 | 54 | //logs.Debug("token struct:", weiboResponseJson) 55 | //logs.Debug("token uid and access token", weiboResponseJson.Uid, weiboResponseJson.Access_token) 56 | 57 | logs.Debug("first weibo id", weiboResponseJson.Uid) 58 | 59 | models.Gorm.Where("weibo_id = ?", weiboResponseJson.Uid).First(&user) 60 | // logs.Debug("weibo struct user:%v", user) 61 | // logs.Debug("weibo struct accesstoken:%v", weiboResponseJson) 62 | if user.ID > 0 { 63 | //用户已注册 64 | //直接登陆 65 | c.SetSession(AuthSessionName, user) 66 | c.Redirect("/", 303) 67 | return 68 | } 69 | //用户未注册 获取用户信息 70 | getURL := fmt.Sprintf("https://api.weibo.com/2/users/show.json?access_token=%s&uid=%d", weiboResponseJson.Access_token, weiboResponseJson.Uid) 71 | respInfo, _ := http.Get(getURL) 72 | defer respInfo.Body.Close() 73 | 74 | //解析json 获取token和uid 75 | var weiboUser WeiboUser 76 | json.NewDecoder(respInfo.Body).Decode(&weiboUser) 77 | //logs.Debug("weibo name info:%v", weiboUser.Name) 78 | //logs.Debug("weibo struct info:%v", weiboUser) 79 | logs.Debug("first weibo info id", weiboUser.Id) 80 | 81 | user.WeiboId = weiboUser.Id 82 | user.Name = weiboUser.Name 83 | user.AvatarImage = weiboUser.Avatar_large 84 | user.Email = fmt.Sprint("weibo_", weiboUser.Id, "@mojotv.cn") 85 | } 86 | c.Data["User"] = user 87 | c.Data["Xsrf"] = c.XSRFToken() //防止跨域 88 | c.TplName = "auth/register.html" 89 | } 90 | 91 | func (c *AuthController) PostRegister() { 92 | password := c.GetString("password") 93 | passwordConfirmed := c.GetString("password_confirmed") 94 | if password == "" || passwordConfirmed == "" || (password != passwordConfirmed) { 95 | c.Data["json"] = map[string]interface{}{"status": "error", "message": "两次输入的密码不相同,或者密码为空", "data": nil} 96 | c.ServeJSON() 97 | return 98 | 99 | } 100 | 101 | email := c.GetString("email") 102 | var isExistUser models.User 103 | models.Gorm.Where("email = ?", email).First(&isExistUser) 104 | if isExistUser.ID > 0 && isExistUser.WeiboId < 1 { 105 | beego.Warning("用户已近存在") 106 | c.Data["json"] = map[string]interface{}{"status": "error", "message": "email已经注册", "data": nil} 107 | c.ServeJSON() 108 | return 109 | } 110 | 111 | //hash password 112 | password_byte := []byte(password) 113 | hashedPassword, _ := bcrypt.GenerateFromPassword(password_byte, bcrypt.DefaultCost) 114 | //new struct from package 115 | //TODO:需要搞清楚 go语言的 pointer * &的用法 116 | isExistUser.Email = email 117 | isExistUser.Password = string(hashedPassword) 118 | isExistUser.Name = c.GetString("name") 119 | isExistUser.WeiboAvatar = c.GetString("avatar_image") 120 | wbId, _ := c.GetInt("weibo_id") 121 | logs.Debug("jerk:", wbId) 122 | isExistUser.WeiboId = uint(wbId) 123 | logs.Debug("jerk:", isExistUser.WeiboId) 124 | 125 | models.Gorm.Create(&isExistUser) 126 | if isExistUser.ID < 1 { 127 | beego.Critical("用户注册数据库添加失败") 128 | c.Data["json"] = map[string]interface{}{"status": "error", "message": "添加新用户失败", "data": nil} 129 | c.ServeJSON() 130 | return 131 | 132 | } else { 133 | c.SetSession(AuthSessionName, isExistUser) 134 | c.Data["json"] = map[string]interface{}{"status": "success", "message": "添加新用户成功", "data": nil} 135 | c.ServeJSON() 136 | return 137 | } 138 | } 139 | 140 | func (c *AuthController) PostLogin() { 141 | email := c.GetString("email") 142 | user := models.User{} 143 | models.Gorm.Table("users").Where("email = ?", email).First(&user) 144 | 145 | if user.ID < 1 { 146 | c.Data["json"] = map[string]interface{}{"status": "error", "message": "用户不存在", "data": nil} 147 | c.ServeJSON() 148 | return 149 | } else { 150 | //比较密码 151 | //string to []byte 152 | password := []byte(c.GetString("password")) 153 | //http://stackoverflow.com/questions/23259586/bcrypt-password-hashing-in-golang-compatible-with-node-js 154 | 155 | // Hashing the password with the default cost of 10 DefaultCost int = 10 156 | //laravel bcrypt /Library/WebServer/Documents/estate/vendor/laravel/framework/src/Illuminate/Hashing/BcryptHasher.php 157 | // Comparing the password with the hash 158 | db_hashed_password := []byte(user.Password) 159 | err := bcrypt.CompareHashAndPassword(db_hashed_password, password) 160 | if err == nil { // nil means it is a match 161 | //设置登陆session info 162 | c.SetSession(AuthSessionName, user) 163 | c.Data["json"] = map[string]interface{}{"status": "success", "message": "用户登陆成功", "data": nil} 164 | c.ServeJSON() 165 | return 166 | } else { 167 | c.Data["json"] = map[string]interface{}{"status": "error", "message": "密码错误", "data": nil} 168 | c.ServeJSON() 169 | return 170 | } 171 | } 172 | } 173 | 174 | // //find password 填写email 175 | func (c *AuthController) GetResetPassword() { 176 | 177 | } 178 | func (c *AuthController) PostResetPassword() { 179 | //获取email地址 发送邮件 180 | 181 | } 182 | 183 | // //注销 184 | func (c *AuthController) GetLogout() { 185 | c.DelSession(AuthSessionName) 186 | c.Redirect("/", 302) 187 | } 188 | 189 | func (c *AuthController) ToutiaoAd() { 190 | c.Redirect("/", 301) 191 | 192 | // c.Data["Title"] = "mojoTV美剧|最新最快最热的美剧周边资讯" 193 | // c.Data["Keyword"] = "mojoTV美剧,轻松学英语,欧美美剧资讯,国外搞笑小视频," 194 | // c.Data["Description"] = "mojoTV美剧|提供最新最热最快最热的美剧资讯,海量英语学习资源,欧美搞笑有创意的短视频gif动图,海量美剧双语原生字幕,这里是英语爱好者的乐园,让每一个人都爱上学习英语" 195 | // c.Layout = "layout/base_index.html" 196 | // c.TplName = "home/toutiaoAd.html" 197 | } 198 | -------------------------------------------------------------------------------- /controllers/controllerBase.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "www.mojotv.cn/models" 5 | 6 | "github.com/astaxie/beego" 7 | ) 8 | 9 | const ( 10 | WeiboAppId = "2646862509" 11 | WeiboAppSecret = "da8185c89ee23d4d1d5f08fe6f3d5d89" 12 | FlashSuccess = "flash_success" 13 | FlashInfo = "flash_info" 14 | FlashError = "flash_Error" 15 | AuthSessionName = "authed_user_session_name" 16 | ) 17 | 18 | type BaseController struct { 19 | beego.Controller //集成beego controller 20 | //UserInfo *models.Users 21 | } 22 | type Crumb struct { 23 | Href string 24 | Class string 25 | Name string 26 | } 27 | 28 | // //为了生成breadcrumb 29 | // type Bread struct { 30 | // Name, Href, Class string 31 | // } 32 | 33 | func (this *BaseController) Prepare() { 34 | this.Data["WeiboAuth2Url"] = "https://api.weibo.com/oauth2/authorize?client_id=2646862509&response_type=code&redirect_uri=http://www.mojotv.cn/auth/register&display=mobile" 35 | sessionUser := this.GetSession(AuthSessionName) 36 | if sessionUser == nil { 37 | this.Data["User"] = nil 38 | this.Data["Uid"] = 0 39 | this.Data["IsAdmin"] = false 40 | } else { 41 | user := this.GetSession(AuthSessionName).(models.User) 42 | this.Data["User"] = user 43 | this.Data["Uid"] = user.ID 44 | this.Data["IsAdmin"] = user.ID == 1 45 | } 46 | if (this.Ctx.Request.Method == "GET") && !this.Ctx.Input.IsAjax() { 47 | this.Data["Xsrf"] = this.XSRFToken() //防止跨域 48 | //fmt.Println(quotes) 49 | //this.Data["Quotes"] = models.Get3RandomQuote() 50 | this.Data["Tags"] = models.FetchAllTagsCached() 51 | this.Data["Imgs"] = models.Fetch5RandomQuoteImageCached() 52 | } 53 | // //判断用户数是否已近登陆 54 | // //读取session 55 | // userLogin := this.GetSession("loginInfo") 56 | // if userLogin == nil { 57 | // this.Uid = 0 58 | // } else { 59 | // this.UserInfo = userLogin.(*models.Users) 60 | // this.Uid = this.UserInfo.Id 61 | // } 62 | // //在模板里面判断登陆状态 63 | // this.Data["Uid"] = this.Uid 64 | // //做一些权限判断 65 | 66 | //为每一个view 赋值侧边栏 67 | 68 | //fmt.Println(models.Fetch5RandomQuoteImageCached()) 69 | 70 | //在这里可以把他填充到模板里面 71 | } 72 | 73 | func (this *BaseController) JsonRetrun(status string, message string, data interface{}) { 74 | this.Data["json"] = map[string]interface{}{"status": status, "message": message, "data": data} 75 | this.ServeJSON() 76 | return 77 | } 78 | -------------------------------------------------------------------------------- /controllers/controllerError.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "github.com/astaxie/beego" 5 | ) 6 | 7 | type ErrorController struct { 8 | beego.Controller 9 | } 10 | 11 | func (c *ErrorController) Error404() { 12 | c.Data["content"] = "page not found" 13 | c.TplName = "error/404.html" 14 | } 15 | 16 | func (c *ErrorController) Error501() { 17 | c.Data["content"] = "server error" 18 | c.TplName = "error/404.html" 19 | } 20 | func (c *ErrorController) Error503() { 21 | c.Data["content"] = "server error" 22 | c.TplName = "error/404.html" 23 | } 24 | func (c *ErrorController) Error500() { 25 | c.Data["content"] = "server error" 26 | c.TplName = "error/404.html" 27 | } 28 | func (c *ErrorController) Error401() { 29 | c.Data["content"] = "server error" 30 | c.TplName = "error/404.html" 31 | } 32 | 33 | func (c *ErrorController) Error403() { 34 | c.Data["content"] = "server error" 35 | c.TplName = "error/404.html" 36 | } 37 | func (c *ErrorController) ErrorDb() { 38 | c.Data["content"] = "database is now down" 39 | c.TplName = "error/404.html" 40 | } 41 | -------------------------------------------------------------------------------- /controllers/controllerFantasy.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "io/ioutil" 8 | "math/rand" 9 | "www.mojotv.cn/models" 10 | "net/http" 11 | 12 | "github.com/astaxie/beego" 13 | ) 14 | 15 | type FantasyController struct { 16 | beego.Controller //集成beego controller 17 | } 18 | 19 | func (c *FantasyController) Index() { 20 | 21 | myChannelId := c.GetString(":mcid") 22 | channelId := c.GetString(":cid", "33716") 23 | vId := c.GetString(":vid", "87816") 24 | t := fmt.Sprint(rand.ExpFloat64()) 25 | //https://www.fantasy.tv/tv/playDetails.action?myChannelId=33716&id=87816&channelId=33716&t=0.0958195376527875 26 | cacheKey := fmt.Sprintf("myChannelId=%s&id=%s&channelId=%s", myChannelId, vId, channelId) 27 | var content []byte 28 | if x, found := models.CacheManager.Get(cacheKey); found { 29 | foo := x.(string) 30 | content = []byte(foo) 31 | } else { 32 | client := &http.Client{} 33 | apiUrl := fmt.Sprintf("https://www.fantasy.tv/tv/playDetails.action?myChannelId=%s&id=%s&channelId=%s&t=%s", myChannelId, vId, channelId, t) 34 | fmt.Println(apiUrl) 35 | req, _ := http.NewRequest("GET", apiUrl, nil) 36 | req.Header = http.Header{ 37 | "Cookie": {"acw_tc=AQAAAB4/YV0LXwIA8cxxq7tfQBTcUPei"}, 38 | "Referer": {"https://www.fantasy.tv/newApp/index.html"}, 39 | "User-Agent": {"Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1"}, 40 | "Host": {"www.fantasy.tv"}, 41 | } 42 | resp, err := client.Do(req) 43 | 44 | defer resp.Body.Close() 45 | if err == nil && resp.StatusCode == 200 { 46 | body, _ := ioutil.ReadAll(resp.Body) 47 | content = body 48 | //放缓存 49 | models.CacheManager.Set(cacheKey, string(content), time.Second*90) 50 | } 51 | } 52 | c.Ctx.Output.Header("Content-Type", "application/json; charset=utf-8") 53 | c.Ctx.Output.Body(content) 54 | return 55 | } 56 | -------------------------------------------------------------------------------- /controllers/controllerHome.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "www.mojotv.cn/models" 5 | ) 6 | 7 | type HomeController struct { 8 | BaseController 9 | } 10 | 11 | func (c *HomeController) Get() { 12 | articles := models.GetBatchArticles(0, models.PageSize) 13 | c.Data["BreadCrumbs"] = []Crumb{{"/", "fa fa-home", "首页"}, {"/article", "fa fa-fire", "资讯"}} 14 | c.Data["Articles"] = articles 15 | c.Data["Title"] = "mojoTV资讯|最新最快最热的美剧周边资讯" 16 | c.Data["Keyword"] = "mojoTV资讯,轻松学英语,欧美美剧资讯,国外搞笑小视频," 17 | c.Data["Description"] = "mojoTV资讯|提供最新最热最快最热的美剧资讯,海量英语学习资源,欧美搞笑有创意的短视频gif动图,海量美剧双语原生字幕,这里是英语爱好者的乐园,让每一个人都爱上学习英语" 18 | c.Layout = "layout/base_index.html" 19 | c.TplName = "home/index.html" 20 | } 21 | -------------------------------------------------------------------------------- /controllers/controllerRenRen.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "bytes" 5 | "crypto/md5" 6 | "encoding/hex" 7 | "fmt" 8 | "io/ioutil" 9 | "www.mojotv.cn/models" 10 | "net/http" 11 | "net/url" 12 | 13 | "github.com/astaxie/beego" 14 | ) 15 | 16 | const SERVER = "https://api.rr.tv" 17 | const SECRET_KEY = "clientSecret=08a30ffbc8004c9a916110683aab0060" 18 | 19 | //const TOKEN = "945e82b94c08447aafe45e6051159737" 20 | const TOKEN = "35dafc24e73d461a94c2e69daef72c6b" //我的iOStoken 21 | 22 | var TOKENS = []string{ 23 | "91b7fc36672548259433ca40bc57e6dd", //我的 24 | "35dafc24e73d461a94c2e69daef72c6b", //我的 25 | "99ce6f3f7ff840e586958770472ec893", 26 | "37570aa5dabe44219ab8a13986778390", 27 | "6f8443b2d6c54d39bf7ab780bb5df3b2", 28 | "3739e42b649948febad6dbfeaeecddfe", 29 | "ffeb43e117d744e6822c3fe6abc12dea", 30 | "5f6d0d3a49e5439e8492515c49b797c5", 31 | "41d74adccd4b46389882242949e82cea", 32 | "ff750a61e2684b7d9d4f783b4e3b14b1", 33 | "f703dcbae2fb42889e8b111829b36167", 34 | "9aea227ebb224a8b9bea347c13192706", 35 | "827af86209464b038552a87f1cd546b2", 36 | "bfc156b8c9ce48d98f2f9e48868130b3", 37 | "c0a60ecf198d458d83b475e50b8d7893", 38 | "22f6e8f0fac049849ce6db26535ecf6d", 39 | "cce5bae0fcdd446fa87f0e61d86cd345", 40 | "d1f33f20d83441228adf791215308d6d", 41 | "803db2c8c9de400a9e90f75c2926d98a", 42 | "b7c4f78d734f48fb871b3c7667f32019", 43 | "99b3109e62fa4f1eb449182f5428cb84", 44 | "10347e508c5946ec9116e3044b728f01", 45 | } 46 | 47 | var tokenIndex = 0 48 | 49 | func generateFakeHeader() (headers http.Header) { 50 | // var randomToken = TOKENS[tokenIndex%(len(TOKENS))] 51 | return http.Header{ 52 | "token": {"6b6cfdd3e90843c0a0914425638db7ef"}, 53 | "clientType": {"android_RRMJ"}, 54 | "clientVersion": {"3.6.3"}, 55 | "deviceId": {"861134030056126"}, 56 | "signature": {"643c184f77372e364550e77adc0360cd"}, 57 | "t": {"1491433993933"}, 58 | "Content-Type": {"application/x-www-form-urlencoded"}, 59 | "Authentication": {"RRTV 470164b995ea4aa5a53f9e5cbceded472:IxIYBj:LPWfRb:I9gvePR5R2N8muXD7NWPCj"}, 60 | } 61 | } 62 | 63 | type RenRenController struct { 64 | beego.Controller //集成beego controller 65 | } 66 | 67 | func (c *RenRenController) Index() { 68 | apiURI := "/v3plus/video/indexInfo" 69 | cacheKey := fmt.Sprint(SERVER, apiURI) 70 | paraData := url.Values{} 71 | c.cacheOrPostReturnJson(cacheKey, apiURI, paraData, generateFakeHeader()) 72 | } 73 | 74 | //根据episodeId找到对应的电视剧播放地址 75 | func (c *RenRenController) M3u8() { 76 | //55872 77 | var FakeHeader = generateFakeHeader() 78 | var episodeSid = c.GetString(":episodeSid") 79 | //https://github.com/wilsonwen/kanmeiju/blob/adc56d8665b3c99a8d48df3cc2f1eaf623f7be6e/index.js line203 80 | apiURI := "/video/findM3u8ByEpisodeSidAuth" 81 | cacheKey := fmt.Sprint(SERVER, apiURI, "/episodeSid/", episodeSid) 82 | 83 | paraData := url.Values{ 84 | "episodeSid": {episodeSid}, 85 | "quality": {"super"}, 86 | "token": {TOKEN}, 87 | "seasonId": {"0"}, 88 | } 89 | key := fmt.Sprint("episodeSid=", episodeSid, "quality=super", "clientType=", FakeHeader["clientType"][0], "clientVersion=", FakeHeader["clientVersion"][0], "t=", FakeHeader["t"][0], SECRET_KEY) 90 | signature := GetMD5Hash(key) 91 | FakeHeader["signature"] = []string{signature} 92 | c.cacheOrPostReturnJson(cacheKey, apiURI, paraData, FakeHeader) 93 | 94 | } 95 | func GetMD5Hash(text string) string { 96 | hasher := md5.New() 97 | hasher.Write([]byte(text)) 98 | return hex.EncodeToString(hasher.Sum(nil)) 99 | } 100 | 101 | func (c *RenRenController) Search() { 102 | searchWord := c.GetString(":keyword") 103 | //https://github.com/wilsonwen/kanmeiju/blob/adc56d8665b3c99a8d48df3cc2f1eaf623f7be6e/index.js line203 104 | apiURI := "/v3plus/video/search" 105 | cacheKey := fmt.Sprint(SERVER, apiURI, "/name/", searchWord) 106 | paraData := url.Values{ 107 | "title": {searchWord}, 108 | "enTitle": {searchWord}, 109 | } 110 | c.cacheOrPostReturnJson(cacheKey, apiURI, paraData, generateFakeHeader()) 111 | } 112 | 113 | func (c *RenRenController) Hot() { 114 | apiURI := "/video/seasonRankingList" 115 | cacheKey := fmt.Sprint(SERVER, apiURI) 116 | paraData := url.Values{} 117 | c.cacheOrPostReturnJson(cacheKey, apiURI, paraData, generateFakeHeader()) 118 | } 119 | 120 | func (c *RenRenController) Top() { 121 | apiURI := "/v3plus/season/topList" 122 | cacheKey := fmt.Sprint(SERVER, apiURI) 123 | paraData := url.Values{} 124 | c.cacheOrPostReturnJson(cacheKey, apiURI, paraData, generateFakeHeader()) 125 | } 126 | 127 | func (c *RenRenController) Season() { 128 | apiURI := "/v3plus/season/detail" 129 | seasonId := c.GetString(":seasonId") 130 | cacheKey := fmt.Sprint(SERVER, apiURI, "/seasonId/", seasonId) 131 | paraData := url.Values{ 132 | "seasonId": {seasonId}, 133 | } 134 | c.cacheOrPostReturnJson(cacheKey, apiURI, paraData, generateFakeHeader()) 135 | } 136 | 137 | func (c *RenRenController) Album() { 138 | apiURI := "/v3plus/video/album" 139 | albumId := c.GetString(":albumId") 140 | cacheKey := fmt.Sprint(SERVER, apiURI, "/albumId/", albumId) 141 | paraData := url.Values{ 142 | "albumId": {albumId}, 143 | } 144 | c.cacheOrPostReturnJson(cacheKey, apiURI, paraData, generateFakeHeader()) 145 | } 146 | 147 | func (c *RenRenController) Category() { 148 | apiURI := "/v3plus/video/search" 149 | cat := c.GetString(":categoryType") 150 | pages := c.GetString(":pages") 151 | cacheKey := fmt.Sprint(SERVER, apiURI, "/category/", cat, "/pages/", pages) 152 | paraData := url.Values{ 153 | "cate": {"cat_list"}, 154 | "cat": {cat}, 155 | "pages": {pages}, 156 | } 157 | c.cacheOrPostReturnJson(cacheKey, apiURI, paraData, generateFakeHeader()) 158 | } 159 | 160 | func (c *RenRenController) cacheOrPostReturnJson(cacheKey string, apiURI string, paraData url.Values, headers http.Header) { 161 | apiUrl := fmt.Sprint(SERVER, apiURI) 162 | var content []byte 163 | if x, found := models.CacheManager.Get(cacheKey); found { 164 | foo := x.(string) 165 | content = []byte(foo) 166 | } else { 167 | client := &http.Client{} 168 | datUrl := paraData.Encode() 169 | req, _ := http.NewRequest("POST", apiUrl, bytes.NewBufferString(datUrl)) 170 | req.Header = headers 171 | resp, err := client.Do(req) 172 | defer resp.Body.Close() 173 | if err == nil && resp.StatusCode == 200 { 174 | body, _ := ioutil.ReadAll(resp.Body) 175 | content = body 176 | //放缓存 177 | models.CacheManager.Set(cacheKey, string(content), models.C_EXPIRE_TIME_HOUR_01) 178 | } 179 | } 180 | c.Ctx.Output.Header("Content-Type", "application/json; charset=utf-8") 181 | c.Ctx.Output.Body(content) 182 | return 183 | } 184 | -------------------------------------------------------------------------------- /controllers/controllerTag.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "fmt" 5 | "www.mojotv.cn/models" 6 | ) 7 | 8 | type TagController struct { 9 | BaseController 10 | } 11 | 12 | func (c *TagController) View() { 13 | tagId, _ := c.GetInt(":id") 14 | //浏览计数 15 | tag := models.Tag{} 16 | var articles []models.Article 17 | models.Gorm.First(&tag, tagId) 18 | 19 | //models.Gorm.Related("Tags", "article_tag.tag_id = ?", tag.ID).Preload("Images").Limit(90).Find(&articles) 20 | if models.Gorm.Model(&tag).Order("articles.created_at desc").Limit(models.PageSize).Preload("Images").Preload("Vote").Related(&articles, "Articles").RecordNotFound() { 21 | c.Abort("404") 22 | } 23 | 24 | //设置head seo参数 25 | //设置breadcrumb 26 | //设置side bar 27 | //设置head navigation bar 28 | url := fmt.Sprintf("/tag/%d", tagId) 29 | c.Data["BreadCrumbs"] = []Crumb{{"/", "fa fa-home", "首页"}, {url, "fa fa-navicon", tag.Name}} 30 | c.Data["Tag"] = tag 31 | c.Data["Articles"] = articles 32 | c.Data["Title"] = tag.Name 33 | c.Data["Keyword"] = tag.KeyWord 34 | c.Data["Description"] = tag.Description 35 | 36 | c.Layout = "layout/base_index.html" 37 | c.TplName = "tag/view.html" 38 | } 39 | func (c *TagController) LoadMore() { 40 | offset, _ := c.GetInt("offset") 41 | size, _ := c.GetInt("size") 42 | tagId, _ := c.GetInt("tagId") 43 | tag := models.Tag{} 44 | tag.ID = uint(tagId) 45 | articles := []models.Article{} 46 | models.Gorm.Model(&tag).Offset(offset).Limit(size).Order("articles.created_at DESC").Preload("Images").Preload("Tags").Preload("Vote").Related(&articles, "Articles") 47 | c.JsonRetrun("success", "欢迎访问我们的小站", articles) 48 | } 49 | 50 | func (c *TagController) IndexPost() { 51 | var tags []models.Tag 52 | models.Gorm.Find(&tags) 53 | c.JsonRetrun("success", "欢迎使用moojoTV", tags) 54 | } 55 | -------------------------------------------------------------------------------- /controllers/controllerVideo.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "www.mojotv.cn/models" 7 | "net/http" 8 | "regexp" 9 | 10 | "github.com/astaxie/beego" 11 | ) 12 | 13 | type VideoController struct { 14 | beego.Controller //集成beego controller 15 | 16 | } 17 | 18 | func (c *VideoController) WeiboVideoParse() { 19 | fid := c.GetString("id") 20 | key := fmt.Sprint("mp4.", fid) 21 | if x, found := models.CacheManager.Get(key); found { 22 | c.Data["json"] = x.(string) 23 | } else { 24 | mp4Url := c.fetchMp4Url(fid) 25 | c.Data["json"] = mp4Url 26 | //这个过期时间也许还需要调整 进过验证有效期为一小时 27 | models.CacheManager.Set(key, mp4Url, models.C_EXPIRE_TIME_MIN_01*55) 28 | } 29 | c.ServeJSON() 30 | } 31 | 32 | func (c *VideoController) fetchMp4Url(fid string) (mp4Url string) { 33 | url := fmt.Sprint("http://video.weibo.com/show?fid=", fid, "&type=mp4") 34 | //reg := regexp.MustCompile() 35 | client := &http.Client{} 36 | req, _ := http.NewRequest("GET", url, nil) 37 | req.Header.Add("Referer", "http://weibo.com/home?wvr=5") 38 | req.Header.Add("User-Agent", "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.114 Mobile Safari/537.36") 39 | req.Header.Add("Host", "www.miaopai.com") 40 | req.Header.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8") 41 | req.Header.Add("Accept-Language", "en-US,en;q=0.8") 42 | req.Header.Add("Accept-Charset", "UTF-8,*;q=0.5") 43 | mp4Url = "" 44 | if resp, err := client.Do(req); err == nil && resp.StatusCode == 200 { 45 | bodyBytes, _ := ioutil.ReadAll(resp.Body) 46 | bodyString := string(bodyBytes) 47 | //