Skip to content

Commit 01e9102

Browse files
committed
First commit
0 parents  commit 01e9102

File tree

6 files changed

+407
-0
lines changed

6 files changed

+407
-0
lines changed

README.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# rss2post v0.0.1
2+
Постинг RSS новостей в социальные сети
3+
(на данный момент поддерживается только VK).
4+
5+
Запуск по cron.
6+
Пример использования:
7+
8+
```PHP
9+
10+
include_once 'rss2post.php';
11+
12+
$configRss = array (
13+
14+
// Адрес RSS-ленты.
15+
'rssFeedUrl' => '',
16+
17+
// Извлекать ли картинки из новости?
18+
// Берется первая картинка из текста новости, если она там есть.
19+
'extractImages' => true,
20+
21+
// RSS-новости опубликованные раньше этой даты игнорируются. Формат: '01/23/2014'.
22+
// Пригодится, если нужно начать постить новости, начиная с конкретной даты. Или false, если не надо.
23+
'actualDate' => false
24+
);
25+
26+
$configSocNetVK = array (
27+
28+
// Название социальной сети.
29+
// Используется в качестве имени файла лога в папке "logs".
30+
'socNetName' => 'VK',
31+
32+
// Токен для доступа к VK api. Получать здесь: https://vk.com/dev/auth_mobile
33+
// Необходимо выдать необходимые привелегии (wall) для доступа к стене.
34+
'vkAccessToken' => 'd7fjs77d5dchf3o3p74315f279a5ffdh47fjs92d6dfd2o9rn7vd12c8gud74js9d2fd5cbc20c6bfs9ff7eo6',
35+
36+
// ID группы или пользователя, где небходимо опубликовать пост.
37+
// ID группы указывается со знаком минус вначале, ID полльзователя без.
38+
'vkPublicID' => -12345678,
39+
40+
// Пост от имени группы (1) или от имени пользователя (0).
41+
'fromGroup' => 1,
42+
43+
// Полностью настраиваемый шаблон поста. В $rssItem передаются все поля из RSS узла <item>
44+
// Можно использовать функцию Rss2Vk_Utils::decodeHtmlEnt для декодирования сущностей навроде &amp; => &
45+
'postTemplate' => function ($rssItem) { return
46+
Rss2Post::decodeHtmlEnt($rssItem['title'])."\n".
47+
$rssItem['link']."\n--------\n".
48+
Rss2Post::decodeHtmlEnt($rssItem['source'])."\n";
49+
}
50+
);
51+
52+
// Инициализация и запуск.
53+
$rssFeed = new RssFeed($configRss);
54+
$loger = new Loger('logs/');
55+
$socNets = array (
56+
'VK' => new VKapi($configSocNetVK)
57+
// TODO: 'Twitter' => new TwitterApi($configTwitterApi);
58+
// TODO: 'Facebook' => new FacebookApi($configFacebookApi);
59+
);
60+
$App = new Rss2Post($rssFeed, $socNets, $loger);
61+
$App->run();
62+
63+
```

classes/loger.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?
2+
class Loger {
3+
private $logFolder;
4+
const DELIMITER = ' | ';
5+
6+
function __construct($logFolder) {
7+
$this->logFolder = $logFolder;
8+
}
9+
10+
public function loadLastRssDate ($SocNetName) {
11+
if (file_exists($this->logFolder.$SocNetName.'.log')) {
12+
$lastRssDate = explode(self::DELIMITER, end(file($this->logFolder.$SocNetName.'.log')));
13+
return trim($lastRssDate[1]);
14+
}
15+
return false;
16+
}
17+
18+
public function savePostDone ($SocNetName, $rssItem) {
19+
if (!file_exists($this->logFolder)) mkdir($this->logFolder);
20+
$title = str_replace("\n", '', $rssItem['title']);
21+
$logStr = date('d.m.Y H:i:s').self::DELIMITER.$rssItem['pubDate'].self::DELIMITER.$title."\n";
22+
file_put_contents($this->logFolder.$SocNetName.'.log', $logStr, FILE_APPEND | LOCK_EX);
23+
}
24+
25+
public function savePostError ($SocNetName, $error_message, $error_var_dump) {
26+
if (!file_exists($this->logFolder)) mkdir($this->logFolder);
27+
$logStr = "=================\n".date('d.m.Y H:i:s')."\n=================\n".
28+
$error_message."\n".var_export($error_var_dump, true)."\n\n";
29+
file_put_contents($this->logFolder.'_'.$SocNetName.'_error.log', $logStr, FILE_APPEND | LOCK_EX);
30+
}
31+
32+
}
33+
?>

