383 lines
8.7 KiB
Go
383 lines
8.7 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/xml"
|
|
"io"
|
|
"net/http"
|
|
"time"
|
|
|
|
lua "github.com/yuin/gopher-lua"
|
|
)
|
|
|
|
type RssFeed struct {
|
|
Title string // This is the title of the rss feed
|
|
Link string // This is the link to original rss feed (if available)
|
|
Content string // This contains the entire rss xml
|
|
LastSyncTime string // This is the last time the rss feed was synced
|
|
}
|
|
|
|
type RssRoot struct {
|
|
XMLName xml.Name `xml:"rss"`
|
|
Channel RssChannel `xml:"channel"`
|
|
}
|
|
|
|
type RssChannel struct {
|
|
Title string `xml:"title"`
|
|
Link string `xml:"link"`
|
|
Description string `xml:"description"`
|
|
Image RssImage `xml:"image"`
|
|
Items []RssItem `xml:"item"`
|
|
}
|
|
|
|
type RssItem struct {
|
|
Title string `xml:"title"`
|
|
Link string `xml:"link"`
|
|
Description string `xml:"description"`
|
|
Category string `xml:"category"`
|
|
Guid string `xml:"guid"`
|
|
PubDate string `xml:"pubDate"`
|
|
}
|
|
|
|
type RssImage struct {
|
|
Url string `xml:"url"`
|
|
Title string `xml:"title"`
|
|
Link string `xml:"link"`
|
|
}
|
|
|
|
const luaRSSGlobalTable = "rss"
|
|
const luaRSSItemTypeName = "RssItem"
|
|
const luaRSSImageTypeName = "RssImage"
|
|
|
|
var luaRSSItemMethods = map[string]lua.LGFunction{
|
|
"title": getSetRssItemTitle,
|
|
"link": getSetRssItemLink,
|
|
"description": getSetRssItemDescription,
|
|
"category": getSetRssItemCategory,
|
|
"guid": getSetRssItemGuid,
|
|
"pubDate": getSetRssItemPubDate,
|
|
}
|
|
|
|
var luaRSSImageMethods = map[string]lua.LGFunction{
|
|
"title": getSetRssImageTitle,
|
|
"url": getSetRssImageUrl,
|
|
"link": getSetRssImageLink,
|
|
}
|
|
|
|
func registerRSSItemType(L *lua.LState) {
|
|
logger.Debug("Registering RssItem type")
|
|
mt := L.NewTypeMetatable(luaRSSItemTypeName)
|
|
L.SetGlobal(luaRSSItemTypeName, mt)
|
|
L.SetField(mt, "new", L.NewFunction(newRSSItem))
|
|
L.SetField(mt, "__index", L.SetFuncs(L.NewTable(), luaRSSItemMethods))
|
|
|
|
// Create a global table called "rss"
|
|
// This table will contain all of the rss helper functions
|
|
rssTable := L.NewTable()
|
|
L.SetGlobal(luaRSSGlobalTable, rssTable)
|
|
L.SetField(rssTable, "get", L.NewFunction(getRssFeedHttp))
|
|
L.SetField(rssTable, "merge", L.NewFunction(mergeRssItems))
|
|
L.SetField(rssTable, "limit", L.NewFunction(limitRssItems))
|
|
L.SetField(rssTable, "generate", L.NewFunction(generateRssFeed))
|
|
}
|
|
|
|
func registerRssImageType(L *lua.LState) {
|
|
logger.Debug("Registering RssImage type")
|
|
mt := L.NewTypeMetatable(luaRSSImageTypeName)
|
|
L.SetGlobal(luaRSSImageTypeName, mt)
|
|
L.SetField(mt, "new", L.NewFunction(newRSSImage))
|
|
L.SetField(mt, "__index", L.SetFuncs(L.NewTable(), luaRSSImageMethods))
|
|
}
|
|
|
|
func newRSSImage(L *lua.LState) int {
|
|
ud := L.NewUserData()
|
|
if L.GetTop() != 3 {
|
|
L.ArgError(3, "Expected 3 arguments")
|
|
return 0
|
|
}
|
|
// TODO: Handle the error better
|
|
title := L.CheckString(1)
|
|
url := L.CheckString(2)
|
|
link := L.CheckString(3)
|
|
item := RssImage{
|
|
Url: url,
|
|
Title: title,
|
|
Link: link,
|
|
}
|
|
ud.Value = &item
|
|
L.SetMetatable(ud, L.GetTypeMetatable(luaRSSImageTypeName))
|
|
L.Push(ud)
|
|
return 1
|
|
}
|
|
|
|
func checkRSSImage(L *lua.LState) *RssImage {
|
|
ud := L.CheckUserData(1)
|
|
if v, ok := ud.Value.(*RssImage); ok {
|
|
return v
|
|
}
|
|
L.ArgError(1, "RssImage expected")
|
|
return nil
|
|
}
|
|
|
|
func getSetRssImageTitle(L *lua.LState) int {
|
|
i := checkRSSImage(L)
|
|
if L.GetTop() == 2 {
|
|
i.Title = L.CheckString(2)
|
|
return 0
|
|
}
|
|
L.Push(lua.LString(i.Title))
|
|
return 1
|
|
}
|
|
|
|
func getSetRssImageUrl(L *lua.LState) int {
|
|
i := checkRSSImage(L)
|
|
if L.GetTop() == 2 {
|
|
i.Url = L.CheckString(2)
|
|
return 0
|
|
}
|
|
L.Push(lua.LString(i.Url))
|
|
return 1
|
|
}
|
|
|
|
func getSetRssImageLink(L *lua.LState) int {
|
|
i := checkRSSImage(L)
|
|
if L.GetTop() == 2 {
|
|
i.Link = L.CheckString(2)
|
|
return 0
|
|
}
|
|
L.Push(lua.LString(i.Link))
|
|
return 1
|
|
}
|
|
|
|
func checkRSSItem(L *lua.LState) *RssItem {
|
|
ud := L.CheckUserData(1)
|
|
if v, ok := ud.Value.(*RssItem); ok {
|
|
return v
|
|
}
|
|
L.ArgError(1, "RssItem expected")
|
|
return nil
|
|
}
|
|
|
|
func newRSSItem(L *lua.LState) int {
|
|
ud := L.NewUserData()
|
|
|
|
if L.GetTop() != 1 {
|
|
L.ArgError(1, "Expected 1 argument")
|
|
return 0
|
|
}
|
|
item := L.CheckUserData(1)
|
|
ud.Value = item
|
|
L.SetMetatable(ud, L.GetTypeMetatable(luaRSSItemTypeName))
|
|
L.Push(ud)
|
|
return 1
|
|
}
|
|
|
|
// Getters and setters for the RssItem type
|
|
func getSetRssItemTitle(L *lua.LState) int {
|
|
i := checkRSSItem(L)
|
|
if L.GetTop() == 2 {
|
|
i.Title = L.CheckString(2)
|
|
return 0
|
|
}
|
|
L.Push(lua.LString(i.Title))
|
|
return 1
|
|
}
|
|
|
|
func getSetRssItemLink(L *lua.LState) int {
|
|
i := checkRSSItem(L)
|
|
if L.GetTop() == 2 {
|
|
i.Link = L.CheckString(2)
|
|
return 0
|
|
}
|
|
L.Push(lua.LString(i.Link))
|
|
return 1
|
|
}
|
|
|
|
func getSetRssItemDescription(L *lua.LState) int {
|
|
i := checkRSSItem(L)
|
|
if L.GetTop() == 2 {
|
|
i.Description = L.CheckString(2)
|
|
return 0
|
|
}
|
|
L.Push(lua.LString(i.Description))
|
|
return 1
|
|
}
|
|
|
|
func getSetRssItemCategory(L *lua.LState) int {
|
|
i := checkRSSItem(L)
|
|
if L.GetTop() == 2 {
|
|
i.Category = L.CheckString(2)
|
|
return 0
|
|
}
|
|
L.Push(lua.LString(i.Category))
|
|
return 1
|
|
}
|
|
|
|
func getSetRssItemGuid(L *lua.LState) int {
|
|
i := checkRSSItem(L)
|
|
if L.GetTop() == 2 {
|
|
i.Guid = L.CheckString(2)
|
|
return 0
|
|
}
|
|
L.Push(lua.LString(i.Guid))
|
|
return 1
|
|
}
|
|
|
|
func getSetRssItemPubDate(L *lua.LState) int {
|
|
i := checkRSSItem(L)
|
|
if L.GetTop() == 2 {
|
|
i.PubDate = L.CheckString(2)
|
|
return 0
|
|
}
|
|
L.Push(lua.LString(i.PubDate))
|
|
return 1
|
|
}
|
|
|
|
func ParseRssFeed(body string) *RssRoot {
|
|
var rss RssRoot
|
|
err := xml.Unmarshal([]byte(body), &rss)
|
|
if err != nil {
|
|
logger.Fatal(err)
|
|
return nil
|
|
}
|
|
return &rss
|
|
}
|
|
|
|
func GetHTTPRequest(url string) string {
|
|
logger.Debug("GET: ", url)
|
|
resp, err := http.Get(url)
|
|
if err != nil {
|
|
logger.Error(err)
|
|
return ""
|
|
}
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
logger.Fatal(err)
|
|
return ""
|
|
}
|
|
return string(body)
|
|
}
|
|
|
|
func getRssFeedHttp(L *lua.LState) int {
|
|
if L.GetTop() != 2 {
|
|
L.ArgError(1, "Expected 1 argument")
|
|
return 0
|
|
}
|
|
// Get the url from the first argument
|
|
url := L.CheckString(2)
|
|
|
|
// Check if the rss feed exists in the database
|
|
var ret string
|
|
currentTime := time.Now().UTC()
|
|
if !appConfig.ShouldCache {
|
|
// If the cache option is disabled, then always fetch the latest entries from the website
|
|
logger.Debug("Cache option is disabled. Syncing: ", url)
|
|
body := GetHTTPRequest(url)
|
|
L.Push(lua.LString(body))
|
|
return 1
|
|
}
|
|
|
|
// TODO: There might be some cleanup opportunities here
|
|
if !DoesRssFeedExist(url) {
|
|
// If it does not exist in the database, then get the latest entries from the website and insert them into the database
|
|
logger.Debug("Rss feed does not exist in database. Syncing for the first time: ", url)
|
|
|
|
body := GetHTTPRequest(url)
|
|
feed := ParseRssFeed(body)
|
|
rssFeed := RssFeed{
|
|
Title: feed.Channel.Title,
|
|
Link: url,
|
|
Content: string(body),
|
|
LastSyncTime: currentTime.Format(time.RFC3339),
|
|
}
|
|
insert_rss_feed(rssFeed)
|
|
ret = body
|
|
} else {
|
|
logger.Debug("Rss feed exists in database but sync is required: ", url)
|
|
body := GetHTTPRequest(url)
|
|
feed := ParseRssFeed(body)
|
|
rssFeed := RssFeed{
|
|
Title: feed.Channel.Title,
|
|
Link: url,
|
|
Content: string(body),
|
|
LastSyncTime: currentTime.Format(time.RFC3339),
|
|
}
|
|
update_rss_feed(rssFeed)
|
|
ret = body
|
|
|
|
}
|
|
//TODO: If the cache option is disabled, then always fetch the latest entries from the website
|
|
L.Push(lua.LString(ret))
|
|
return 1
|
|
}
|
|
|
|
func mergeRssItems(L *lua.LState) int {
|
|
table1 := L.CheckTable(1)
|
|
table2 := L.CheckTable(2)
|
|
|
|
if table1.Len() == 0 {
|
|
L.Push(table2)
|
|
return 1
|
|
} else if table2.Len() == 0 {
|
|
L.Push(table1)
|
|
return 1
|
|
}
|
|
table2.ForEach(func(_ lua.LValue, value lua.LValue) {
|
|
table1.Append(value)
|
|
})
|
|
L.Push(table1)
|
|
return 1
|
|
}
|
|
|
|
func limitRssItems(L *lua.LState) int {
|
|
table := L.CheckTable(1)
|
|
limit := L.CheckInt(2)
|
|
|
|
for i := table.Len(); i > limit; i-- {
|
|
table.Remove(i)
|
|
}
|
|
L.Push(table)
|
|
return 1
|
|
}
|
|
|
|
func generateRssFeed(L *lua.LState) int {
|
|
// Creating a new rss feed from scratch
|
|
title := L.CheckString(1)
|
|
entries := L.CheckTable(2) // For right now this will only be a string array of the description
|
|
|
|
feed := RssRoot{}
|
|
feed.Channel = RssChannel{
|
|
Title: title,
|
|
Link: "http://example.com",
|
|
Description: "This is an example rss feed",
|
|
Image: RssImage{
|
|
Url: "http://example.com/image.jpg",
|
|
Title: "Example Image",
|
|
Link: "http://example.com",
|
|
},
|
|
}
|
|
|
|
// Append the items to the feed
|
|
// Notably the <channel> tag as <item>s
|
|
items := []RssItem{}
|
|
for i := 1; i <= entries.Len(); i++ {
|
|
entry := entries.RawGetInt(i)
|
|
item := RssItem{
|
|
Title: "Novemeber 2023",
|
|
Link: "http://example.com",
|
|
Description: entry.String(),
|
|
Category: "Reverse Engineering",
|
|
Guid: "http://example.com",
|
|
PubDate: time.Now().UTC().Format(time.RFC3339),
|
|
}
|
|
items = append(items, item)
|
|
}
|
|
feed.Channel.Items = items
|
|
output, err := xml.MarshalIndent(feed, "", " ")
|
|
if err != nil {
|
|
logger.Fatal(err)
|
|
return 0
|
|
}
|
|
L.Push(lua.LString(string(output)))
|
|
return 1
|
|
}
|