Changes
This commit is contained in:
@@ -2,36 +2,140 @@
|
||||
|
||||
namespace App\Services\VK\Album;
|
||||
|
||||
use App\Services\VK\VkAuthService;
|
||||
use CURLFile;
|
||||
use GuzzleHttp\Client;
|
||||
use Illuminate\Http\Client\RequestException;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use VK\Client\VKApiClient;
|
||||
|
||||
class VkAlbumService
|
||||
{
|
||||
private VKApiClient $vk;
|
||||
private VkAuthService $vkAuthService;
|
||||
|
||||
private string $wallToken;
|
||||
private string $serviceToken;
|
||||
private string $publicId;
|
||||
private string $publicDomain;
|
||||
|
||||
public function __construct(VKApiClient $vk) {
|
||||
$this->vk = $vk;
|
||||
$this->wallToken = env('WALL_ACCESS_VK_TOKEN');
|
||||
$this->vkAuthService = new VkAuthService();
|
||||
$this->serviceToken = env('SERVICE_ACCESS_VK_KEY');
|
||||
$this->publicId = env('PUBLIC_ID');
|
||||
$this->publicDomain = env('PUBLIC_DOMAIN');
|
||||
}
|
||||
|
||||
public function getServerForUploadImages()
|
||||
public function getServerForUploadImages($album_id, $group_id)
|
||||
{
|
||||
return $this->vk->photos()->getUploadServer($this->wallToken, array(
|
||||
''
|
||||
));
|
||||
return $this->vk->photos()->getUploadServer(
|
||||
$this->vkAuthService->getToken()->access_token,
|
||||
array(
|
||||
'album_id' => $album_id,
|
||||
'group_id' => $group_id,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function uploadImagesToUploadServer($uploadUrl, $images)
|
||||
{
|
||||
// Массив для хранения локальных путей к загруженным изображениям
|
||||
$localFiles = [];
|
||||
|
||||
// Загрузка изображений из URL
|
||||
foreach ($images as $imageUrl) {
|
||||
$localFile = tempnam(sys_get_temp_dir(), 'img_') . '.webp';
|
||||
file_put_contents($localFile, file_get_contents($imageUrl));
|
||||
$localFiles[] = $localFile;
|
||||
}
|
||||
|
||||
// Подготовка данных для отправки
|
||||
$postFields = [];
|
||||
foreach ($localFiles as $index => $localFile) {
|
||||
// Добавляем файл в массив
|
||||
$postFields['file' . ($index + 1)] = new CURLFile($localFile);
|
||||
}
|
||||
|
||||
// Инициализация cURL
|
||||
$ch = curl_init();
|
||||
|
||||
// Установка параметров cURL
|
||||
curl_setopt($ch, CURLOPT_URL, $uploadUrl);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $postFields);
|
||||
|
||||
// Выполнение запроса
|
||||
$response = curl_exec($ch);
|
||||
|
||||
curl_close($ch);
|
||||
|
||||
// Проверка на ошибки
|
||||
if (curl_errno($ch)) {
|
||||
return 'Ошибка cURL: ' . curl_error($ch);
|
||||
}
|
||||
|
||||
// Закрытие cURL
|
||||
|
||||
// Обработка ответа
|
||||
$responseData = json_decode($response, true);
|
||||
|
||||
|
||||
|
||||
// Удаление временных файлов
|
||||
foreach ($localFiles as $localFile) {
|
||||
unlink($localFile);
|
||||
}
|
||||
|
||||
return $responseData;
|
||||
}
|
||||
|
||||
public function saveImagesToUploadServer($albumId, $server, $photosList, $hash)
|
||||
{
|
||||
// URL для запроса
|
||||
$url = 'https://api.vk.com/method/photos.save';
|
||||
|
||||
// Подготовка данных для отправки
|
||||
$postFields = [
|
||||
'album_id' => $albumId,
|
||||
'server' => $server,
|
||||
'group_id' => env('PUBLIC_ID'),
|
||||
'photos_list' => $photosList, // Преобразуем массив в JSON-строку
|
||||
'hash' => $hash,
|
||||
'access_token' => $this->vkAuthService->getToken()->access_token,
|
||||
'v' => '5.131', // Версия API
|
||||
];
|
||||
|
||||
// Инициализация cURL
|
||||
$ch = curl_init();
|
||||
|
||||
// Установка параметров cURL
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $postFields);
|
||||
|
||||
// Выполнение запроса
|
||||
$response = curl_exec($ch);
|
||||
|
||||
// Проверка на ошибки
|
||||
if (curl_errno($ch)) {
|
||||
return 'Ошибка cURL: ' . curl_error($ch);
|
||||
}
|
||||
|
||||
// Закрытие cURL
|
||||
curl_close($ch);
|
||||
|
||||
// Обработка ответа
|
||||
$responseData = json_decode($response, true);
|
||||
|
||||
return $responseData;
|
||||
}
|
||||
public function createAlbum(string $title)
|
||||
{
|
||||
try {
|
||||
return $this->vk->photos()->createAlbum($this->wallToken, array(
|
||||
return $this->vk->photos()->createAlbum($this->vkAuthService->getToken()->access_token, array(
|
||||
'title' => $title,
|
||||
'group_id' => $this->publicId,
|
||||
'privacy' => 0,
|
||||
@@ -46,4 +150,20 @@ class VkAlbumService
|
||||
}
|
||||
}
|
||||
|
||||
public function deleteAlbum(int $album_id, int $group_id)
|
||||
{
|
||||
try {
|
||||
return $this->vk->photos()->deleteAlbum($this->vkAuthService->getToken()->access_token, array(
|
||||
'album_id' => $album_id,
|
||||
'group_id' => $group_id,
|
||||
));
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Ошибка при удалении альбома: ' . $e->getMessage());
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => 'Не удалось удалить альбом: ' . $e->getMessage(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\VK;
|
||||
|
||||
use App\Services\VK\Album\VkAlbumService;
|
||||
use App\Services\VK\Wall\VkWallService;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use VK\Client\VKApiClient;
|
||||
|
||||
class VkAuthService
|
||||
{
|
||||
|
||||
public function getToken()
|
||||
{
|
||||
try {
|
||||
// Получаем последний токен
|
||||
$token = DB::table('vk_tokens')->latest()->first();
|
||||
|
||||
// Проверяем, существует ли токен и является ли он валидным
|
||||
if ($token && !$this->isTokenValid($token)) {
|
||||
// Если токен не валиден, обновляем его
|
||||
return $this->refresh();
|
||||
}
|
||||
return $token;
|
||||
} catch (\Exception $e) {
|
||||
// Обработка ошибок, например, логирование
|
||||
Log::error('Ошибка при получении токена: ' . $e->getMessage());
|
||||
return null; // Или выбросьте исключение, если это необходимо
|
||||
}
|
||||
}
|
||||
|
||||
public function redirectToProvider()
|
||||
{
|
||||
$state = bin2hex(random_bytes(16)); // Генерация случайной строки состояния
|
||||
session(['vk_state' => $state]);
|
||||
|
||||
$code_verifier = $this->generateCodeVerifier();
|
||||
$code_challenge = $this->generateCodeChallenge($code_verifier);
|
||||
|
||||
session(['vk_code_verifier' => $code_verifier]);
|
||||
|
||||
|
||||
$url = 'https://id.vk.com/authorize?' . http_build_query([
|
||||
'response_type' => 'code',
|
||||
'client_id' => env('VK_APP_ID'),
|
||||
'redirect_uri' => env('VK_REDIRECT_URI'),
|
||||
'state' => $state,
|
||||
'scope' => 'photos wall', // Укажите необходимые права доступа
|
||||
'code_challenge' => $code_challenge, // Добавьте код, если используете PKCE
|
||||
'code_challenge_method' => 's256',
|
||||
]);
|
||||
|
||||
return redirect($url);
|
||||
}
|
||||
|
||||
public function handleProviderCallback(Request $request)
|
||||
{
|
||||
$this->validateState($request);
|
||||
$codeVerifier = session('vk_code_verifier');
|
||||
|
||||
$tokenData = $this->exchangeCodeForTokens($request, $codeVerifier);
|
||||
|
||||
|
||||
if (isset($tokenData->error)) {
|
||||
return response()->json(['error' => $tokenData->error_description], 400);
|
||||
}
|
||||
|
||||
return $this->storeTokenData($tokenData, $request);
|
||||
}
|
||||
|
||||
public function refresh()
|
||||
{
|
||||
$token = DB::table('vk_tokens')->latest()->first();
|
||||
$newTokenData = $this->refreshToken($token->refresh_token, bin2hex(random_bytes(16)), $token->device_id);
|
||||
return $this->storeRefreshTokenData($newTokenData['data']);
|
||||
}
|
||||
|
||||
public function logout()
|
||||
{
|
||||
session()->forget('vk_state');
|
||||
session()->forget('vk_code_verifier');
|
||||
|
||||
return redirect('/'); // Перенаправление на главную страницу
|
||||
}
|
||||
|
||||
private function exchangeCodeForTokens(Request $request, $codeVerifier)
|
||||
{
|
||||
return Http::asForm()->post('https://id.vk.com/oauth2/auth', [
|
||||
'grant_type' => 'authorization_code',
|
||||
'code_verifier' => $codeVerifier,
|
||||
'redirect_uri' => env('VK_REDIRECT_URI_AFTER_AUTH'),
|
||||
'code' => $request->code,
|
||||
'client_id' => env('VK_APP_ID'),
|
||||
'device_id' => $request->device_id,
|
||||
'state' => $request->state,
|
||||
'scope' => 'photos wall',
|
||||
])->object();
|
||||
}
|
||||
|
||||
private function refreshToken($refresh_token, $state, $device_id)
|
||||
{
|
||||
$response = Http::asForm()->post('https://id.vk.com/oauth2/auth', [
|
||||
'grant_type' => 'refresh_token',
|
||||
'refresh_token' => $refresh_token,
|
||||
'client_id' => env('VK_APP_ID'),
|
||||
'device_id' => $device_id,
|
||||
'state' => $state,
|
||||
'scope' => 'photos wall',
|
||||
]);
|
||||
|
||||
if ($response->failed()) {
|
||||
// Обработка ошибок
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => $response->json(), // Возвращаем детали ошибки
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'data' => $response->object(), // Успешный ответ
|
||||
];
|
||||
}
|
||||
|
||||
private function storeTokenData($tokenData, Request $request)
|
||||
{
|
||||
try {
|
||||
DB::table('vk_tokens')->updateOrInsert(
|
||||
['user_id' => $tokenData->user_id],
|
||||
[
|
||||
'user_id' => $tokenData->user_id,
|
||||
'access_token' => $tokenData->access_token,
|
||||
'refresh_token' => $tokenData->refresh_token,
|
||||
'id_token' => $tokenData->id_token,
|
||||
'state' => $tokenData->state,
|
||||
'scope' => $tokenData->scope,
|
||||
'device_id' => $request->device_id,
|
||||
'token_expire' => Carbon::createFromTimestamp(Carbon::now()->timestamp + $tokenData->expires_in),
|
||||
'updated_at' => now(),
|
||||
]
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Ошибка при обновлении или вставке токена: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Произошла ошибка при обновлении или создания токена.',
|
||||
'error_message' => $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
|
||||
return redirect('/'); // Перенаправление после успешной авторизации
|
||||
}
|
||||
|
||||
private function storeRefreshTokenData($tokenData)
|
||||
{
|
||||
try {
|
||||
// Обновляем запись и получаем количество затронутых строк
|
||||
$updatedRows = DB::table('vk_tokens')->where('user_id', $tokenData->user_id)->update(
|
||||
[
|
||||
'access_token' => $tokenData->access_token,
|
||||
'refresh_token' => $tokenData->refresh_token,
|
||||
'state' => $tokenData->state,
|
||||
'scope' => $tokenData->scope,
|
||||
'token_expire' => Carbon::createFromTimestamp(Carbon::now()->timestamp + $tokenData->expires_in),
|
||||
'updated_at' => now(),
|
||||
]
|
||||
);
|
||||
|
||||
// Если обновление прошло успешно, получаем обновленную запись
|
||||
if ($updatedRows > 0) {
|
||||
return DB::table('vk_tokens')->where('user_id', $tokenData->user_id)->first();
|
||||
}
|
||||
|
||||
// Если запись не найдена, можно вернуть null или выбросить исключение
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Запись не найдена для обновления.',
|
||||
], 404);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Ошибка при обновлении токена: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Произошла ошибка при обновлении токена.',
|
||||
'error_message' => $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
private function generateCodeVerifier($length = 128)
|
||||
{
|
||||
return bin2hex(random_bytes($length / 2));
|
||||
}
|
||||
|
||||
private function generateCodeChallenge($code_verifier)
|
||||
{
|
||||
return rtrim(strtr(base64_encode(hash('sha256', $code_verifier, true)), '+/', '-_'), '=');
|
||||
}
|
||||
|
||||
private function validateState(Request $request)
|
||||
{
|
||||
if ($request->input('state') !== session('vk_state')) {
|
||||
abort(403, 'Invalid state');
|
||||
}
|
||||
}
|
||||
|
||||
private function isTokenValid($token)
|
||||
{
|
||||
if (Carbon::now() > $token->token_expire) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -10,9 +10,12 @@ class VkService
|
||||
{
|
||||
protected VkWallService $wallService;
|
||||
protected VkAlbumService $albumService;
|
||||
public function __construct(VKApiClient $vk) {
|
||||
protected int $public_id;
|
||||
public function __construct() {
|
||||
$vk = new VKApiClient();
|
||||
$this->wallService = new VkWallService($vk);
|
||||
$this->albumService = new VkAlbumService($vk);
|
||||
$this->public_id = env('PUBLIC_ID');
|
||||
}
|
||||
|
||||
public function getPosts(int $count = 10)
|
||||
@@ -20,14 +23,70 @@ class VkService
|
||||
return $this->wallService->getPosts($count);
|
||||
}
|
||||
|
||||
public function createPost(string $message, int $from_group = 1)
|
||||
public function getPostById(int $id)
|
||||
{
|
||||
return $this->wallService->createPost($message, $from_group);
|
||||
return $this->wallService->getPostById($id);
|
||||
}
|
||||
|
||||
public function createAlbum(string $title)
|
||||
public function createPost(string $title, string $message, array $images = [], int $publish_date = null)
|
||||
{
|
||||
return $this->albumService->createAlbum($title);
|
||||
$from_group = 1;
|
||||
$album_attachment = '';
|
||||
if ($images !== []) {
|
||||
$album = $this->createAlbum($title, $images);
|
||||
$album_attachment = $this->createAlbumAttachmentParam($album['id']);
|
||||
}
|
||||
|
||||
return $this->wallService->createPost($message, $from_group, $album_attachment, $publish_date);
|
||||
}
|
||||
|
||||
public function updatePost(int $id, string $title, string $message, array $images = [], int $publish_date = null)
|
||||
{
|
||||
$from_group = 1;
|
||||
$vk_post = $this->wallService->getPostById($id);
|
||||
$album_attachment = '';
|
||||
if ($vk_post['attachments']) {
|
||||
if ($this->getAlbumAttachment($vk_post['attachments'])) {
|
||||
$album = $this->getAlbumAttachment($vk_post['attachments']);
|
||||
$this->albumService->deleteAlbum($album['album']['id'], $this->public_id);
|
||||
}
|
||||
}
|
||||
if ($images !== []) {
|
||||
$album = $this->createAlbum($title, $images);
|
||||
$album_attachment = $this->createAlbumAttachmentParam($album['id']);
|
||||
}
|
||||
|
||||
return $this->wallService->updatePost($id, $message, $from_group, $album_attachment, $publish_date);
|
||||
}
|
||||
|
||||
public function createAlbum(string $title, $images)
|
||||
{
|
||||
$album = $this->albumService->createAlbum($title);
|
||||
$uploadServer = $this->albumService->getServerForUploadImages($album['id'], env('PUBLIC_ID'));
|
||||
foreach (array_chunk($images, 4) as $images_slice) {
|
||||
$images_data = $this->albumService->uploadImagesToUploadServer($uploadServer['upload_url'], $images_slice);
|
||||
$this->albumService->saveImagesToUploadServer(
|
||||
$images_data['aid'],
|
||||
$images_data['server'],
|
||||
$images_data['photos_list'],
|
||||
$images_data['hash']
|
||||
);
|
||||
}
|
||||
return $album;
|
||||
}
|
||||
|
||||
private function createAlbumAttachmentParam(int $attachmentId)
|
||||
{
|
||||
return $this->wallService->generateAttachmentsParams('album', $attachmentId);
|
||||
}
|
||||
|
||||
private function getAlbumAttachment(array $attachments)
|
||||
{
|
||||
$data = collect($attachments);
|
||||
$filteredAttachment = $data->filter(function($attachment) {
|
||||
return $attachment['type'] === 'album';
|
||||
});
|
||||
return (isset($filteredAttachment[0]) ? $filteredAttachment[0] : null);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,6 +2,10 @@
|
||||
|
||||
namespace App\Services\VK\Wall;
|
||||
|
||||
use App\Services\VK\VkAuthService;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use VK\Client\VKApiClient;
|
||||
|
||||
@@ -19,6 +23,8 @@ class VkWallService
|
||||
$this->serviceToken = env('SERVICE_ACCESS_VK_KEY');
|
||||
$this->publicId = env('PUBLIC_ID');
|
||||
$this->publicDomain = env('PUBLIC_DOMAIN');
|
||||
$this->vkAuthService = new VkAuthService();
|
||||
|
||||
}
|
||||
|
||||
public function getPosts(int $count)
|
||||
@@ -30,15 +36,40 @@ class VkWallService
|
||||
));
|
||||
}
|
||||
|
||||
public function createPost(string $message, int $from_group, string $attachments = '')
|
||||
public function getPostById(int $id)
|
||||
{
|
||||
$post = $this->vk->wall()->getById($this->serviceToken, array(
|
||||
'posts' => '-'. $this->publicId . '_' . $id,
|
||||
));
|
||||
return $post[0];
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function createPost(string $message, int $from_group, string $attachments = '', int $publish_date)
|
||||
{
|
||||
$params = [
|
||||
'owner_id' => '-' . $this->publicId,
|
||||
'from_group' => $from_group,
|
||||
'message' => $message,
|
||||
'attachments' => $attachments,
|
||||
'access_token' => $this->wallToken,
|
||||
'publish_date' => $publish_date,
|
||||
'v' => '5.131',
|
||||
];
|
||||
|
||||
try {
|
||||
return $this->vk->wall()->post($this->wallToken, array(
|
||||
'owner_id' => '-' . $this->publicId,
|
||||
'from_group' => $from_group,
|
||||
'message' => $message,
|
||||
'attachments' => $attachments
|
||||
));
|
||||
$response = Http::asForm()->post('https://api.vk.com/method/wall.post', $params);
|
||||
|
||||
if ($response->successful() && isset($response['response'])) {
|
||||
return [
|
||||
'success' => true,
|
||||
'post_id' => $response['response']['post_id'],
|
||||
];
|
||||
} else {
|
||||
throw new \Exception('Ошибка API: ' . json_encode($response->json()));
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Ошибка при создании поста: ' . $e->getMessage());
|
||||
return [
|
||||
@@ -47,4 +78,58 @@ class VkWallService
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
public function updatePost(int $post_id, string $message = '', int $from_group = 1, string $attachments = '', int $publish_date)
|
||||
{
|
||||
if (empty($message) && empty($attachments)) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => 'Необходимо указать либо сообщение, либо вложения.',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
$params = [
|
||||
'owner_id' => '-' . $this->publicId,
|
||||
'post_id' => $post_id,
|
||||
'message' => $message,
|
||||
'attachments' => $attachments,
|
||||
'access_token' => $this->wallToken,
|
||||
'v' => '5.131',
|
||||
];
|
||||
|
||||
|
||||
|
||||
|
||||
try {
|
||||
return $this->vk->wall()->edit(
|
||||
$this->vkAuthService->getToken()->access_token,
|
||||
array(
|
||||
'owner_id' => '-' . $this->publicId,
|
||||
'post_id' => $post_id,
|
||||
'message' => $message,
|
||||
'attachments' => $attachments,
|
||||
'publish_date' => $publish_date,
|
||||
),
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Ошибка при обновлении новости SDK: ' . $e->getMessage());
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => 'Не удалось обновить новость SDK: ' . $e->getMessage(),
|
||||
];
|
||||
}
|
||||
}
|
||||
public function generateAttachmentsParams(string $attachmentType, int $attachmentId)
|
||||
{
|
||||
$pubic_id = env('PUBLIC_ID');
|
||||
switch ($attachmentType) {
|
||||
case 'album': {
|
||||
return "album-{$pubic_id}_{$attachmentId}";
|
||||
}
|
||||
case 'doc': {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user