479 lines
13 KiB
Go
479 lines
13 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"html/template"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
"crypto/rand"
|
|
"encoding/base64"
|
|
|
|
"github.com/tidwall/gjson"
|
|
|
|
"context" // <- TODO: what's this??
|
|
|
|
"golang.org/x/oauth2"
|
|
"github.com/gorilla/sessions"
|
|
//"path/filepath"
|
|
//"regexp"
|
|
//"strings"
|
|
//"maps"
|
|
//"slices"
|
|
)
|
|
|
|
|
|
var templates *template.Template = template.New("")
|
|
var client = &http.Client{Timeout: 2*time.Second}
|
|
|
|
var session_db = sessions.NewCookieStore([]byte(os.Getenv("SESSIONS_SECRET")))
|
|
|
|
|
|
type UserInfo struct{
|
|
ProviderCode, // DC--Discord, GG--Google, GH--GitHub, LC--local database
|
|
ExtID,
|
|
Username,
|
|
Fullname,
|
|
Email,
|
|
Locale string
|
|
|
|
CreatedTS,
|
|
UpdatedTS int64 // UNIX timestamps
|
|
}
|
|
|
|
var oauth2_dc_config = &oauth2.Config{
|
|
ClientID: os.Getenv("MOIRA_OAUTH2_DC_CLI_ID"),
|
|
ClientSecret: os.Getenv("MOIRA_OAUTH2_DC_CLI_SECRET"),
|
|
Scopes: []string{"identify", "email"},
|
|
RedirectURL: "https://moira.makemore.cloud/oauth2/ext/dc/clbk/",
|
|
Endpoint: oauth2.Endpoint{
|
|
AuthURL: "https://discord.com/oauth2/authorize",
|
|
TokenURL: "https://discord.com/api/oauth2/token",
|
|
},
|
|
}
|
|
|
|
func oauth2_dc_genRandState() (_ string, err error){
|
|
ret := make([]byte, 24)
|
|
|
|
_, err = rand.Read(ret)
|
|
if err != nil{ return }
|
|
|
|
return base64.RawURLEncoding.EncodeToString(ret), nil
|
|
}
|
|
|
|
func oauth2_dc_genLoginURL() (_ string, _ *http.Cookie, err error){
|
|
state, err := oauth2_dc_genRandState()
|
|
if err != nil{ return }
|
|
|
|
ret_cookie := &http.Cookie{
|
|
Name: "moira_db_oauth_state",
|
|
Value: state,
|
|
Path: "/",
|
|
HttpOnly: true,
|
|
Secure: true,
|
|
SameSite: http.SameSiteLaxMode, // TODO: what's lax??
|
|
MaxAge: 600,
|
|
}
|
|
|
|
return oauth2_dc_config.AuthCodeURL(state), ret_cookie, nil
|
|
}
|
|
|
|
func oauth2_dc_exchangeReceivedCode(req *http.Request) (_ *http.Client, err error){
|
|
cookie, err := req.Cookie("moira_db_oauth_state")
|
|
if err != nil{ return }
|
|
if cookie.Value != req.URL.Query().Get("state"){
|
|
err = errors.New("oauth2_dc: csrf test failed")
|
|
return
|
|
}
|
|
|
|
token, err := oauth2_dc_config.Exchange(context.Background(), req.URL.Query().Get("code"))
|
|
if err != nil{ return }
|
|
|
|
return oauth2_dc_config.Client(context.Background(), token), nil
|
|
}
|
|
|
|
func oauth2_fetch_dcUserInfo(in_client *http.Client) (ret []byte, err error){
|
|
req, err := http.NewRequest("GET", "https://discord.com/api/users/@me", nil)
|
|
if err != nil{ return }
|
|
|
|
//req.Header.Set("User-Agent", "Mozilla/5.0 (compatible; MoiraBot/1.0)")
|
|
|
|
resp,err := in_client.Do(req)
|
|
if err != nil{ return }
|
|
defer resp.Body.Close()
|
|
|
|
resp_text, err := io.ReadAll(resp.Body)
|
|
if err != nil{ return }
|
|
//fmt.Printf("%+v\n", string(resp_text))
|
|
|
|
return resp_text, nil
|
|
}
|
|
|
|
func oauth2_dcUserInfo_to_UserInfo(resp_text []byte) (ret UserInfo, err error){
|
|
//fmt.Printf("%s\n", string(resp_text))
|
|
// TODO: you might want to check if the data fields are not empty
|
|
|
|
ts_now := time.Now().Unix()
|
|
|
|
return UserInfo{
|
|
ProviderCode: "DC",
|
|
ExtID: gjson.GetBytes(resp_text, "id").String(),
|
|
Username: gjson.GetBytes(resp_text, "username").String(),
|
|
Fullname: gjson.GetBytes(resp_text, "global_name").String(),
|
|
Email: gjson.GetBytes(resp_text, "email").String(),
|
|
Locale: gjson.GetBytes(resp_text, "locale").String(),
|
|
|
|
CreatedTS: ts_now,
|
|
UpdatedTS: ts_now,
|
|
}, nil
|
|
}
|
|
|
|
func oauth2_hdlr_login(resp http.ResponseWriter, req *http.Request){
|
|
url, cookie, err := oauth2_dc_genLoginURL()
|
|
if err != nil {
|
|
http.Error(resp, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
http.SetCookie(resp, cookie)
|
|
http.Redirect(resp, req, url, http.StatusFound)
|
|
}
|
|
|
|
func oauth2_hdlr_clbk(resp http.ResponseWriter, req *http.Request){
|
|
client, err := oauth2_dc_exchangeReceivedCode(req)
|
|
if err != nil { return } // TODO: should the error be reported somehow?
|
|
|
|
resp_text, err := oauth2_fetch_dcUserInfo(client)
|
|
if err != nil { return } // TODO: should the error be reported somehow?
|
|
|
|
new_userInfo, err := oauth2_dcUserInfo_to_UserInfo(resp_text)
|
|
if err != nil { return } // TODO: should the error be reported somehow?
|
|
|
|
t_json, _ := json.MarshalIndent(new_userInfo, "", " ")
|
|
fmt.Printf("%+v\n", string(t_json))
|
|
|
|
http.Redirect(resp, req, "/", http.StatusFound)
|
|
}
|
|
|
|
|
|
func sessions_is_loggedIn(resp http.ResponseWriter, req *http.Request) (ret bool, err error){
|
|
session, err := session_db.Get(req, "moira_db_session_main")
|
|
if err != nil{
|
|
fmt.Println(err.Error())
|
|
return
|
|
}
|
|
|
|
if session.IsNew{
|
|
err = session.Save(req, resp)
|
|
if err != nil{
|
|
fmt.Println(err.Error())
|
|
return
|
|
}
|
|
|
|
fmt.Println("new session")
|
|
return false, nil
|
|
}
|
|
|
|
val := session.Values["user"]
|
|
if val == ""{
|
|
fmt.Println("empty user")
|
|
return false, nil
|
|
}
|
|
|
|
//if val not in user_db: false,nil
|
|
return true, nil
|
|
}
|
|
|
|
|
|
type Film struct{
|
|
Title_orig,
|
|
Title_EN,
|
|
Title_PL,
|
|
Desc,
|
|
Img,
|
|
Source string
|
|
|
|
Year int
|
|
}
|
|
var db_film = make(map[string]any)
|
|
|
|
|
|
func findURL_in_msg(in_msg string) (_ string,err error){
|
|
i := strings.Index(in_msg, "http")
|
|
if(i == -1){
|
|
err = errors.New("No URL found in: " + in_msg)
|
|
return
|
|
}
|
|
|
|
j := strings.Index(in_msg[i:], " ")
|
|
if(j == -1){
|
|
j = len(in_msg)-i
|
|
}
|
|
|
|
return in_msg[i:i+j], nil
|
|
}
|
|
|
|
func resolve_redirs(in_url string) (string,error){
|
|
resp, err := client.Get(in_url)
|
|
if(err != nil){
|
|
return "", err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
return resp.Request.URL.String(), nil
|
|
}
|
|
|
|
func fetch_filmInfo(in_url string) (_ Film, err error){
|
|
parsedUrl, err := url.Parse(in_url)
|
|
if err != nil{ return }
|
|
parsedUrl.RawQuery = ""
|
|
|
|
var in_url_api string
|
|
switch(parsedUrl.Hostname()){
|
|
case "www.filmweb.pl":
|
|
in_url_api = fmt.Sprintf(
|
|
"https://www.filmweb.pl/api/v1/film/%s/preview",
|
|
in_url[strings.LastIndex(in_url, "-")+1:],
|
|
)
|
|
|
|
case "www.imdb.com":
|
|
t_parsedUrl, _ := url.Parse(in_url)
|
|
t_parsedUrl.RawQuery = ""
|
|
t_parsedUrl.Host = "api.imdbapi.dev"
|
|
in_url_api = strings.Replace(t_parsedUrl.String(), "/title/", "/titles/", 1)
|
|
|
|
if in_url_api[len(in_url_api)-1] == '/'{
|
|
in_url_api = in_url_api[:len(in_url_api)-1]
|
|
}
|
|
|
|
default:
|
|
err = errors.New("[0] Film database unsupported " + parsedUrl.Hostname())
|
|
return
|
|
}
|
|
|
|
fmt.Println("in_url_api:", in_url_api);
|
|
|
|
req,err := http.NewRequest("GET", in_url_api, nil)
|
|
if err != nil{ return }
|
|
|
|
req.Header.Set("User-Agent", "Mozilla/5.0 (compatible; MoiraBot/1.0)")
|
|
|
|
resp,err := client.Do(req)
|
|
if err != nil{ return }
|
|
defer resp.Body.Close()
|
|
|
|
resp_text, err := io.ReadAll(resp.Body)
|
|
if err != nil{ return }
|
|
//fmt.Printf("%+v\n", string(resp_text))
|
|
|
|
titles := make(map[string]string)
|
|
switch(parsedUrl.Hostname()){
|
|
case "www.filmweb.pl":
|
|
if(len(gjson.GetBytes(resp_text, "internationalTitle").String())>0){
|
|
titles[gjson.GetBytes(resp_text, "internationalTitle.lang").String()] = gjson.GetBytes(resp_text, "internationalTitle.title").String()
|
|
titles["_int"] = gjson.GetBytes(resp_text, "internationalTitle.title").String()
|
|
}
|
|
if(len(gjson.GetBytes(resp_text, "originalTitle").String())>0){
|
|
titles[gjson.GetBytes(resp_text, "originalTitle.lang").String()] = gjson.GetBytes(resp_text, "originalTitle.title").String()
|
|
titles["_orig"] = gjson.GetBytes(resp_text, "originalTitle.title").String()
|
|
}
|
|
if(len(gjson.GetBytes(resp_text, "title").String())>0){
|
|
titles[gjson.GetBytes(resp_text, "title.lang").String()] = gjson.GetBytes(resp_text, "title.title").String()
|
|
}
|
|
return Film{
|
|
Title_orig: (func()string{
|
|
if val,ok:=titles["_orig"]; ok{
|
|
return val
|
|
}else{
|
|
return ""
|
|
}
|
|
})(),
|
|
Title_EN: (func()string{
|
|
if val,ok:=titles["en"]; ok{
|
|
return val
|
|
}else{
|
|
return ""
|
|
}
|
|
})(),
|
|
Title_PL: (func()string{
|
|
if val,ok:=titles["pl"]; ok{
|
|
return val
|
|
}else{
|
|
return ""
|
|
}
|
|
})(),
|
|
Desc: gjson.GetBytes(resp_text, "plotOrDescriptionSynopsis").String(),
|
|
Year: int(gjson.GetBytes(resp_text,"year").Int()),
|
|
Img: "https://fwcdn.pl/fpo"+gjson.GetBytes(resp_text, "poster.path").String(),
|
|
|
|
Source: in_url,
|
|
}, err
|
|
|
|
case "www.imdb.com":
|
|
if(len(gjson.GetBytes(resp_text, "primaryTitle").String())>0){
|
|
titles["en"] = gjson.GetBytes(resp_text, "primaryTitle").String()
|
|
titles["_int"] = gjson.GetBytes(resp_text, "primaryTitle").String()
|
|
}
|
|
if(len(gjson.GetBytes(resp_text, "originalTitle").String())>0){
|
|
if(len(gjson.GetBytes(resp_text, "originCountries.0.code").String())>0){
|
|
titles[strings.ToLower(gjson.GetBytes(resp_text, "originCountries.0.code").String())] = gjson.GetBytes(resp_text, "originalTitle").String()
|
|
}
|
|
titles["_orig"] = gjson.GetBytes(resp_text, "originalTitle").String()
|
|
}else{
|
|
if val,ok:=titles["_int"]; ok{
|
|
titles["_orig"] = val
|
|
}
|
|
}
|
|
return Film{
|
|
Title_orig: (func()string{
|
|
if val,ok:=titles["_orig"]; ok{
|
|
return val
|
|
}else{
|
|
return ""
|
|
}
|
|
})(),
|
|
Title_EN: (func()string{
|
|
if val,ok:=titles["en"]; ok{
|
|
return val
|
|
}else{
|
|
return ""
|
|
}
|
|
})(),
|
|
Title_PL: (func()string{
|
|
if val,ok:=titles["pl"]; ok{
|
|
return val
|
|
}else{
|
|
return ""
|
|
}
|
|
})(),
|
|
Desc: gjson.GetBytes(resp_text, "plot").String(),
|
|
Year: int(gjson.GetBytes(resp_text,"startYear").Int()),
|
|
Img: gjson.GetBytes(resp_text, "primaryImage.url").String(),
|
|
|
|
Source: in_url,
|
|
}, err
|
|
|
|
default:
|
|
err = errors.New("[1] Film database unsupported " + parsedUrl.Hostname())
|
|
return
|
|
}
|
|
}
|
|
|
|
func hdlr_root(template_renders map[string]struct{Template string;Context *map[string]any}) http.HandlerFunc{
|
|
fSrv := http.FileServer(http.Dir("./website/static"))
|
|
|
|
return func (resp http.ResponseWriter, req *http.Request){
|
|
if fileName, ok := template_renders[req.URL.Path]; ok{
|
|
fmt.Println("[srv] template", req.URL.Path, fileName.Template)
|
|
|
|
is_loggedIn, _ := sessions_is_loggedIn(resp, req)
|
|
fmt.Println(">>LoggedIn?:", is_loggedIn)
|
|
|
|
url_query := req.URL.Query()
|
|
url_query_id := url_query.Get("id")
|
|
var context any
|
|
if(url_query_id != ""){
|
|
context = (*fileName.Context)[url_query_id]
|
|
}else{
|
|
context = nil
|
|
}
|
|
err := templates.ExecuteTemplate(resp, fileName.Template, context)
|
|
|
|
if(err != nil){
|
|
http.Error(resp, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
}else{
|
|
fmt.Println("[srv] static", req.URL.Path)
|
|
|
|
fSrv.ServeHTTP(resp, req)
|
|
}
|
|
}
|
|
}
|
|
|
|
func templates_register(template_renders map[string]struct{Template string;Context *map[string]any}){
|
|
for _,i_file := range template_renders{
|
|
text, err := os.ReadFile("./website/templates/" + i_file.Template)
|
|
if(err != nil){
|
|
panic(err)
|
|
}
|
|
|
|
templates.New(i_file.Template).Parse(string(text))
|
|
}
|
|
}
|
|
|
|
func hdlr_film_parseMsg(resp http.ResponseWriter, req *http.Request){
|
|
fmt.Println("[srv] util", req.URL)
|
|
|
|
url_query := req.URL.Query()
|
|
|
|
t_url, _ := findURL_in_msg(url_query.Get("text"))
|
|
fmt.Println(t_url)
|
|
t_url_noRed, _ := resolve_redirs(t_url)
|
|
fmt.Println(t_url_noRed)
|
|
t_film, _ := fetch_filmInfo(t_url_noRed)
|
|
|
|
db_film[fmt.Sprintf("%d",len(db_film))] = t_film
|
|
|
|
t_json, _ := json.MarshalIndent(db_film, "", " ")
|
|
fmt.Printf("%+v\n", string(t_json))
|
|
fmt.Println("/film/view/?id="+fmt.Sprintf("%d",len(db_film)-1))
|
|
|
|
http.Redirect(resp, req, "/film/view/?id="+fmt.Sprintf("%d",len(db_film)-1), http.StatusFound)
|
|
}
|
|
|
|
func main(){
|
|
fmt.Println("CLIENT_ID:", oauth2_dc_config.ClientID);
|
|
fmt.Println("CLIENT_SECRET:", oauth2_dc_config.ClientSecret);
|
|
fmt.Println("SESSIONS_SECRET:", os.Getenv("SESSIONS_SECRET"));
|
|
|
|
//t_film, _ := fetch_filmInfo("https://www.filmweb.pl/film/Spirited+Away%3A+W+krainie+Bog%C3%B3w-2001-33288")
|
|
//t_film, _ := fetch_filmInfo("https://www.filmweb.pl/film/Pulp+Fiction-1994-1039")
|
|
//t_film, _ := fetch_filmInfo("https://www.filmweb.pl/film/Kl%C4%85twa+Doliny+W%C4%99%C5%BCy-1987-6785")
|
|
//t_film, _ := fetch_filmInfo("https://www.imdb.com/title/tt0245429/?ref_=nv_sr_srsg_0_tt_8_nm_0_in_0_q_spirited")
|
|
//t_film, _ := fetch_filmInfo("https://www.imdb.com/title/tt0110912/")
|
|
//t_film, _ := fetch_filmInfo("https://www.imdb.com/title/tt8814476/")
|
|
|
|
//t_url_noRed, _ := resolve_redirs("https://www.imdb.com/title/tt0095456/?ref_=nv_sr_srsg_0_tt_1_nm_0_in_0_q_kl%C4%85twa%20doliny%20w%C4%99%C5%BC")
|
|
//t_url_noRed, _ := resolve_redirs("https://share.google/EsMXhLYol3eQTKcQH")
|
|
|
|
//t_url, _ := findURL_in_msg("https://share.google/EsMXhLYol3eQTKcQH")
|
|
//t_url, _ := findURL_in_msg("abcabc https://share.google/EsMXhLYol3eQTKcQH abcabc")
|
|
//t_url, _ := findURL_in_msg("abcabchttps://share.google/EsMXhLYol3eQTKcQH abcabc")
|
|
//t_url, _ := findURL_in_msg("abcabchttps://share.google/EsMXhLYol3eQTKcQH")
|
|
//t_url, _ := findURL_in_msg("Fantastyczny Pan Lis (2009) - Filmweb https://share.google/EsMXhLYol3eQTKcQH")
|
|
//fmt.Println(t_url)
|
|
|
|
//t_url_noRed, _ := resolve_redirs(t_url)
|
|
//fmt.Println(t_url_noRed)
|
|
//t_film, _ := fetch_filmInfo(t_url_noRed)
|
|
//t_json, _ := json.MarshalIndent(t_film, "", " ")
|
|
//fmt.Printf("%+v\n", string(t_json))
|
|
|
|
template_renders := map[string]struct{Template string;Context *map[string]any} {
|
|
"/": {"index.html", nil},
|
|
"/film/view/": {"film/view/index.html", &db_film},
|
|
"/film/edit/": {"index.html", nil},
|
|
//"/list/view/": {"index.html", nil},
|
|
//"/list/edit/": {"index.html", nil},
|
|
//"/group/view/": {"index.html", nil},
|
|
//"/group/edit/": {"index.html", nil},
|
|
}
|
|
|
|
templates_register(template_renders)
|
|
http.HandleFunc("/", hdlr_root(template_renders))
|
|
|
|
|
|
http.HandleFunc("/film/parse_msg/", hdlr_film_parseMsg)
|
|
http.HandleFunc("/oauth2/ext/dc/login/", oauth2_hdlr_login)
|
|
http.HandleFunc("/oauth2/ext/dc/clbk/", oauth2_hdlr_clbk)
|
|
|
|
|
|
//fmt.Println("Welcome to moira_db!");
|
|
log.Fatal(http.ListenAndServe(":8099", nil))
|
|
}
|