라공에디션

린더님의 메모잇 스킨 트위터 이미지 변환 개조/적용하기

<span class="sv_member">니루</span>
니루 @tabom0w0
2026-06-22 21:03

린더님이 작성하신 [라공 에디션] 메모잇 스킨 트위터 이미지 변환 (+260618 수정) 적용하기


1. 린더님이 올려주신 파일 적용하니까 트윗 내 이미지가 1개여도 무조건 4개씩 출력되는 문제 발생


문제: 닷홈은 웹호스팅 측의 보안 설정 상 allow_url_fopen 옵션이 꺼져 있어서  file_get_contents 함수를 사용할 수 없음

→ $response에 받아온 값이 없음 

→ 트윗에 이미지가 포함되어 있어도 $success_parse = false 그대로 유지됨 

→   if (!$success_parse) {} 조건문으로 빠져버림 (API 차단이나 네트워크 오류로 실패했을 때만 4장 전체 열어두는 백업 모드로 전환)


해결: 다행히 대부분의 호스팅 서버는 allow_url_fopen은 꺼두더라도 cURL 라이브러리는 열어두므로 cURL 기반 코드로 교체하면 됨

$api_url = "https://api.fxtwitter.com/status/" . $tweet_id; 와 $success_parse = false; 사이 코드를 아래 코드로 바꿔주면 된다.

// cURL 세션 초기화
$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, $api_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 3); // 기존과 동일하게 3초 타임아웃 제한

// 트위터 API 서버가 거부하지 않도록 브라우저 위장용 헤더 설정
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36');

$headers = array(
    "Accept: application/json"
);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

// 혹시 모를 서버의 SSL 인증서 미비 문제를 방지하기 위해 SSL 검증 제외 옵션 추가
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);

// 데이터 요청 및 결과 저장
$response = curl_exec($ch);

// 에러 발생 여부 체크 (필요 시 주석 해제하여 확인 가능)

// if (curl_errno($ch)) {
//     // echo 'cURL Error: ' . curl_error($ch);
//     $response = false;
// }

curl_close($ch); // cURL 세션 종료


2. 트윗 url 뒤에 ?ref_src=twsrc^tfw|twcamp^tweetembed이 붙으면 autolink 통해서 트윗 임베딩 될 때 동영상만 임베딩되는 걸 활용해서 조건문 추가한다.

// 동영상 존재 확인
// 사진과 동영상 모두 첨부되어 있을 시 첫번째 동영상을 기준으로 치환
$media_videos = $data['tweet']['media']['videos'][0]['variants'] ?? [];

if (!empty($media_videos) && is_array($media_videos)) {
    $tweet_video_link = 'https://twitter.com/' . $screen_name . '/status/' . $tweet_id . '?ref_src=twsrc^tfw|twcamp^tweetembed';
    $replacement_images = $tweet_video_link;
}

이거 추가하면 리스트에서는 내용 빈 인용문으로 보이고,

clipboard_1782136960754.png

자세히 보기 눌러서 내부로 들어가면 동영상 형태로 임베딩되어 보인다.

clipboard_1782136981026.png


[참고용] 1+2 내용을 포함하여 실제 적용한 예

(1) 트위터 api 체크하는 함수를 별도로 뺌

