소스 검색

Add custom emoji

Daniel Supernault 3 년 전
부모
커밋
ca79e26d3a

+ 41 - 0
app/Models/CustomEmoji.php

@@ -0,0 +1,41 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Support\Facades\Cache;
+use Illuminate\Support\Str;
+
+class CustomEmoji extends Model
+{
+	use HasFactory;
+
+	const SCAN_RE = "/(?<=[^[:alnum:]:]|\n|^):([a-zA-Z0-9_]{2,}):(?=[^[:alnum:]:]|$)/x";
+	const CACHE_KEY = "pf:custom_emoji:";
+
+	public static function scan($text)
+	{
+		return Str::of($text)
+		->matchAll(self::SCAN_RE)
+		->map(function($match) {
+			$tag = Cache::remember(self::CACHE_KEY . $match, 14400, function() use($match) {
+				return self::whereShortcode(':' . $match . ':')->first();
+			});
+
+			if($tag) {
+				$url = url('/storage/' . $tag->media_path);
+				return [
+					'shortcode' => $match,
+					'url' => $url,
+					'static_path' => $url,
+					'visible_in_picker' => $tag->disabled == false
+				];
+			}
+		})
+		->filter(function($tag) {
+			return $tag && isset($tag['static_path']);
+		})
+		->values();
+	}
+}

+ 91 - 0
app/Services/CustomEmojiService.php

@@ -0,0 +1,91 @@
+<?php
+
+namespace App\Services;
+
+use App\Util\ActivityPub\Helpers;
+use Illuminate\Support\Facades\Http;
+use App\Models\CustomEmoji;
+
+class CustomEmojiService
+{
+	public static function getByShortcode($shortcode)
+	{
+		return CustomEmoji::whereShortcode($shortcode)->first();
+	}
+
+	public static function getByUrl($url)
+	{
+		if(Helpers::validateUrl($url) == false) {
+			return;
+		}
+
+		$emoji = CustomEmoji::whereUri($url)->first();
+		if($emoji) {
+			return $emoji;
+		}
+
+		$res = Http::acceptJson()->get($url);
+
+		if($res->successful()) {
+			$json = $res->json();
+
+			if(
+				!$json ||
+				!isset($json['id']) ||
+				!isset($json['type']) ||
+				$json['type'] !== 'Emoji' ||
+				!isset($json['icon']) ||
+				!isset($json['icon']['mediaType']) ||
+				!isset($json['icon']['url']) ||
+				!isset($json['icon']['type']) ||
+				$json['icon']['type'] !== 'Image' ||
+				!in_array($json['icon']['mediaType'], ['image/jpeg', 'image/png', 'image/jpg'])
+			) {
+				return;
+			}
+
+			if(!self::headCheck($json['icon']['url'])) {
+				return;
+			}
+			$emoji = new CustomEmoji;
+			$emoji->shortcode = $json['name'];
+			$emoji->uri = $json['id'];
+			$emoji->domain = parse_url($json['id'], PHP_URL_HOST);
+			$emoji->image_remote_url = $json['icon']['url'];
+			$emoji->save();
+
+			$ext = '.' . last(explode('/', $json['icon']['mediaType']));
+			$dest = storage_path('app/public/emoji/') . $emoji->id . $ext;
+			copy($emoji->image_remote_url, $dest);
+			$emoji->media_path = 'emoji/' . $emoji->id . $ext;
+			$emoji->save();
+
+			return $emoji;
+		} else {
+			return;
+		}
+	}
+
+	public static function headCheck($url)
+	{
+		$res = Http::head($url);
+
+		if(!$res->successful()) {
+			return false;
+		}
+
+		$type = $res->header('content-type');
+		$length = $res->header('content-length');
+
+		if(
+			!$type ||
+			!$length ||
+			!in_array($type, ['image/jpeg', 'image/png', 'image/jpg']) ||
+			$length > config('federation.custom_emoji.max_size')
+		) {
+			return false;
+		}
+
+		return true;
+	}
+}

