FacebookService.php 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. <?php
  2. namespace App\Services\Smm;
  3. use App\Services\Contracts\SmmPlatformInterface;
  4. use Carbon\Carbon;
  5. use Illuminate\Http\Request;
  6. use Illuminate\Support\Facades\Http;
  7. use Illuminate\Support\Facades\Log;
  8. use Illuminate\Support\Facades\Session;
  9. class FacebookService implements SmmPlatformInterface
  10. {
  11. protected $configData;
  12. protected $graphVersion = 'v22.0';
  13. protected $timeout = 60;//总超时时间 30 秒
  14. protected $connectTimeout = 15;//连接阶段超时 10 秒
  15. public function __construct($configData)
  16. {
  17. $this->configData = $configData;
  18. }
  19. public function login()
  20. {
  21. $permissions = [
  22. 'publish_video',
  23. 'pages_manage_cta',
  24. 'pages_show_list',
  25. 'business_management',
  26. 'pages_read_engagement',
  27. 'pages_manage_metadata',
  28. 'pages_read_user_content',
  29. 'pages_manage_ads',
  30. 'pages_manage_posts',
  31. 'pages_manage_engagement',
  32. ];
  33. $query = http_build_query([
  34. 'client_id' => env('SSM_FACEBOOK_APP_ID'),
  35. 'redirect_uri' => env('DIST_SITE_URL').'/dist/callback/facebook',
  36. 'response_type' => 'code',
  37. 'scope' => implode(',', $permissions),
  38. 'state' => csrf_token(),
  39. ]);
  40. return ['status' => true, 'data' => ['url' => "https://www.facebook.com/{$this->graphVersion}/dialog/oauth?{$query}"]];
  41. }
  42. public function loginCallback(Request $request)
  43. {
  44. // 验证 state 防止 CSRF
  45. if ($request->state !== csrf_token()) {
  46. return ['status' => false, 'data' => 'Invalid state parameter'];
  47. }
  48. try {
  49. // 获取短期访问令牌
  50. $response = Http::asForm()->post("https://graph.facebook.com/{$this->graphVersion}/oauth/access_token", [
  51. 'client_id' => env('SSM_FACEBOOK_APP_ID'),
  52. 'client_secret' => env('SSM_FACEBOOK_APP_SECRET'),
  53. 'redirect_uri' => env('DIST_SITE_URL').'/dist/callback/facebook',
  54. 'code' => $request->code,
  55. ]);
  56. if ($response->failed()) {
  57. throw new \Exception($response->json('error.message'));
  58. }
  59. $shortToken = $response->json('access_token');
  60. // 转换为长期令牌
  61. $longTokenResponse = Http::get("https://graph.facebook.com/{$this->graphVersion}/oauth/access_token", [
  62. 'grant_type' => 'fb_exchange_token',
  63. 'client_id' => env('SSM_FACEBOOK_APP_ID'),
  64. 'client_secret' => env('SSM_FACEBOOK_APP_SECRET'),
  65. 'fb_exchange_token' => $shortToken,
  66. ]);
  67. if ($longTokenResponse->failed()) {
  68. throw new \Exception($longTokenResponse->json('error.message'));
  69. }
  70. $longToken = $longTokenResponse->json('access_token');
  71. //当前时间加60天
  72. $expiresAt = Carbon::now()->addDays(60)->format('Y-m-d H:i:s');
  73. // 获取用户信息
  74. $userInfo = $this->getFacebookUser($longToken);
  75. if (!$userInfo['status']) {
  76. return $userInfo;
  77. }
  78. return [
  79. 'status' => true,
  80. 'data' => [
  81. 'accessToken' => $longToken,
  82. 'accessToken_expiresAt' => $expiresAt,
  83. 'userName' => $userInfo['data']['name'],
  84. 'userId' => $userInfo['data']['id']
  85. ]
  86. ];
  87. } catch (\Exception $e) {
  88. return ['status' => false, 'data' => $e->getMessage()];
  89. }
  90. }
  91. public function postImage($message, $imagePaths, $accessToken)
  92. {
  93. try {
  94. Log::info('开始拿page access token');
  95. $pagesInfo = $this->getAccountsInfo($accessToken);
  96. if (!$pagesInfo['status']) {
  97. return $pagesInfo;
  98. }
  99. $results = [];
  100. foreach ($pagesInfo['data'] as $page) {
  101. $mediaIds = [];
  102. foreach ($imagePaths as $imagePath) {
  103. // 上传图片
  104. Log::info('开始上传图片');
  105. $uploadResponse = Http::timeout($this->timeout)->connectTimeout($this->connectTimeout)->withToken($page['pageAccessToken'])
  106. ->attach('source', file_get_contents($imagePath), basename($imagePath))
  107. ->post("https://graph.facebook.com/{$this->graphVersion}/{$page['id']}/photos", [
  108. 'published' => false,
  109. ]);
  110. if ($uploadResponse->failed()) {
  111. return ['status' => false, 'data' => $uploadResponse->json('error.message')];
  112. }
  113. Log::info('上传图片完成');
  114. $mediaIds[] = ['media_fbid' => $uploadResponse->json('id')];
  115. }
  116. // 发布帖子
  117. Log::info('开始发布帖子');
  118. $postResponse = Http::timeout($this->timeout)->connectTimeout($this->connectTimeout)->withToken($page['pageAccessToken'])
  119. ->post("https://graph.facebook.com/{$this->graphVersion}/{$page['id']}/feed", [
  120. 'message' => $message,
  121. 'attached_media' => json_encode($mediaIds),
  122. ]);
  123. Log::info('发布帖子完成');
  124. if ($postResponse->failed()) {
  125. return ['status' => false, 'data' => $postResponse->json('error.message')];
  126. }
  127. $results[] = $postResponse->json('id');
  128. }
  129. return ['status' => true,
  130. 'data' => [
  131. 'responseIds' => $results,
  132. ]];
  133. } catch (\Exception $e) {
  134. return ['status' => false, 'data' => $e->getMessage()];
  135. }
  136. }
  137. public function postVideo($message, $videoPath, $accessToken)
  138. {
  139. try {
  140. Log::info('开始拿page access token');
  141. $pagesInfo = $this->getAccountsInfo($accessToken);
  142. if (!$pagesInfo['status']) {
  143. return $pagesInfo;
  144. }
  145. $results = [];
  146. foreach ($pagesInfo['data'] as $page) {
  147. Log::info('开始发布视频');
  148. $response = Http::timeout($this->timeout)->connectTimeout($this->connectTimeout)->withToken($page['pageAccessToken'])
  149. ->attach('source', file_get_contents($videoPath), basename($videoPath))
  150. ->post("https://graph.facebook.com/{$this->graphVersion}/{$page['id']}/videos", [
  151. 'description' => $message,
  152. ]);
  153. Log::info('发布视频完成:');
  154. if ($response->failed()) {
  155. throw new \Exception($response->json('error.message'));
  156. }
  157. $results[] = $response->json('id');
  158. }
  159. return ['status' => true,
  160. 'data' => [
  161. 'responseIds' => $results,
  162. ]];
  163. } catch (\Exception $e) {
  164. return ['status' => false, 'data' => $e->getMessage()];
  165. }
  166. }
  167. private function getFacebookUser($accessToken)
  168. {
  169. try {
  170. $response = Http::withToken($accessToken)
  171. ->get("https://graph.facebook.com/{$this->graphVersion}/me", [
  172. 'fields' => 'name,id'
  173. ]);
  174. if ($response->failed()) {
  175. throw new \Exception($response->json('error.message'));
  176. }
  177. return [
  178. 'status' => true,
  179. 'data' => $response->json()
  180. ];
  181. } catch (\Exception $e) {
  182. return ['status' => false, 'data' => $e->getMessage()];
  183. }
  184. }
  185. public function getAccountsInfo($accessToken)
  186. {
  187. try {
  188. $response = Http::timeout($this->timeout)->connectTimeout($this->connectTimeout)->withToken($accessToken)
  189. ->get("https://graph.facebook.com/{$this->graphVersion}/me/accounts");
  190. if ($response->failed()) {
  191. throw new \Exception($response->json('error.message'));
  192. }
  193. $pages = collect($response->json('data'))->map(function ($page) {
  194. return [
  195. 'id' => $page['id'],
  196. 'name' => $page['name'],
  197. 'pageAccessToken' => $page['access_token'],
  198. ];
  199. })->toArray();
  200. if (empty($pages)) {
  201. Log::info('No pages found');
  202. throw new \Exception('No pages found');
  203. }
  204. return ['status' => true, 'data' => $pages];
  205. } catch (\Exception $e) {
  206. Log::info('获取页面信息失败:getAccountsInfo:'.$e->getMessage());
  207. return ['status' => false, 'data' => $e->getMessage()];
  208. }
  209. }
  210. // 其他接口方法保持空实现
  211. public function getComments($postId) {}
  212. public function replyToComment($commentId) {}
  213. public function deleteComment($commentId) {}
  214. }