function twitter_api_fetch($screen_name, $tweet_id)
    {
        $replacement_images = "\n";

        $api_url = "https://api.fxtwitter.com/status/" . $tweet_id;

        // cURL 세션 초기화
        $ch = curl_init();

        curl_setopt($ch, CURLOPT_URL, $api_url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_TIMEOUT, 3); // 기존과 동일하게 3초 타임아웃 제한

        // 트위터 API 서버가 거부하지 않도록 브라우저 위장용 헤더 설정
        curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36');

        $headers = array(
            "Accept: application/json"
        );
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

        // 혹시 모를 서버의 SSL 인증서 미비 문제를 방지하기 위해 SSL 검증 제외 옵션 추가
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);

        // 데이터 요청 및 결과 저장
        $response = curl_exec($ch);

        // 에러 발생 여부 체크 (필요 시 주석 해제하여 확인 가능)

        // if (curl_errno($ch)) {
        //     // echo 'cURL Error: ' . curl_error($ch);
        //     $response = false;
        // }

        curl_close($ch); // cURL 세션 종료

        if ($response) {
            $data = json_decode($response, true);

            // 사진 존재 확인
            $media_photos = $data['tweet']['media']['photos'] ?? [];

            if (!empty($media_photos) && is_array($media_photos)) {

                // 실제 트윗에 존재하는 사진 개수만큼만 완벽하게 루프를 돕니다 (1장이면 1장, 3장이면 3장)
                foreach ($media_photos as $index => $img) {
                    $photo_num = $index + 1;

                    // 💡 [치트키 2] 프록시 가짜 주소(photo/숫자)를 버리고, API가 내려준 "트위터 진짜 원본 핫링크 주소(url)"를 다이렉트로 매핑합니다!
                    // 예: https://pbs.twimg.com/media/...jpg
                    $real_hotlink = $img['url'] ?? '';

                    // 만약 이미지 주소가 비어있지 않다면
                    if ($real_hotlink) {
                        $replacement_images .= "![" . $tweet_id . "_" . $photo_num . "](" . $real_hotlink . ")\n";
                    } else {
                        // 원본 주소가 없을 때의 백업 처리
                        $photo_num = $index + 1;
                        $replacement_images .= "![" . $tweet_id . "_" . $photo_num . "](https://d.fxtwitter.com/" . $screen_name . "/status/" . $tweet_id . "/photo/" . $photo_num . ")\n";
                    }
                }
            }

            // 동영상 존재 확인
            // 사진과 동영상 모두 첨부되어 있을 시 첫번째 동영상을 기준으로 치환
            $media_videos = $data['tweet']['media']['videos'][0]['variants'] ?? [];

            if (!empty($media_videos) && is_array($media_videos)) {
                $tweet_video_link = 'https://twitter.com/' . $screen_name . '/status/' . $tweet_id . '?ref_src=twsrc^tfw|twcamp^tweetembed';
                $replacement_images = $tweet_video_link;
            }
        }

        $replacement_images .= "\n";

        return $replacement_images;
    }


(2) memoit_markdown 함수 안에서 트윗 내 이미지 임베드 관련 내용 수정


// 1. X(트위터) 링크 및 이미지 매핑 처리
$screen_name = '';
$tweet_id = '';

if (preg_match('/(?:x\.com|twitter\.com)\/([a-zA-Z0-9_]+)\/status\/([0-9]+)/i', $text, $matches)) {
    $screen_name = $matches[1];
    $tweet_id = $matches[2];
}

if ($screen_name && $tweet_id) {
    $replacement_images = twitter_api_fetch($screen_name, $tweet_id);

    // 본문의 트위터 주소를 완벽히 파싱된 이미지 핫링크 세트로 치환
    $text = preg_replace('/https?:\/\/(?:t\.co|x\.com|twitter\.com)\/[a-zA-Z0-9_\/]+/i', $replacement_images, $text, 1);
}


3. 리스트에서 미리보기 내용에도 마크다운 적용되는 게 정신사나워서 memo-card-preview 내용은 마크다운 적용 안 되도록 함

list.skin.php 에서 memo-card-preview 부분에 memoit_markdown 함수를 적용하는 부분을 수정한다.

// 변경 전
<div class="memo-card-preview"></?php echo memoit_markdown($preview_text); ?></div>

// 변경 후
<div class="memo-card-preview"><?php echo $preview_text; ?></div>


+ 유튜브 영상 스크랩할 때

메모잇 스킨이어도 그냥 타임라인으로 클리핑 해서 유튜브 영상 바로 임베딩되게 하는 게 보기 편한듯

댓글목록

등록된 댓글이 없습니다.