classes/rssfeed.php

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?
2+
class RssFeed {
3+
private $config;
4+
5+
function __construct($configRss) {
6+
$this->config = $configRss;
7+
}
8+
9+
public function getConfigActualDate() {
10+
return trim($this->config['actualDate']);
11+
}
12+
13+
public function getRssNews() {
14+
15+
if ($this->config['actualDate']) $actualDate = new DateTime($this->config['actualDate']);
16+
else $actualDate = false;
17+
18+
// Загружаем RSS-ленту.
19+
$rss = simplexml_load_file($this->config['rssFeedUrl']);
20+
if ($rss === false) {
21+
// $this->Log->saveError('Error parse RSS', $this->rss2VkConfig['rssFeedUrl']);
22+
return 'Error parse RSS: '.$this->config['rssFeedUrl'];
23+
}
24+
25+
// Возвращаем массив новостей.
26+
$rssItems = array();
27+
foreach($rss->xpath('//item') as $rssItem) {
28+
$rssItemPubDate = DateTime::createFromFormat(DateTime::RSS, (string)$rssItem->pubDate);
29+
if (($actualDate != false) && ($rssItemPubDate <= $actualDate)) continue;
30+
$newRssItem = (array) $rssItem->children();
31+
$newRssItem['description'] = (string) $newRssItem['description'];
32+
33+
// Если постим с картинками, то из текста новости берем адрес первой картинки (если она есть).
34+
if ($this->config['extractImages']) {
35+
preg_match('/<img[^>]+src=([\'"])?((?(1).+?|[^\s>]+))(?(1)\1)/', $newRssItem['description'], $matchesImg);
36+
if (isset($matchesImg[2])) {
37+
$newRssItem['postImage'] = $matchesImg[2];
38+
} else {
39+
$newRssItem['postImage'] = false;
40+
}
41+
}
42+
43+
array_push($rssItems, $newRssItem);
44+
}
45+
46+
if (count($rssItems)) return $rssItems;
47+
else return false;
48+
}
49+
50+
}
51+
?>

classes/vkapi.php

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
<?
2+
class VKapi {
3+
private $configVKapi;
4+
5+
function __construct($configVKapi) {
6+
$this->config = $configVKapi;
7+
}
8+
9+
public function getSocNetName() {
10+
return $this->config['socNetName'];
11+
}
12+
13+
function preparePost($rssItem) {
14+
15+
/* Подготавливаем новый пост */
16+
$postParams = array(
17+
'owner_id' => $this->config['vkPublicID'],
18+
'from_group' => $this->config['fromGroup'],
19+
'message' => $this->config['postTemplate']($rssItem),
20+
);
21+
22+
// Если есть картинка, то скачиваем ее к себе на сервер и заливаем на сервер VK
23+
// (чтобы приаттачить ее по полученному в ответе id).
24+
if ($rssItem['postImage']) {
25+
$imgDownUpResponse = $this->imgDownloadUpload($rssItem['postImage'], $this->config['vkPublicID']);
26+
if ($imgDownUpResponse[0]->id) {
27+
$postParams['attachments'] = "{$imgDownUpResponse[0]->id}";
28+
}
29+
}
30+
31+
return $postParams;
32+
}
33+
34+
function doPost($preparatedPost) {
35+
$newVkPostResponse = $this->api('wall.post', $preparatedPost);
36+
if ($newVkPostResponse->post_id) {
37+
$postResult = array(
38+
'error' => false,
39+
'response' => $newVkPostResponse
40+
);
41+
} else {
42+
$postResult = array(
43+
'error' => true,
44+
'response' => $newVkPostResponse
45+
);
46+
}
47+
return $postResult;
48+
}
49+
50+
51+
public function imgDownloadUpload($img, $publicID) {
52+
/* Download image */
53+
$ch = curl_init($img);
54+
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
55+
$imgDown = curl_exec($ch);
56+
curl_close($ch);
57+
$imgfn = uniqid().'.jpg';
58+
file_put_contents($imgfn, $imgDown);
59+
if (strpos(mime_content_type($imgfn), 'image') === false) {
60+
echo 'mime_content_type: '.mime_content_type($imgfn);
61+
echo "\n Не удалось получить изображение. Возможно, защита от хотлинка \n";
62+
unlink($imgfn);
63+
return false;
64+
}
65+
66+
/* Upload image */
67+
$imgUpResponse = $this->api('photos.getWallUploadServer', array('group_id' => abs($publicID)));
68+
$uploadURL = $imgUpResponse->upload_url;
69+
$fullServerPathToImage = $imgfn;
70+
$output = array();
71+
exec("curl -X POST -F 'photo=@$fullServerPathToImage' '$uploadURL'", $output);
72+
$imgUpResponse = json_decode($output[0]);
73+
$imgUpResponse = $this->api('photos.saveWallPhoto', array(
74+
'group_id' => abs($publicID),
75+
'photo' => $imgUpResponse->photo,
76+
'server' => $imgUpResponse->server,
77+
'hash' => $imgUpResponse->hash,
78+
));
79+
80+
unlink($imgfn);
81+
return $imgUpResponse;
82+
}
83+
84+
public function api($method, array $query = array()) {
85+
/* Generate query string from array */
86+
$parameters = array();
87+
foreach ($query as $param => $value) {
88+
$q = $param . '=';
89+
if (is_array($value)) {
90+
$q .= urlencode(implode(',', $value));
91+
} else {
92+
$q .= urlencode($value);
93+
}
94+
$parameters[] = $q;
95+
}
96+
$q = implode('&', $parameters);
97+
if (count($query) > 0) {
98+
$q .= '&'; // Add "&" sign for access_token if query exists
99+
}
100+
$url = 'https://api.vk.com/method/' . $method . '?' . $q . 'access_token=' . $this->config['vkAccessToken'];
101+
$result = json_decode($this->curl($url));
102+
if (isset($result->response)) {
103+
return $result->response;
104+
}
105+
return $result;
106+
}
107+
108+
private function curl($url) {
109+
$ch = curl_init();
110+
curl_setopt($ch, CURLOPT_URL, $url);
111+
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
112+
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
113+
$result = curl_exec($ch);
114+
if (!$result) {
115+
$errno = curl_errno($ch);
116+
$error = curl_error($ch);
117+
}
118+
curl_close($ch);
119+
if (isset($errno) && isset($error)) {
120+
throw new \Exception($error, $errno);
121+
}
122+
return $result;
123+
}
124+
125+
}
126+
?>

