aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--README.md28
-rw-r--r--utils.lua29
-rwxr-xr-xvk2rss.lua56
-rw-r--r--vkfeed.lua165
5 files changed, 279 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f40bd5e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+access_token
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..6f86cbc
--- /dev/null
+++ b/README.md
@@ -0,0 +1,28 @@
+vk2rss -- граббер ленты сообществ и пабликов ВКонтакте в RSS
+============================================================
+
+Программа vk2rss создана для конвертации ленты из паблика или группы ВКонтакте
+в формат RSS. Иногда это бывает полезно, если у вас уже есть RSS-ридер, в
+котором вы бы хотели видеть посты какого-нибудь редкого сообщества, у которого
+нет отдельного сайта с экспортом их ленты в RSS.
+
+Зависимости
+-----------
+
+- lua
+- cjson (установить можно через luarocks)
+- wget || curl
+
+Как использовать
+----------------
+
+./vk2rss <название_группы/паблика> [access token]
+
+На stdout: XML в RSS-формате с постами (последние 100 штук).
+В случае, если группа закрытая, необходимо предоставить access token.
+
+Access token
+------------
+
+Для его получения обратитесь к инструкции на стр. `https://new.vk.com/dev/auth_mobile`
+Пример: `https://oauth.vk.com/authorize?client_id=1&display=page&scope=offline&redirect_uri=https://oauth.vk.com/blank.html&response_type=token&v=5.52`
diff --git a/utils.lua b/utils.lua
new file mode 100644
index 0000000..56492f4
--- /dev/null
+++ b/utils.lua
@@ -0,0 +1,29 @@
+function string.nl2br(input)
+ return input:gsub( "\n", "<br />" )
+end
+
+function string.starts(input, start)
+ return input:sub( 1, string.len( start ) ) == start
+end
+
+function io.readAll(file)
+ local f = io.open(file, "rb")
+ local content = f:read("*all")
+ f:close()
+ return content
+end
+
+function os.download( url )
+ file = os.tmpname()
+ os.execute( "/usr/bin/wget -qO- '" .. url .. "' > " .. file )
+ data = io.readAll( file )
+ os.remove( file )
+
+ return data
+end
+
+function fatal( string )
+ print( string )
+ os.exit( 2 )
+end
+
diff --git a/vk2rss.lua b/vk2rss.lua
new file mode 100755
index 0000000..f611f40
--- /dev/null
+++ b/vk2rss.lua
@@ -0,0 +1,56 @@
+#!/usr/bin/lua
+
+local xml = require "xml"
+
+dofile( "vkfeed.lua" )
+dofile( "utils.lua" )
+
+function usage()
+ print( "usage: vk2rss <domain> [token]" )
+ os.exit(1)
+end
+
+function main()
+ local argc = table.maxn( arg )
+ if( argc < 1 ) then
+ usage()
+ end
+
+ local domain = arg[1]
+ local access_token = nil
+
+ if( argc == 2 ) then
+ access_token = arg[2]
+ end
+
+ local feedInfoJson = getFeedInfo( domain, access_token )
+ local feedInfo = parseFeedInfo( feedInfoJson )
+ local feedJson = getFeed( domain, 100, 0, access_token )
+ local feed = parseFeed( feedInfo["name"], feedJson )
+
+ if( feed == nil ) then
+ fatal( "Can't get feed from " .. domain )
+ end
+
+ local rss = { xml = 'rss', version="2.0",
+ { xml = 'channel',
+ { xml = 'title', feedInfo["name"] },
+ { xml = 'description', feedInfo["description"] },
+ { xml = 'link', feedInfo["url"] },
+ },
+ }
+
+ for key, item in ipairs( feed ) do
+ table.insert( rss[1], { xml = 'item',
+ { xml = 'title', item["title"] },
+ { xml = 'pubDate', item["date"] },
+ { xml = 'link', item["link"] },
+ { xml = 'description', item["description"] },
+ })
+ end
+
+ print( '<?xml version="1.0" encoding="utf-8"?>' )
+ print( xml.dump( rss ) )
+end
+
+main()
diff --git a/vkfeed.lua b/vkfeed.lua
new file mode 100644
index 0000000..767e544
--- /dev/null
+++ b/vkfeed.lua
@@ -0,0 +1,165 @@
+dofile( "utils.lua" )
+
+local json = require "cjson"
+
+local API_VK_SERVER = "https://api.vk.com/"
+local VK_BASE_DOMAIN = "https://vk.com/"
+
+function parseFeed( feedName, jsonText )
+ local items = {}
+
+ object = json.decode( jsonText )
+ if( object["response"] == nil ) then
+ return nil
+ end
+
+ for k, item in ipairs( object["response"]["items"] ) do
+ if (item["is_pinned"] ~= 1) then
+ local link = VK_BASE_DOMAIN .. "/wall" .. item["owner_id"] .. "_" .. item["id"]
+ local description = item["text"]
+ local date = os.date( "%a, %d %b %Y %X GMT", item["date"] )
+
+ local photos = {}
+ local links = {}
+ local videos = {}
+
+ if( item["attachments"] ~= nil ) then
+ for k, attach in ipairs( item["attachments"] ) do
+ if( attach["type"] == "photo" ) then
+
+ local attachInfo = attach["photo"]
+ local photoSizes = getSortPhotos( attachInfo )
+ table.insert( photos, { small = photoSizes[3], large = photoSizes[ table.maxn( photoSizes ) ] } )
+
+ elseif( attach["type"] == "link" ) then
+
+ local attachInfo = attach["link"]
+ local photo = nil
+ if ( attachInfo["photo"] ~= nil ) then
+ local photoSizes = getSortPhotos(attachInfo["photo"])
+ photo = photoSizes[1]
+ end
+ table.insert( links, { photo = photo,
+ url = attachInfo["url"],
+ title = attachInfo["title"] } )
+
+ elseif( attach["type"] == "video" ) then
+ local attachInfo = attach["video"]
+ local photoSizes = getSortPhotos( attachInfo )
+ local url = VK_BASE_DOMAIN .. "/video" .. attachInfo["owner_id"] .. "_" .. attachInfo["id"]
+ table.insert( videos, { photo = photoSizes[2],
+ url = url,
+ title = attachInfo["title"] } )
+ end
+ end
+ end
+
+ table.insert( items,
+ createItem( date, title, description, link, videos, photos, links ) )
+ end
+ end
+
+ return items
+end
+
+function getFeed( domain, count, offset, access_token )
+ local url = ""
+ if( tonumber( domain ) ~= nil ) then
+ url = API_VK_SERVER .. "/method/wall.get?owner_id=-" .. domain .. "&offset=" .. offset .. "&count=" .. count .. "&v=5.44"
+ else
+ url = API_VK_SERVER .. "/method/wall.get?domain=" .. domain .. "&offset=" .. offset .. "&count=" .. count .. "&v=5.44"
+ end
+
+ if( access_token ~= nil ) then
+ url = url .. "&access_token=" .. access_token
+ end
+ return os.download( url )
+end
+
+function getFeedInfo( domain, access_token )
+ local url = API_VK_SERVER .. "/method/groups.getById?group_id=" .. domain .. "&fields=city,country,place,description,wiki_page,members_count,counters,start_date,finish_date,can_post,can_see_all_posts,activity,status,contacts,links,fixed_post,verified,site,ban_info&v=5.44"
+ if( access_token ~= nil ) then
+ url = url .. "&access_token=" .. access_token
+ end
+ return os.download( url )
+end
+
+function parseFeedInfo( jsonText )
+ local object = json.decode( jsonText )
+ if( object["response"] == nil ) then
+ return nil
+ end
+
+ local feed = object["response"][1]
+ local info = {}
+
+ info["name"] = feed["name"]
+ info["description"] = feed["description"]
+
+ if feed["screen_name"] ~= nil then
+ info["url"] = VK_BASE_DOMAIN .. "/" .. feed["screen_name"]
+ elseif feed["type"] == "page" then
+ info["url"] = VK_BASE_DOMAIN .. "/public" .. feed["id"]
+ elseif feed["type"] == "group" then
+ info["url"] = VK_BASE_DOMAIN .. "/group" .. feed["id"]
+ end
+
+ return info
+end
+
+function createItem( date, title, description, link, videos, photos, links )
+ local photoMaxHeight = 100
+ if( table.maxn(photos) == 1 ) then
+ photoMaxHeight = nil
+ end
+
+ photosHtml = "<p>"
+ for k, photo in ipairs(photos) do
+ if photoMaxHeight ~= nil then
+ photosHtml = photosHtml .. "<a href='" .. photo["large"] .. "'><img hspace='3' height=".. photoMaxHeight .." src='" .. photo["small"] .. "'/></a>"
+ else
+ photosHtml = photosHtml .. "<a href='" .. photo["large"] .. "'><img hspace='3' src='" .. photo["small"] .. "'/></a>"
+ end
+ end
+ photosHtml = photosHtml .. "</p>"
+
+ linksHtml = "<p>"
+ for k, link in ipairs(links) do
+ if link["photo"] ~= nil then
+ linksHtml = linksHtml .. "<a href='" .. link["url"] .. "'><img hspace='6' src='" .. link["photo"] .. "'/>" .. link["title"] .."</a>"
+ else
+ linksHtml = linksHtml .. "<a href='" .. link["url"] .. "'>" .. link["title"] .."</a>"
+ end
+ end
+ linksHtml = linksHtml .. "</p>"
+
+ videosHtml = "<p>"
+ for k, video in ipairs(videos) do
+ videosHtml = videosHtml .. "<a href='" .. video["url"] .. "'><img hspace='3' src='" .. video["photo"] .. "'/><p>" .. video["title"] .."</p></a>"
+ end
+ videosHtml = videosHtml .. "</p>"
+
+ return { title = title,
+ pubDate = date,
+ link = link,
+ description = description:nl2br() .. photosHtml .. videosHtml .. linksHtml }
+end
+
+function getSortPhotos(photo)
+ local sizes = {}
+ for key in pairs(photo) do
+ if( key:starts( "photo_" ) ) then
+ local size = tonumber( key:sub( string.len( "photo_" ) + 1 ) )
+ table.insert( sizes, size )
+ end
+ end
+
+ table.sort( sizes )
+
+ local photos = {}
+ for k, size in ipairs(sizes) do
+ table.insert( photos, photo[ "photo_" .. size ] )
+ end
+
+ return photos
+end