ActiveSharedInboxService.php 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. <?php
  2. namespace App\Services\Federation;
  3. use App\Profile;
  4. use App\Util\ActivityPub\Helpers;
  5. use Illuminate\Support\Facades\Cache;
  6. use Illuminate\Support\Facades\Redis;
  7. use Illuminate\Support\Facades\Storage;
  8. class ActiveSharedInboxService
  9. {
  10. const CACHE_KEY = 'pf:services:asinbox:list';
  11. const CACHE_KEY_CHECK = 'pf:services:asinbox:list-check';
  12. const CACHE_FILE_NAME = 'ap_asis.json';
  13. const CACHE_FILE_VERSION = 1;
  14. public static function get()
  15. {
  16. $res = Redis::smembers(self::CACHE_KEY);
  17. if ($res) {
  18. return $res;
  19. }
  20. if (! $res && self::count() == '0') {
  21. return self::warmCheck();
  22. }
  23. }
  24. public static function contains($member)
  25. {
  26. return Redis::sismember(self::CACHE_KEY, strtolower($member));
  27. }
  28. public static function count()
  29. {
  30. return Redis::scard(self::CACHE_KEY);
  31. }
  32. public static function add($member)
  33. {
  34. if (empty($member)) {
  35. return false;
  36. }
  37. if (! str_starts_with(strtolower($member), 'https://')) {
  38. $member = 'https://'.$member;
  39. }
  40. $validUrl = Helpers::validateUrl($member, false, true);
  41. if (! $validUrl) {
  42. return false;
  43. }
  44. return Redis::sadd(self::CACHE_KEY, strtolower($member));
  45. }
  46. public static function remove($member)
  47. {
  48. if (empty($member)) {
  49. return false;
  50. }
  51. if (! str_starts_with(strtolower($member), 'https://')) {
  52. $member = 'https://'.$member;
  53. }
  54. $validUrl = Helpers::validateUrl($member);
  55. if (! $validUrl) {
  56. return false;
  57. }
  58. return Redis::srem(self::CACHE_KEY, strtolower($member));
  59. }
  60. public static function warmCheck()
  61. {
  62. if (! Cache::has(self::CACHE_KEY_CHECK)) {
  63. return self::warmCacheFromDatabase();
  64. }
  65. return [];
  66. }
  67. public static function warmCacheFromDatabase()
  68. {
  69. $res = self::parseCacheFileData() ? self::parseCacheFileData() : self::getFromDatabase();
  70. if (Storage::has(self::CACHE_FILE_NAME)) {
  71. $res = Storage::get(self::CACHE_FILE_NAME);
  72. if (! $res) {
  73. $res = self::getFromDatabase();
  74. } else {
  75. $res = json_decode($res, true);
  76. if (isset($res['version'], $res['data'], $res['created'], $res['updated'])) {
  77. if (now()->parse($res['updated'])->lt(now()->subMonths(6))) {
  78. $res = self::getFromDatabase();
  79. } else {
  80. if ($res['version'] === self::CACHE_FILE_VERSION) {
  81. $res = $res['data'];
  82. } else {
  83. $res = self::getFromDatabase();
  84. }
  85. }
  86. } else {
  87. $res = self::getFromDatabase();
  88. }
  89. }
  90. } else {
  91. $res = self::getFromDatabase();
  92. }
  93. if (! $res) {
  94. return [];
  95. }
  96. $filteredList = [];
  97. foreach ($res as $value) {
  98. if (! $value || ! str_starts_with($value, 'https://')) {
  99. continue;
  100. }
  101. $passed = self::add($value);
  102. if ($passed) {
  103. $filteredList[] = $value;
  104. }
  105. }
  106. self::saveCacheToDisk($filteredList);
  107. Cache::remember(self::CACHE_KEY_CHECK, 86400, function () {
  108. return true;
  109. });
  110. return $res;
  111. }
  112. public static function parseCacheFileData()
  113. {
  114. if (Storage::has(self::CACHE_FILE_NAME)) {
  115. $res = Storage::get(self::CACHE_FILE_NAME);
  116. if (! $res) {
  117. return false;
  118. } else {
  119. $res = json_decode($res, true);
  120. if (! $res || isset($res['version'], $res['data'], $res['created'], $res['updated'])) {
  121. if (now()->parse($res['updated'])->lt(now()->subMonths(6))) {
  122. return false;
  123. } else {
  124. if ($res['version'] === self::CACHE_FILE_VERSION) {
  125. return $res;
  126. } else {
  127. return false;
  128. }
  129. }
  130. } else {
  131. return false;
  132. }
  133. }
  134. }
  135. return false;
  136. }
  137. public static function transformCacheFileData($res)
  138. {
  139. return [
  140. 'id' => 'pixelfed/storage/app/'.self::CACHE_FILE_NAME,
  141. 'version' => self::CACHE_FILE_VERSION,
  142. 'created' => now()->format('c'),
  143. 'updated' => now()->format('c'),
  144. 'length' => count($res),
  145. 'data' => $res,
  146. ];
  147. }
  148. public static function updateCacheFileData()
  149. {
  150. $res = self::parseCacheFileData();
  151. if (! $res) {
  152. return false;
  153. }
  154. $diff = [];
  155. $nodes = $res['data'];
  156. $latest = self::getFromDatabase();
  157. $merge = array_merge($nodes, $latest);
  158. foreach ($merge as $val) {
  159. if (! in_array($val, $nodes)) {
  160. if (self::add($val)) {
  161. $nodes[] = $val;
  162. } else {
  163. unset($nodes[$val]);
  164. }
  165. }
  166. }
  167. $data = [
  168. 'id' => 'pixelfed/storage/app/'.self::CACHE_FILE_NAME,
  169. 'version' => self::CACHE_FILE_VERSION,
  170. 'created' => $res['created'],
  171. 'updated' => now()->format('c'),
  172. 'length' => count($nodes),
  173. 'data' => $nodes,
  174. ];
  175. $contents = json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
  176. Storage::put(self::CACHE_FILE_NAME, $contents);
  177. return 1;
  178. }
  179. public static function saveCacheToDisk($res = false)
  180. {
  181. if (! $res) {
  182. return;
  183. }
  184. $contents = json_encode(self::transformCacheFileData($res), JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
  185. Storage::put(self::CACHE_FILE_NAME, $contents);
  186. }
  187. public static function getFromDatabase()
  188. {
  189. return Profile::whereNotNull('sharedInbox')->groupBy('sharedInbox')->pluck('sharedInbox')->toArray();
  190. }
  191. public static function scanForUpdates()
  192. {
  193. $res = self::getFromDatabase();
  194. $filteredList = [];
  195. foreach ($res as $value) {
  196. if (! $value || ! str_starts_with($value, 'https://')) {
  197. continue;
  198. }
  199. Redis::sadd(self::CACHE_KEY, $value);
  200. $filteredList[] = $value;
  201. }
  202. self::saveCacheToDisk($filteredList);
  203. return 1;
  204. }
  205. }