+ 6 - 4
app/Transformer/Api/StatusStatelessTransformer.php

@@ -14,6 +14,7 @@ use App\Services\StatusLabelService;
 use App\Services\StatusMentionService;
 use App\Services\ProfileService;
 use App\Services\PollService;
+use App\Models\CustomEmoji;
 
 class StatusStatelessTransformer extends Fractal\TransformerAbstract
 {
@@ -25,17 +26,18 @@ class StatusStatelessTransformer extends Fractal\TransformerAbstract
 		return [
 			'_v'                        => 1,
 			'id'                        => (string) $status->id,
+			//'gid'						=> $status->group_id ? (string) $status->group_id : null,
 			'shortcode'                 => HashidService::encode($status->id),
 			'uri'                       => $status->url(),
 			'url'                       => $status->url(),
-			'in_reply_to_id'            => (string) $status->in_reply_to_id,
-			'in_reply_to_account_id'    => (string) $status->in_reply_to_profile_id,
+			'in_reply_to_id'            => $status->in_reply_to_id ? (string) $status->in_reply_to_id : null,
+			'in_reply_to_account_id'    => $status->in_reply_to_profile_id ? (string) $status->in_reply_to_profile_id : null,
 			'reblog'                    => null,
 			'content'                   => $status->rendered ?? $status->caption,
 			'content_text'              => $status->caption,
 			'created_at'                => $status->created_at->format('c'),
-			'emojis'                    => [],
-			'reblogs_count'             => 0,
+			'emojis'                    => CustomEmoji::scan($status->caption),
+			'reblogs_count'             => $status->reblogs_count ?? 0,
 			'favourites_count'          => $status->likes_count ?? 0,
 			'reblogged'                 => null,
 			'favourited'                => null,

+ 2 - 0
app/Util/Lexer/RestrictedNames.php

@@ -157,6 +157,8 @@ class RestrictedNames
 		'embed',
 		'email',
 		'emails',
+		'emoji',
+		'emojis',
 		'error',
 		'explore',
 		'export',

+ 8 - 1
config/federation.php

@@ -44,6 +44,13 @@ return [
 		'enabled' => env('WEBFINGER', true)
 	],
 
-	'network_timeline' => env('PF_NETWORK_TIMELINE', true)
+	'network_timeline' => env('PF_NETWORK_TIMELINE', true),
+
+	'custom_emoji' => [
+		'enabled' => env('CUSTOM_EMOJI', false),
+
+		// max size in bytes, default is 2mb
+		'max_size' => env('CUSTOM_EMOJI_MAX_SIZE', 2000000),
+	]
 
 ];

+ 47 - 0
database/migrations/2022_01_19_025041_create_custom_emoji_table.php

@@ -0,0 +1,47 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+class CreateCustomEmojiTable extends Migration
+{
+	/**
+	* Run the migrations.
+	*
+	* @return void
+	*/
+	public function up()
+	{
+		Schema::create('custom_emoji', function (Blueprint $table) {
+			$table->id();
+			$table->string('shortcode')->unique()->index();
+			$table->string('media_path')->nullable();
+			$table->string('domain')->nullable()->index();
+			$table->boolean('disabled')->default(false)->index();
+			$table->string('uri')->nullable();
+			$table->string('image_remote_url')->nullable();
+			$table->unsignedInteger('category_id')->nullable();
+			$table->unique(['shortcode', 'domain']);
+			$table->timestamps();
+		});
+
+		Schema::create('custom_emoji_categories', function (Blueprint $table) {
+			$table->id();
+			$table->string('name')->unique()->index();
+			$table->boolean('disabled')->default(false)->index();
+			$table->timestamps();
+		});
+	}
+
+	/**
+	* Reverse the migrations.
+	*
+	* @return void
+	*/
+	public function down()
+	{
+		Schema::dropIfExists('custom_emoji');
+		Schema::dropIfExists('custom_emoji_categories');
+	}
+}