cron.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?
2+
include_once 'rss2post.php';
3+
4+
$configRss = array (
5+
6+
// Адрес RSS-ленты.
7+
'rssFeedUrl' => '',
8+
9+
// Извлекать ли картинки из новости?
10+
// Берется первая картинка из текста новости, если она там есть.
11+
'extractImages' => true,
12+
13+
// RSS-новости опубликованные раньше этой даты игнорируются. Формат: '01/23/2014'.
14+
// Пригодится, если нужно начать постить новости, начиная с конкретной даты. Или false, если не надо.
15+
'actualDate' => false
16+
);
17+
18+
$configSocNetVK = array (
19+
20+
// Название социальной сети.
21+
// Используется в качестве имени файла лога в папке "logs".
22+
'socNetName' => 'VK',
23+
24+
// Токен для доступа к VK api. Получать здесь: https://vk.com/dev/auth_mobile
25+
// Необходимо выдать необходимые привелегии (wall) для доступа к стене.
26+
'vkAccessToken' => 'd7fjs77d5dchf3o3p74315f279a5ffdh47fjs92d6dfd2o9rn7vd12c8gud74js9d2fd5cbc20c6bfs9ff7eo6',
27+
28+
// ID группы или пользователя, где небходимо опубликовать пост.
29+
// ID группы указывается со знаком минус вначале, ID полльзователя без.
30+
'vkPublicID' => -12345678,
31+
32+
// Пост от имени группы (1) или от имени пользователя (0).
33+
'fromGroup' => 1,
34+
35+
// Полностью настраиваемый шаблон поста. В $rssItem передаются все поля из RSS узла <item>
36+
// Можно использовать функцию Rss2Vk_Utils::decodeHtmlEnt для декодирования сущностей навроде &amp; => &
37+
'postTemplate' => function ($rssItem) { return
38+
Rss2Post::decodeHtmlEnt($rssItem['title'])."\n".
39+
$rssItem['link']."\n--------\n".
40+
Rss2Post::decodeHtmlEnt($rssItem['source'])."\n";
41+
}
42+
);
43+
44+
// Инициализация и запуск.
45+
$rssFeed = new RssFeed($configRss);
46+
$loger = new Loger('logs/');
47+
$socNets = array (
48+
'VK' => new VKapi($configSocNetVK)
49+
// TODO: 'Twitter' => new TwitterApi($configTwitterApi);
50+
// TODO: 'Facebook' => new FacebookApi($configFacebookApi);
51+
);
52+
$App = new Rss2Post($rssFeed, $socNets, $loger);
53+
$App->run();
54+
?>

0 commit comments

Comments
 (0)