ImportPostController.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. <?php
  2. namespace App\Http\Controllers;
  3. use Illuminate\Http\Request;
  4. use App\Models\ImportPost;
  5. use App\Services\ImportService;
  6. use App\Services\StatusService;
  7. use App\Http\Resources\ImportStatus;
  8. use App\Follower;
  9. use App\User;
  10. class ImportPostController extends Controller
  11. {
  12. public function __construct()
  13. {
  14. $this->middleware('auth');
  15. }
  16. public function getConfig(Request $request)
  17. {
  18. return [
  19. 'enabled' => config('import.instagram.enabled'),
  20. 'limits' => [
  21. 'max_posts' => config('import.instagram.limits.max_posts'),
  22. 'max_attempts' => config('import.instagram.limits.max_attempts'),
  23. ],
  24. 'allow_video_posts' => config('import.instagram.allow_video_posts'),
  25. 'allow_image_webp' => config('import.instagram.allow_image_webp') && str_contains(config_cache('pixelfed.media_types'), 'image/webp'),
  26. 'permissions' => [
  27. 'admins_only' => config('import.instagram.permissions.admins_only'),
  28. 'admin_follows_only' => config('import.instagram.permissions.admin_follows_only'),
  29. 'min_account_age' => config('import.instagram.permissions.min_account_age'),
  30. 'min_follower_count' => config('import.instagram.permissions.min_follower_count'),
  31. ],
  32. 'allowed' => $this->checkPermissions($request, false)
  33. ];
  34. }
  35. public function getProcessingCount(Request $request)
  36. {
  37. abort_unless(config('import.instagram.enabled'), 404);
  38. $processing = ImportPost::whereProfileId($request->user()->profile_id)
  39. ->whereNull('status_id')
  40. ->whereSkipMissingMedia(false)
  41. ->count();
  42. $finished = ImportPost::whereProfileId($request->user()->profile_id)
  43. ->whereNotNull('status_id')
  44. ->whereSkipMissingMedia(false)
  45. ->count();
  46. return response()->json([
  47. 'processing_count' => $processing,
  48. 'finished_count' => $finished,
  49. ]);
  50. }
  51. public function getImportedFiles(Request $request)
  52. {
  53. abort_unless(config('import.instagram.enabled'), 404);
  54. return response()->json(
  55. ImportService::getImportedFiles($request->user()->profile_id),
  56. 200,
  57. [],
  58. JSON_UNESCAPED_SLASHES
  59. );
  60. }
  61. public function getImportedPosts(Request $request)
  62. {
  63. abort_unless(config('import.instagram.enabled'), 404);
  64. return ImportStatus::collection(
  65. ImportPost::whereProfileId($request->user()->profile_id)
  66. ->has('status')
  67. ->cursorPaginate(9)
  68. );
  69. }
  70. public function formatHashtags($val = false)
  71. {
  72. if(!$val || !strlen($val)) {
  73. return null;
  74. }
  75. $groupedHashtagRegex = '/#\w+(?=#)/';
  76. return preg_replace($groupedHashtagRegex, '$0 ', $val);
  77. }
  78. public function store(Request $request)
  79. {
  80. abort_unless(config('import.instagram.enabled'), 404);
  81. $this->checkPermissions($request);
  82. $uid = $request->user()->id;
  83. $pid = $request->user()->profile_id;
  84. foreach($request->input('files') as $file) {
  85. $media = $file['media'];
  86. $c = collect($media);
  87. $postHash = hash('sha256', $c->toJson());
  88. $exts = $c->map(function($m) {
  89. $fn = last(explode('/', $m['uri']));
  90. return last(explode('.', $fn));
  91. });
  92. $postType = 'photo';
  93. if($exts->count() > 1) {
  94. if($exts->contains('mp4')) {
  95. if($exts->contains('jpg', 'png')) {
  96. $postType = 'photo:video:album';
  97. } else {
  98. $postType = 'video:album';
  99. }
  100. } else {
  101. $postType = 'photo:album';
  102. }
  103. } else {
  104. if(in_array($exts[0], ['jpg', 'png'])) {
  105. $postType = 'photo';
  106. } else if(in_array($exts[0], ['mp4'])) {
  107. $postType = 'video';
  108. }
  109. }
  110. $ip = new ImportPost;
  111. $ip->user_id = $uid;
  112. $ip->profile_id = $pid;
  113. $ip->post_hash = $postHash;
  114. $ip->service = 'instagram';
  115. $ip->post_type = $postType;
  116. $ip->media_count = $c->count();
  117. $ip->media = $c->map(function($m) {
  118. return [
  119. 'uri' => $m['uri'],
  120. 'title' => $this->formatHashtags($m['title']),
  121. 'creation_timestamp' => $m['creation_timestamp']
  122. ];
  123. })->toArray();
  124. $ip->caption = $c->count() > 1 ? $this->formatHashtags($file['title']) : $this->formatHashtags($ip->media[0]['title']);
  125. $ip->filename = last(explode('/', $ip->media[0]['uri']));
  126. $ip->metadata = $c->map(function($m) {
  127. return [
  128. 'uri' => $m['uri'],
  129. 'media_metadata' => isset($m['media_metadata']) ? $m['media_metadata'] : null
  130. ];
  131. })->toArray();
  132. $ip->creation_date = $c->count() > 1 ? now()->parse($file['creation_timestamp']) : now()->parse($media[0]['creation_timestamp']);
  133. $ip->creation_year = now()->parse($ip->creation_date)->format('y');
  134. $ip->creation_month = now()->parse($ip->creation_date)->format('m');
  135. $ip->creation_day = now()->parse($ip->creation_date)->format('d');
  136. $ip->save();
  137. ImportService::getImportedFiles($pid, true);
  138. ImportService::getPostCount($pid, true);
  139. }
  140. return [
  141. 'msg' => 'Success'
  142. ];
  143. }
  144. public function storeMedia(Request $request)
  145. {
  146. abort_unless(config('import.instagram.enabled'), 404);
  147. $this->checkPermissions($request);
  148. $mimes = config('import.instagram.allow_video_posts') ? 'mimetypes:image/png,image/jpeg,video/mp4' : 'mimetypes:image/png,image/jpeg';
  149. $this->validate($request, [
  150. 'file' => 'required|array|max:10',
  151. 'file.*' => [
  152. 'required',
  153. 'file',
  154. $mimes,
  155. 'max:' . config_cache('pixelfed.max_photo_size')
  156. ]
  157. ]);
  158. foreach($request->file('file') as $file) {
  159. $fileName = $file->getClientOriginalName();
  160. $file->storeAs('imports/' . $request->user()->id . '/', $fileName);
  161. }
  162. ImportService::getImportedFiles($request->user()->profile_id, true);
  163. return [
  164. 'msg' => 'Success'
  165. ];
  166. }
  167. protected function checkPermissions($request, $abortOnFail = true)
  168. {
  169. $user = $request->user();
  170. if($abortOnFail) {
  171. abort_unless(config('import.instagram.enabled'), 404);
  172. }
  173. if($user->is_admin) {
  174. if(!$abortOnFail) {
  175. return true;
  176. } else {
  177. return;
  178. }
  179. }
  180. $admin = User::whereIsAdmin(true)->first();
  181. if(config('import.instagram.permissions.admins_only')) {
  182. if($abortOnFail) {
  183. abort_unless($user->is_admin, 404, 'Only admins can use this feature.');
  184. } else {
  185. if(!$user->is_admin) {
  186. return false;
  187. }
  188. }
  189. }
  190. if(config('import.instagram.permissions.admin_follows_only')) {
  191. $exists = Follower::whereProfileId($admin->profile_id)
  192. ->whereFollowingId($user->profile_id)
  193. ->exists();
  194. if($abortOnFail) {
  195. abort_unless(
  196. $exists,
  197. 404,
  198. 'Only admins, and accounts they follow can use this feature'
  199. );
  200. } else {
  201. if(!$exists) {
  202. return false;
  203. }
  204. }
  205. }
  206. if(config('import.instagram.permissions.min_account_age')) {
  207. $res = $user->created_at->lt(
  208. now()->subDays(config('import.instagram.permissions.min_account_age'))
  209. );
  210. if($abortOnFail) {
  211. abort_unless(
  212. $res,
  213. 404,
  214. 'Your account is too new to use this feature'
  215. );
  216. } else {
  217. if(!$res) {
  218. return false;
  219. }
  220. }
  221. }
  222. if(config('import.instagram.permissions.min_follower_count')) {
  223. $res = Follower::whereFollowingId($user->profile_id)->count() >= config('import.instagram.permissions.min_follower_count');
  224. if($abortOnFail) {
  225. abort_unless(
  226. $res,
  227. 404,
  228. 'You don\'t have enough followers to use this feature'
  229. );
  230. } else {
  231. if(!$res) {
  232. return false;
  233. }
  234. }
  235. }
  236. if(intval(config('import.instagram.limits.max_posts')) > 0) {
  237. $res = ImportService::getPostCount($user->profile_id) >= intval(config('import.instagram.limits.max_posts'));
  238. if($abortOnFail) {
  239. abort_if(
  240. $res,
  241. 404,
  242. 'You have reached the limit of post imports and cannot import any more posts'
  243. );
  244. } else {
  245. if($res) {
  246. return false;
  247. }
  248. }
  249. }
  250. if(intval(config('import.instagram.limits.max_attempts')) > 0) {
  251. $res = ImportService::getAttempts($user->profile_id) >= intval(config('import.instagram.limits.max_attempts'));
  252. if($abortOnFail) {
  253. abort_if(
  254. $res,
  255. 404,
  256. 'You have reached the limit of post import attempts and cannot import any more posts'
  257. );
  258. } else {
  259. if($res) {
  260. return false;
  261. }
  262. }
  263. }
  264. if(!$abortOnFail) {
  265. return true;
  266. }
  267. }
  268. }