미쳤다. 나만 살짝 알자~ 챗GPT 블로그 신박한 활용! 이미지 업무 완전 자동화

Поділитися
Вставка
  • Опубліковано 26 сер 2024
  • 챗GPT를 활용한 포스팅 업무 자동화, 블로그 글쓰기 자동화 완결편.
    챗GPT 달리3 이미지 생성 Actions API와 워드프레스의 완벽 연동 시리즈.
    이것 적용하면 시간 단축되며 품질 향상은 보너스입니다.
    채널에 가입하여 혜택을 누려보세요.
    / @sourceplayground

КОМЕНТАРІ • 40

  • @sourcePlayground
    @sourcePlayground  4 місяці тому +1

    다음은 영상에서 설명한 Schema 내용입니다. 참조하세요
    {
    "openapi": "3.0.0",
    "info": {
    "title": "워드프레스 자동 포스팅",
    "description": "워드프레스 OAuth를 사용해 자동으로 글을 생성합니다",
    "version": "v1.0.0"
    },
    "servers": [
    {
    "url": "자신의사이트도메인넣으세요"
    }
    ],
    "paths": {
    "/wp-json/wp/v2/posts": {
    "get": {
    "operationId": "getAllPosts",
    "description": "게시물 목록 조회",
    "parameters": [
    {
    "name": "after",
    "in": "query",
    "description": "이 시간 이후에 발행된 게시물만 조회합니다 (형식: YYYY-MM-DDTHH:MM:SS) 전체 글 조회시 비워둡니다",
    "default": "",
    "schema": {
    "type": "string"
    }
    },
    {
    "name": "before",
    "in": "query",
    "description": "이 시간 이전에 발행된 게시물만 조회합니다 (형식: YYYY-MM-DDTHH:MM:SS) 전체 글 조회시 비워둡니다",
    "default": "",
    "schema": {
    "type": "string"
    }
    },
    {
    "name": "search",
    "in": "query",
    "description": "이 값이 포함된 게시물에 대한 검색을 실행합니다. 전체 글 조회시 비워둡니다",
    "default": "",
    "schema": {
    "type": "string"
    }
    },
    {
    "name": "categories",
    "in": "query",
    "description": "카테고리 ID 값으로 이 카테고리에 속하는 게시물만 조회합니다. 전체 글 조회시 비워둡니다",
    "default": "",
    "schema": {
    "type": "string"
    }
    },
    {
    "name": "post_id",
    "in": "path",
    "description": "조회하고자 하는 게시물의 고유 ID로 /wp-json/wp/v2/posts/{post_id}로 접속하면 해당 post_id 게시물만 조회됩니다. post_id 값이 없 게시물 전체를 조회하게 됩니다. 전체 글 조회시 비워둡니다",
    "required": true,
    "default": "",
    "schema": {
    "type": "string"
    }
    }
    ]
    },
    "post": {
    "operationId": "CreatePost",
    "parameters": [
    {
    "name": "post_id",
    "in": "path",
    "required": true,
    "description": "추가 모드로 사용하고자 할 때, post_id 값을 0으로 합니다",
    "schema": {
    "type": "integer"
    }
    }
    ],
    "requestBody": {
    "required": "true",
    "content": {
    "application/json": {
    "schema": {
    "type": "object",
    "properties": {
    "categories": {
    "type": "string",
    "description": "getAllCategories를 통해 얻은 카테고리 ID (이 값은 필수가 아님. 없어도 등록됨)"
    },
    "title": {
    "type": "string",
    "description": "글 제목"
    },
    "content": {
    "type": "string",
    "description": "글 내용"
    },
    "status": {
    "type": "string",
    "description": "글 상태 ( publish (즉시 발행), pending (보류 또는 대기), future (date_gmt 시간에 예약 발행) )"
    },
    "date_gmt": {
    "type": "string",
    "description": "status가 future인 경우, 이 값의 시간에 예약 발행됨 (GMT 형식의 시간) "
    }
    }
    }
    }
    }
    }
    }
    },
    "/wp-json/wp/v2/categories": {
    "get": {
    "operationId": "getAllCategories",
    "description": "모든 카테고리 조회 (이것을 통해 /wp-json/wp/v2/post의 categories에 들어갈 카테고리 ID 목록을 얻을 수 있습니다)",
    "parameters": [
    {
    "name": "search",
    "in": "query",
    "description": "이 값은 특정 키워드로, 카테고리를 검색하고자 할 때 사용합니다",
    "default": "100",
    "schema": {
    "type": "string"
    }
    }
    ]
    }
    },
    "/wp-json/custom/append-post-content": {
    "post": {
    "operationId": "appendContents",
    "requestBody": {
    "required": "true",
    "content": {
    "application/json": {
    "schema": {
    "type": "object",
    "properties": {
    "post_id": {
    "type": "string",
    "description": "내용을 추가하고자 하는 게시물의 ID"
    },
    "content": {
    "type": "string",
    "description": "기존 내용에 추가하고자 하는 내용"
    }
    }
    }
    }
    }
    }
    }
    },
    "/wp-json/custom/upload-image-from-url": {
    "post": {
    "operationId": "uploadImageFromUrl",
    "description": "http 또는 https로 시작하는 이미지 파일의 URL 전체 경로를 제공하면 해당 파일을 업로드합니다. 업로드 성공 시 업로드된 이미지 경로를 반환합니다. 이곳에 달리로 생성한 이미지 경로를 넣어주세요.",
    "requestBody": {
    "required": "true",
    "content": {
    "application/json": {
    "schema": {
    "type": "object",
    "properties": {
    "image_url": {
    "type": "string",
    "description": "업로드하고자 하는 이미지 파일의 전체 URL 경로 (예: myhomepage.com/image.jpg)"
    },
    "resize": {
    "type": "string",
    "description": "1000 픽셀 초과하는 이미지를 1000픽셀로 축소할 지 여부 (true 또는 false 값, 필수정보)"
    }
    }
    }
    }
    }
    }
    }
    },
    "/wp-json/custom/update-post": {
    "post": {
    "operationId": "updatePostContents",
    "description": "특정 글(post_id)의 내용을 수정합니다 (특정 글(post_id) 수정을 요청하면 먼저 그 글을 조회하여 내용을 획득한 후, 해당 내용에 근거해 수정해 줍니다.)",
    "requestBody": {
    "required": "true",
    "content": {
    "application/json": {
    "schema": {
    "type": "object",
    "properties": {
    "post_id": {
    "type": "string",
    "description": "수정하고자 하는 글의 고유 post_id"
    },
    "content": {
    "type": "string",
    "description": "수정할 내용 (기존 내용이 이 내용으로 대체됩니다)"
    }
    }
    }
    }
    }
    }
    }
    }
    }
    }

    • @sourcePlayground
      @sourcePlayground  4 місяці тому

      아래 내용은 사용하시는 테마 폴더에 있는 functions.php 마지막 부분에 붙여넣으세요.
      예: 사용하시는 테마가 twentytwentytwo라면 function.php 경로는 아래와 같습니다.
      /wp-content/themes/twentytwentytwo/functions.php
      (작업 전 반드시 백업이 필요합니다)
      (2016년 12월경에 나온 워드프레스 4.7버전 이상에 적용하세요)
      function append_post_content( $request ) {
      // post_id를 POST 요청 본문으로부터 받음
      $post_id = $request['post_id'];
      $append_content = $request['content'];
      // 게시물을 조회
      $post = get_post( $post_id );
      if ( !$post ) {
      return new WP_Error( 'not_found', 'Post not found', array( 'status' => 404 ) );
      }
      // 기존 내용에 추가
      $new_content = $post->post_content . $append_content;
      // 게시물 업데이트
      wp_update_post( array(
      'ID' => $post_id,
      'post_content' => $new_content
      ) );
      return new WP_REST_Response( array( 'status' => 'success', 'post_id' => $post_id ), 200 );
      }
      // 커스텀 엔드포인트 등록 예: my.myphar.net/wp-json/custom/append-post-content
      add_action( 'rest_api_init', function () {
      register_rest_route( 'custom', '/append-post-content', array(
      'methods' => 'POST',
      'callback' => 'append_post_content',
      'args' => array(
      'post_id' => array(
      'required' => true,
      'validate_callback' => function($param, $request, $key) {
      return is_numeric( $param );
      }
      ),
      'content' => array(
      'required' => true,
      'validate_callback' => function($param, $request, $key) {
      return is_string( $param );
      }
      ),
      ),
      ) );
      } );
      add_action('rest_api_init', function () {
      register_rest_route('custom', '/upload-image-from-url', array(
      'methods' => 'POST',
      'callback' => 'custom_upload_image_from_url',
      'args' => array(
      'image_url' => array(
      'required' => true,
      'validate_callback' => function($param, $request, $key) {
      return filter_var($param, FILTER_VALIDATE_URL);
      }
      ),
      // resize 매개변수 추가
      'resize' => array(
      'required' => false,
      'default' => false,
      'validate_callback' => function($param, $request, $key) {
      return is_bool($param) || $param === 'true' || $param === 'false';
      },
      'sanitize_callback' => function($param, $request, $key) {
      return $param === 'true' || $param === true;
      }
      ),
      ),
      'permission_callback' => function () {
      return current_user_can('upload_files');
      }
      ));
      });
      function custom_upload_image_from_url($request) {
      $image_url = $request['image_url'];
      $should_resize = $request['resize'];
      // 지원하는 확장자 목록
      $allowed_extensions = array('jpg', 'jpeg', 'png', 'webp', 'gif');

      // URL에서 확장자 확인
      $is_allowed_extension = true;
      // cURL 세션 초기화
      $ch = curl_init();
      // cURL 옵션 설정
      curl_setopt($ch, CURLOPT_URL, $image_url); // 이미지 URL
      curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // 결과를 문자열로 반환
      curl_setopt($ch, CURLOPT_HEADER, false); // 헤더는 불필요
      curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); // 리다이렉션 따라가기

      // User-Agent 설정 (최신 버전으로 변경)
      curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36');
      // Referer와 Origin 헤더를 실제 타겟 사이트에 맞추기
      curl_setopt($ch, CURLOPT_REFERER, 'www.midjourney.com');
      curl_setopt($ch, CURLOPT_HTTPHEADER, array(
      'Origin: www.midjourney.com',
      'Accept-Language: en-US,en;q=0.9', // 추가적인 헤더
      'Accept-Encoding: gzip, deflate, br'
      ));
      // 쿠키와 세션 처리 (필요한 경우)
      // curl_setopt($ch, CURLOPT_COOKIE, 'name=value; name2=value2');
      // SSL/TLS 설정 (보안을 위해 가능한 활성화 상태를 유지)
      curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
      curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
      // cURL 실행
      $image_data = curl_exec($ch);
      $http_status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
      // cURL 세션 종료
      curl_close($ch);
      $err_text = "";
      if($image_data) {
      $err_text = "Image is Success Download But ";
      }
      // HTTP 상태 코드에 따른 세분화된 에러 처리
      switch ($http_status) {
      case 200:
      // 이미지 접근이 성공적인 경우, 추가 로직을 실행하거나 성공 메시지를 반환할 수 있습니다.
      break;
      case 403:
      return new WP_Error('image_access_forbidden', $err_text . 'The provided image URL points to a resource that is forbidden for access.', array('status' => 403));
      break;
      case 404:
      return new WP_Error('image_not_found', $err_text . 'The provided image URL does not point to an accessible image. Please provide a valid image URL.', array('status' => 404));
      break;
      case 500:
      case 502:
      case 503:
      case 504:
      return new WP_Error('server_error', $err_text . 'The server encountered an error while trying to access the provided image URL. Please try again later.', array('status' => $http_status));
      break;
      default:
      return new WP_Error('unknown_error', $err_text . 'An unknown error occurred while trying to access the provided image URL.', array('status' => $http_status));
      }
      // 이미지 데이터가 있는지 확인
      if (!$image_data) {
      return new WP_Error('image_not_accessible', 'No image data found. Please check the image source.', array('status' => 404));
      }
      // 이미지 데이터로부터 이미지 정보를 얻어냅니다.
      $image_info = @getimagesizefromstring($image_data);
      if ($image_info !== false) {
      // $image_data가 이미지인 경우의 처리
      $is_allowed_extension = true;
      } else {
      // 이미지가 아닌 경우의 처리
      $is_allowed_extension = false;
      }
      if (!$is_allowed_extension) {
      return new WP_Error('unsupported_image_type', 'Unsupported image type.', array('status' => 400));
      }
      $upload_dir = wp_upload_dir();
      $unique_file_name = "";
      $filename = "";
      do {
      // 고유한 파일 이름 생성
      $unique_file_name = uniqid('image-', true) . '-' . rand(1000, 9999) . '.jpg';
      $filename = $upload_dir['path'] . '/' . $unique_file_name;
      } while (file_exists($filename));
      // Imagick 객체를 사용하여 이미지 로드
      $imagick = new Imagick();
      $imagick->readImageBlob($image_data);
      // 이미지 사이즈 체크 및 조절
      $need_resize = ($imagick->getImageWidth() > 1000 || $imagick->getImageHeight() > 1000) && $should_resize;
      if ($need_resize) {
      $imagick->resizeImage(1000, 1000, Imagick::FILTER_LANCZOS, 1, true);
      }
      // 모든 이미지를 JPG로 변환
      $imagick->setImageFormat('jpg');
      $imagick->setImageCompression(Imagick::COMPRESSION_JPEG);
      $imagick->setImageCompressionQuality(75); // 압축률 설정, 필요에 따라 조절
      // 최종 파일 확장자를 jpg로 변경
      $ext = 'jpg';
      // 변경된 이미지 데이터를 파일로 저장
      if ($imagick->writeImage($filename)) {
      $imagick->clear();
      $imagick->destroy();
      } else {
      return new WP_Error('image_conversion_error', 'Failed to convert and save image.', array('status' => 500));
      }
      // 이미지를 미디어 라이브러리에 등록
      $attachment = array(
      'guid' => $upload_dir['url'] . '/' . basename($filename),
      'post_mime_type' => 'image/' . $ext,
      'post_title' => preg_replace('/\.[^.]+$/', '', basename($filename)),
      'post_content' => '',
      'post_status' => 'inherit'
      );
      $attach_id = wp_insert_attachment($attachment, $filename);
      require_once(ABSPATH . 'wp-admin/includes/image.php');
      $attach_data = wp_generate_attachment_metadata($attach_id, $filename);
      wp_update_attachment_metadata($attach_id, $attach_data);
      return new WP_REST_Response(array('url' => $upload_dir['url'] . '/' . basename($filename)), 200);
      }
      add_action( 'rest_api_init', function() {
      register_rest_route( 'custom', '/update-post', array(
      'methods' => 'POST',
      'callback' => 'update_post_contents',
      'args' => array(
      'post_id' => array(
      'required' => true,
      'validate_callback' => function($param, $request, $key) {
      return is_numeric( $param );
      }
      ),
      'content' => array(
      'required' => true,
      ),
      ),
      ) );
      } );
      function update_post_contents( $request ) {
      $post_id = $request['post_id'];
      $content = $request['content'];
      // 게시글 ID와 새 내용으로 게시글 업데이트
      $post_update = wp_update_post( array(
      'ID' => $post_id,
      'post_content' => $content,
      ), true );
      if ( is_wp_error( $post_update ) ) {
      return new WP_Error( 'fail_to_update', 'Post Update Failed', array( 'status' => 500 ) );
      }
      return new WP_REST_Response( array( 'message' => 'Post Updated Successfully', 'status' => 200 ) );
      }

    • @sourcePlayground
      @sourcePlayground  4 місяці тому

      워드프레스 접속 권한을 부여하도록 하는 응용 프로그램 비밀번호 설정 방법은 다음 영상 참조하세요
      ua-cam.com/video/77Lzaj_UhP4/v-deo.htmlsi=wJ56ho8BwHvL-BMe

    • @user-ox6fz2ts4c
      @user-ox6fz2ts4c 4 місяці тому

      항상 좋은내용 감사합니다!^^

    • @user-ox6fz2ts4c
      @user-ox6fz2ts4c 4 місяці тому

      영상 올려주신거 잘보고 있답니다^^ 펑션.php파일도 변경된것 같은데 댓글에 안올려주셔서 문의 드려요 항상 감사합니다.

    • @sourcePlayground
      @sourcePlayground  4 місяці тому

      @@user-ox6fz2ts4c 고정 댓글 열어 보시면 그 하위 댓글에 있습니다.
      다시 넣어 드릴게요.
      function append_post_content( $request ) {
      // post_id를 POST 요청 본문으로부터 받음
      $post_id = $request['post_id'];
      $append_content = $request['content'];
      // 게시물을 조회
      $post = get_post( $post_id );
      if ( !$post ) {
      return new WP_Error( 'not_found', 'Post not found', array( 'status' => 404 ) );
      }
      // 기존 내용에 추가
      $new_content = $post->post_content . $append_content;
      // 게시물 업데이트
      wp_update_post( array(
      'ID' => $post_id,
      'post_content' => $new_content
      ) );
      return new WP_REST_Response( array( 'status' => 'success', 'post_id' => $post_id ), 200 );
      }
      // 커스텀 엔드포인트 등록 예: my.myphar.net/wp-json/custom/append-post-content
      add_action( 'rest_api_init', function () {
      register_rest_route( 'custom', '/append-post-content', array(
      'methods' => 'POST',
      'callback' => 'append_post_content',
      'args' => array(
      'post_id' => array(
      'required' => true,
      'validate_callback' => function($param, $request, $key) {
      return is_numeric( $param );
      }
      ),
      'content' => array(
      'required' => true,
      'validate_callback' => function($param, $request, $key) {
      return is_string( $param );
      }
      ),
      ),
      ) );
      } );
      add_action('rest_api_init', function () {
      register_rest_route('custom', '/upload-image-from-url', array(
      'methods' => 'POST',
      'callback' => 'custom_upload_image_from_url',
      'args' => array(
      'image_url' => array(
      'required' => true,
      'validate_callback' => function($param, $request, $key) {
      return filter_var($param, FILTER_VALIDATE_URL);
      }
      ),
      // resize 매개변수 추가
      'resize' => array(
      'required' => false,
      'default' => false,
      'validate_callback' => function($param, $request, $key) {
      return is_bool($param) || $param === 'true' || $param === 'false';
      },
      'sanitize_callback' => function($param, $request, $key) {
      return $param === 'true' || $param === true;
      }
      ),
      ),
      'permission_callback' => function () {
      return current_user_can('upload_files');
      }
      ));
      });
      function custom_upload_image_from_url($request) {
      $image_url = $request['image_url'];
      $should_resize = $request['resize'];
      // 지원하는 확장자 목록
      $allowed_extensions = array('jpg', 'jpeg', 'png', 'webp', 'gif');

      // URL에서 확장자 확인
      $is_allowed_extension = true;
      // cURL 세션 초기화
      $ch = curl_init();
      // cURL 옵션 설정
      curl_setopt($ch, CURLOPT_URL, $image_url); // 이미지 URL
      curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // 결과를 문자열로 반환
      curl_setopt($ch, CURLOPT_HEADER, false); // 헤더는 불필요
      curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); // 리다이렉션 따라가기

      // User-Agent 설정 (최신 버전으로 변경)
      curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36');
      // Referer와 Origin 헤더를 실제 타겟 사이트에 맞추기
      curl_setopt($ch, CURLOPT_REFERER, 'www.midjourney.com');
      curl_setopt($ch, CURLOPT_HTTPHEADER, array(
      'Origin: www.midjourney.com',
      'Accept-Language: en-US,en;q=0.9', // 추가적인 헤더
      'Accept-Encoding: gzip, deflate, br'
      ));
      // 쿠키와 세션 처리 (필요한 경우)
      // curl_setopt($ch, CURLOPT_COOKIE, 'name=value; name2=value2');
      // SSL/TLS 설정 (보안을 위해 가능한 활성화 상태를 유지)
      curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
      curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
      // cURL 실행
      $image_data = curl_exec($ch);
      $http_status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
      // cURL 세션 종료
      curl_close($ch);
      $err_text = "";
      if($image_data) {
      $err_text = "Image is Success Download But ";
      }
      // HTTP 상태 코드에 따른 세분화된 에러 처리
      switch ($http_status) {
      case 200:
      // 이미지 접근이 성공적인 경우, 추가 로직을 실행하거나 성공 메시지를 반환할 수 있습니다.
      break;
      case 403:
      return new WP_Error('image_access_forbidden', $err_text . 'The provided image URL points to a resource that is forbidden for access.', array('status' => 403));
      break;
      case 404:
      return new WP_Error('image_not_found', $err_text . 'The provided image URL does not point to an accessible image. Please provide a valid image URL.', array('status' => 404));
      break;
      case 500:
      case 502:
      case 503:
      case 504:
      return new WP_Error('server_error', $err_text . 'The server encountered an error while trying to access the provided image URL. Please try again later.', array('status' => $http_status));
      break;
      default:
      return new WP_Error('unknown_error', $err_text . 'An unknown error occurred while trying to access the provided image URL.', array('status' => $http_status));
      }
      // 이미지 데이터가 있는지 확인
      if (!$image_data) {
      return new WP_Error('image_not_accessible', 'No image data found. Please check the image source.', array('status' => 404));
      }
      // 이미지 데이터로부터 이미지 정보를 얻어냅니다.
      $image_info = @getimagesizefromstring($image_data);
      if ($image_info !== false) {
      // $image_data가 이미지인 경우의 처리
      $is_allowed_extension = true;
      } else {
      // 이미지가 아닌 경우의 처리
      $is_allowed_extension = false;
      }
      if (!$is_allowed_extension) {
      return new WP_Error('unsupported_image_type', 'Unsupported image type.', array('status' => 400));
      }
      $upload_dir = wp_upload_dir();
      $unique_file_name = "";
      $filename = "";
      do {
      // 고유한 파일 이름 생성
      $unique_file_name = uniqid('image-', true) . '-' . rand(1000, 9999) . '.jpg';
      $filename = $upload_dir['path'] . '/' . $unique_file_name;
      } while (file_exists($filename));
      // Imagick 객체를 사용하여 이미지 로드
      $imagick = new Imagick();
      $imagick->readImageBlob($image_data);
      // 이미지 사이즈 체크 및 조절
      $need_resize = ($imagick->getImageWidth() > 1000 || $imagick->getImageHeight() > 1000) && $should_resize;
      if ($need_resize) {
      $imagick->resizeImage(1000, 1000, Imagick::FILTER_LANCZOS, 1, true);
      }
      // 모든 이미지를 JPG로 변환
      $imagick->setImageFormat('jpg');
      $imagick->setImageCompression(Imagick::COMPRESSION_JPEG);
      $imagick->setImageCompressionQuality(75); // 압축률 설정, 필요에 따라 조절
      // 최종 파일 확장자를 jpg로 변경
      $ext = 'jpg';
      // 변경된 이미지 데이터를 파일로 저장
      if ($imagick->writeImage($filename)) {
      $imagick->clear();
      $imagick->destroy();
      } else {
      return new WP_Error('image_conversion_error', 'Failed to convert and save image.', array('status' => 500));
      }
      // 이미지를 미디어 라이브러리에 등록
      $attachment = array(
      'guid' => $upload_dir['url'] . '/' . basename($filename),
      'post_mime_type' => 'image/' . $ext,
      'post_title' => preg_replace('/\.[^.]+$/', '', basename($filename)),
      'post_content' => '',
      'post_status' => 'inherit'
      );
      $attach_id = wp_insert_attachment($attachment, $filename);
      require_once(ABSPATH . 'wp-admin/includes/image.php');
      $attach_data = wp_generate_attachment_metadata($attach_id, $filename);
      wp_update_attachment_metadata($attach_id, $attach_data);
      return new WP_REST_Response(array('url' => $upload_dir['url'] . '/' . basename($filename)), 200);
      }
      add_action( 'rest_api_init', function() {
      register_rest_route( 'custom', '/update-post', array(
      'methods' => 'POST',
      'callback' => 'update_post_contents',
      'args' => array(
      'post_id' => array(
      'required' => true,
      'validate_callback' => function($param, $request, $key) {
      return is_numeric( $param );
      }
      ),
      'content' => array(
      'required' => true,
      ),
      ),
      ) );
      } );
      function update_post_contents( $request ) {
      $post_id = $request['post_id'];
      $content = $request['content'];
      // 게시글 ID와 새 내용으로 게시글 업데이트
      $post_update = wp_update_post( array(
      'ID' => $post_id,
      'post_content' => $content,
      ), true );
      if ( is_wp_error( $post_update ) ) {
      return new WP_Error( 'fail_to_update', 'Post Update Failed', array( 'status' => 500 ) );
      }
      return new WP_REST_Response( array( 'message' => 'Post Updated Successfully', 'status' => 200 ) );
      }

  • @user-jc2tu6zz8e
    @user-jc2tu6zz8e 23 дні тому

    👏👏👏👏👏

  • @bang7lew
    @bang7lew 4 місяці тому +1

    계속 좋은 정보를 공유해주셔서 감사합니다. 염치없이 계속 얻기가 좀 그래서 적지만 감사의 마음을 전합니다.

  • @TV-sy8nc
    @TV-sy8nc 4 місяці тому

    소스놀이터님의 수고 최고!!!!

  • @user-is8vt8rc2g
    @user-is8vt8rc2g 4 місяці тому

    귀한 정보 감사합니다.

  • @user-bl1nl6jw5d
    @user-bl1nl6jw5d 4 місяці тому

    저도 좋은 정보 감사함에 적지만 성의 표합니다~

    • @sourcePlayground
      @sourcePlayground  4 місяці тому

      감사합니다~! 힘내서 좋은 영상으로 답해 드리겠습니다~

  • @DealVilain4989
    @DealVilain4989 4 місяці тому

    대박 계속 발전 하네요 게다가 무료!!!! 감사합니다

  • @AiYoungHo
    @AiYoungHo 28 днів тому

    참 신박하네요. 한가지 문의 드려요. 고유주소를 왜 꼭 요일과 이름으로 해야 될까요? SEO에는 글이름이 유리하다고 알고 있는데요.

    • @sourcePlayground
      @sourcePlayground  28 днів тому

      글이름으로 하셔도 됩니다. 주로 요일과이름으로 테스트 해서 안전성 측면으로 제안드린겁니다. 만약 고유주소 옵션에 따라 호출경로가 바뀌는 경우가 있다면 그에 맞계 Action에 넣어주면 됩니다. (API 주소가 바뀌는 경우가 있을수 있는데 그에 맞게 Action에 넣어주면 됨)
      시도해 보시고 잘되면 피드백 부탁드립니다~

  • @user-un9pm3xp6g
    @user-un9pm3xp6g 12 днів тому

    영상 감사히 잘 보고 있습니다 이제 막 시작하려는 새내기입니다 다른분 블러그 글 보면 켄바랑 이용해서 귀엽고 깜찍하게 글 작성하는 분들 부럽던데 주제나 챗 gpt에서 주제에 맞는 글 작성해서 주면 귀엽고 깜찍하게 폰트랑 색상 거기에 어울리는 이모티콘과 이미지까지 자동으로 만들어 주는 프로그램은 없나요? ㅠㅠ

    • @sourcePlayground
      @sourcePlayground  11 днів тому +1

      이모티콘은 훈련만 잘 시키면 지금도 잘될거 같은데요?
      글 생성 내용은 프롬프트와 훈련을 더하면 품질 차이가 많이 납니다.
      이미지 올리는 건, 원래 잘되었는데.. 최근에 챗GPT에서 막은거 같아요.
      (이미지 도용을 막으려 했을까요?)
      (챗GPT는 막혔지만 다른 이미지 생성 AI 쓰신다면 해당 이미지 경로를 넣어 이미지를 가지고 올 수 있습니다)
      마지막으로 자동 블로그 유료 프로그램이 좀 있는 걸로 압니다. 제가 써보지는 않아 자세히는 모르지만, 이미지 업로드 기능이 있는지 확인해보실 수 있습니다.

  • @user-bl1nl6jw5d
    @user-bl1nl6jw5d 4 місяці тому

    감사합니다.

  • @user-bl1nl6jw5d
    @user-bl1nl6jw5d 4 місяці тому

    좋은 영상 감사합니다. 이미지 주소 알려주고, 올리라고 했더니 gtps가 post 번호를 못 찾아서 그건 워드프레스 관리자 페이지에서 찾아서 알려줬더니 이미지를 올리긴 합니다. 그런데, 올린 이미지가 모니터 화면에서 새끼 손톱만한 작은 크기로 (푸른 하늘에 산에 구름 그려져 있는 이미지. 이미지가 첨부된 자리긴한데, 이미지가 깨진듯한) 올라가네요ㅠㅠ

    • @sourcePlayground
      @sourcePlayground  4 місяці тому +1

      저는 다음 순서로 했습니다.
      먼저 이미지만 따로 올립니다.
      그럼 이미지를 올리고, 확인할 수 있는 주소를 알려줘서 클릭해 보면 제대로 뜹니다.
      그 다음, 이 이미지를 특정 글에 넣어달라고 하면 됩니다.
      보통은 글 생성한 후, 즉시 이미지를 올려달라고 하기에 post_id를 알고 있어 잘 처리해 줍니다.
      예시:
      (1) 챗GPT에 대한 글 작성해서 올려줘 (글 올려줌)
      (2) 챗GPT 이미지 생성해줘. (생성 후 URL 복사하여)
      (3)이 이미지 올려줘 (이미지 올라간 후, 업로드된 이미지 볼 수 있는 URL 알려줌)
      (4) 방금 올라간 이미지를 챗GPT에 대한 글 마지막 부분에 넣어달라고 함 (그럼 제대로 들어감)
      그런데 다른 예로, 글은 이미 있고 이미지만 올릴 경우는 이렇게 해보세요
      (1) 이미지 생성해줘. (생성 후 URL 복사하여)
      (2)이 이미지 올려줘 (이미지 올라간 후, 업로드된 이미지 볼 수 있는 URL 알려줌)
      (3) "ABCD" 글에 이미지 넣고 싶은데, 먼저 "ABCD"로 조회하여 post_id 알아낸 후, 처리해줘

  • @RC-by5hg
    @RC-by5hg 24 дні тому

    소스놀이터님 영상보면서 똑같이 진행했는데, 스키마 내용 입력하고 아래 항목중에 getAllPosts를 테스트 하면 "API 호출에 실패했습니다. 특정 게시물 ID가 필요합니다. 게시물 ID를 제공해 주시면 다시 시도해 보겠습니다." 그리고 CreatePost를 테스트하면 "API 호출에 실패했습니다. 사용자가 글을 작성할 수 있는 권한이 없다고 합니다. 권한을 확인해 주시거나, 다른 방법으로 시도해 보시기 바랍니다."라는 오류메시지가 뜹니다. 이 문제는 어떻게 해결하면 되는지 가르쳐 주시면 감사하겠습니다.

    • @sourcePlayground
      @sourcePlayground  23 дні тому

      getAllPosts의 스키마 내용을 보면
      post_id 항목에 이런 설명이 있습니다.
      조회하고자 하는 게시물의 고유 ID로 /wp-json/wp/v2/posts/{post_id}로 접속하면 해당 post_id 게시물만 조회됩니다. post_id 값이 없 게시물 전체를 조회하게 됩니다. 전체 글 조회시 비워둡니다.
      그래서 post_id 없이 getAllPosts 실행해 달라고 하면, 전체 글을 조회해 줄 겁니다.
      혹은 search 항목에 검색어를 넣을 수 있는데, 특정 검색어를 seach에 넣어 실행해 달라고 하세요.
      처음 몇번의 훈련만 통과하면 이후부터는 잘 실행될 겁니다.
      (사용할 수록 훈련됩니다)
      CreatePost 테스트 시, 글 작성 권한 문제는.. 말그대로 쓰기 권한이 없어서 그럴 수 있습니다. (getAllPosts는 쓰기 권한 없어도 실행 가능함)
      응용 프로그램 비밀번호 설정 부분 확인해 보세요.
      만약 아이디가 adminuser이고 응용프로그램비번이 "1111 2222 3333 4444 5555 6666"라면..
      아래 문자열을 base64 인코딩해야 합니다.
      adminuser:1111 2222 3333 4444 5555 6666
      ("아이디:응용프로그램비번" 이 형태인데 "아이디"와 콜론(:) 사이에 공백이 있으면 안되고, "응용프로그램비번"과 콜론(:)사이에도 공백이 있으면 안됩니다)
      그리고 이때 사용된 아이디는 글쓰기 권한이 있는 아이디여야 하며, 응용프로그램 비번도 동일 아이디로 로그인된 상태에서 얻은 것이어야 합니다 (저는 관리자 아이디로 했습니다)
      한편 Authentication Type은 "API Key"로 하고 Auth Type은 "Basic"으로 하세요.
      그래도 잘 안되시면, 사파리나 다른 브라우저로 해보세요.
      그리고 이 영상도 참조하실 수 있습니다. (이전 버전 영상인데, 특정 부분은 더 자세히 설명되어 있습니다)
      ua-cam.com/video/77Lzaj_UhP4/v-deo.htmlsi=jfBz2pLPtRndktYd

  • @kovill2067
    @kovill2067 Місяць тому

    안녕하세요~~ 좋은 내용들 잘 배우고 있습니다. 감사합니다. 헌데 이미지가업로드가 안되고, 계속 오류가 나네요. 이전에도 안되서 이미지는 직접 올리고 있었는데, 오늘 다시 처음부터 해보자는 마음으로 했는데, 여전히 실패입니다. gpts한테 문제가 모냐고 물어보니까 이렇게 답변합니다. --->>>> '''이미지 업로드 과정에서 계속해서 오류가 발생하고 있는 이유는 제공된 이미지 URL을 사용하여 업로드할 때 문제가 발생하기 때문입니다. 이로 인해 이미지를 게시글에 포함시키는 과정에서 실패하고 있습니다. 다음 단계로는 이미지를 로컬에 다운로드한 후 직접 워드프레스 미디어 라이브러리에 업로드해보는 것을 추천드립니다.'''

    • @sourcePlayground
      @sourcePlayground  Місяць тому

      원인이 다양하여, 증상만으로 원인 도출이 쉽지 않습니다.
      먼저 GPT 설정 화면에서 작업 편집으로 들어와, uploadImageFromUrl에 테스트 버튼 클릭하여 실행해 보세요. 여기서는 에러가 나면, 자세한 로그를 확인 가능합니다. 어떤 에러 문자가 노출되었는지 그대로 알려달라고 해 보세요.
      그리고, 그외의 원인들 중, 서버 단에서 문제가 되었을 수도 있습니다.
      호스팅 서버를 사용하시나요?
      사용하시는 회사의 서버에 Imagick, cURL 모듈이 설치되어 있는지도 확인되어야 합니다.
      이 두가지 모듈이 설치 및 활성화 되어 있어야 이미지 업로드 기능이 정상 작동합니다.
      현재로서는 정확한 에러 문구를 아는 게 가장 빠른 방법입니다.
      아쉽게도 해당 문구를 알려면 서버단에서 로그를 확인해야 할 수도 있기에, GPT 설정의 테스트 버튼이 그나마 접근이 쉬운 방법입니다.

    • @kovill2067
      @kovill2067 Місяць тому

      @@sourcePlayground 테스트버튼을 누르니까 에러가 나와서 알려주신데로 했더니. 다음과 같은 에러문자를 주었습니다. "{
      "code": "image_not_found",
      "message": "Image is Success Download But The provided image URL does not point to an accessible image. Please provide a valid image URL.",
      "data": {
      "status": 404
      }
      }"

    • @sourcePlayground
      @sourcePlayground  Місяць тому

      이미지 경로를 복사하여 넣을 때, 유효하지 않은 URL이라는 설명 같습니다.
      이상해서 살펴보니... 챗GPT에서 이전과 다르게 로그인되지 않은 상태에서는 생성된 이미지에 접근이 불가능하네요.
      자신이 생성한 이미지는 자신의 아이디로 로그인해야 볼 수 있도록 기능이 변경된 거 같습니다.
      그래서 아쉽지만 현재는 챗GPT 생성 이미지는 이 기능이 작동하지 않네요.

    • @kovill2067
      @kovill2067 Місяць тому

      @@sourcePlayground 아쉽기는 하지만 그래도 친절한 답변 감사드립니다.^^ 좋은하루되세요!

  • @user-wy6mx6lv1s
    @user-wy6mx6lv1s Місяць тому

    이전에 만든 글쓰기 자동화 gpt챗 말고 따로 이미지 gpt챗을 만드는 것인가요?

    • @sourcePlayground
      @sourcePlayground  Місяць тому

      네 맞습니다. 이전에 만든 영상의 업그레이드 버전으로 이미지 자동 업로드 기능이 추가되었습니다.

  • @DEVELOPDOC.-ih6ck
    @DEVELOPDOC.-ih6ck 3 місяці тому

    사용자에 응용프로그램비밀번호 항목 자체가 안나오는 건 어떤 문제일까요? "API 키 및 기타 개인 설정을 찾고 계셨습니까?" 문구가 나와 눌러보아도 사용자 프로필로 이동되어집니다. ㅠ

    • @sourcePlayground
      @sourcePlayground  3 місяці тому

      응용프로그램비번은 사용자 프로필의 하단에 나오는데, 사용자 프로필 화면에서 안보인다는 말씀이시죠?
      다음 사항 확인해 보세요.
      1. 워드프레스 버전이 5.6보다 낮은가? (오래된 구버전은 지원하지 않습니다. 관리자 로그인 후의 첫페이지인 대시보드에서 버전 정보 찾아보세요)
      2. 로그인한 사용자가 관리자가 맞는지 확인해 보세요. 낮은 권한의 사용자라면 응용 프로그램 설정 부분이 나오지 않을 수 있습니다.
      3. 보안이나 사용자 권한과 관련된 플러그인이 설치되어 있고, 응용 프로그램 기능을 제한했을 가능성이 있습니다. Wordfence Security, iThemes Security, Usr Role Editor가 설치되어 있는지 확인해 보세요. 혹은 Code Snippets 플러그인을 통해 커스텀 코드가 추가되지는 않았는지 확인해 보세요.
      4. 사용하시는 테마에서 응용프로그램 권한을 제한했을 가능성도 있습니다.
      5. 아주 드문 경우이지만, wp-config.php 파일에서 수동으로 응용 프로그램 비밀번호 기능을 활성 및 비활성화되었을 수 있습니다. define('APPLICATION_PASSWORDS', false); 가 같은 코드가 있다면 define('APPLICATION_PASSWORDS', true); 로 변경하여야 합니다. wp-config.php 파일은 웹사이트의 최상위 폴더인 루트 폴더에 위치합니다. 파일질라와 같은 FTP 프로그램으로 해당 파일을 다운로드 받아 내용을 확인해 볼 수 있습니다. wp-config.php 파일 내용은 가능성이 낮아 마지막에 확인해 보시는 것이 좋습니다.

  • @user-yc8gr1sx9g
    @user-yc8gr1sx9g 3 місяці тому

    functions. php 관련 코드 댓글로 올려주실 수 있나요? ㅠㅠ

    • @sourcePlayground
      @sourcePlayground  3 місяці тому

      사용하시는 테마 폴더에 있는 functions.php 마지막 부분에 붙여넣으세요.
      예: 사용하시는 테마가 twentytwentytwo라면 function.php 경로는 아래와 같습니다.
      /wp-content/themes/twentytwentytwo/functions.php
      (작업 전 반드시 백업이 필요합니다)
      (2016년 12월경에 나온 워드프레스 4.7버전 이상에 적용하세요)
      function append_post_content( $request ) {
      // post_id를 POST 요청 본문으로부터 받음
      $post_id = $request['post_id'];
      $append_content = $request['content'];
      // 게시물을 조회
      $post = get_post( $post_id );
      if ( !$post ) {
      return new WP_Error( 'not_found', 'Post not found', array( 'status' => 404 ) );
      }
      // 기존 내용에 추가
      $new_content = $post->post_content . $append_content;
      // 게시물 업데이트
      wp_update_post( array(
      'ID' => $post_id,
      'post_content' => $new_content
      ) );
      return new WP_REST_Response( array( 'status' => 'success', 'post_id' => $post_id ), 200 );
      }
      // 커스텀 엔드포인트 등록 예: my.myphar.net/wp-json/custom/append-post-content
      add_action( 'rest_api_init', function () {
      register_rest_route( 'custom', '/append-post-content', array(
      'methods' => 'POST',
      'callback' => 'append_post_content',
      'args' => array(
      'post_id' => array(
      'required' => true,
      'validate_callback' => function($param, $request, $key) {
      return is_numeric( $param );
      }
      ),
      'content' => array(
      'required' => true,
      'validate_callback' => function($param, $request, $key) {
      return is_string( $param );
      }
      ),
      ),
      ) );
      } );
      add_action('rest_api_init', function () {
      register_rest_route('custom', '/upload-image-from-url', array(
      'methods' => 'POST',
      'callback' => 'custom_upload_image_from_url',
      'args' => array(
      'image_url' => array(
      'required' => true,
      'validate_callback' => function($param, $request, $key) {
      return filter_var($param, FILTER_VALIDATE_URL);
      }
      ),
      // resize 매개변수 추가
      'resize' => array(
      'required' => false,
      'default' => false,
      'validate_callback' => function($param, $request, $key) {
      return is_bool($param) || $param === 'true' || $param === 'false';
      },
      'sanitize_callback' => function($param, $request, $key) {
      return $param === 'true' || $param === true;
      }
      ),
      ),
      'permission_callback' => function () {
      return current_user_can('upload_files');
      }
      ));
      });
      function custom_upload_image_from_url($request) {
      $image_url = $request['image_url'];
      $should_resize = $request['resize'];
      // 지원하는 확장자 목록
      $allowed_extensions = array('jpg', 'jpeg', 'png', 'webp', 'gif');

      // URL에서 확장자 확인
      $is_allowed_extension = true;
      // cURL 세션 초기화
      $ch = curl_init();
      // cURL 옵션 설정
      curl_setopt($ch, CURLOPT_URL, $image_url); // 이미지 URL
      curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // 결과를 문자열로 반환
      curl_setopt($ch, CURLOPT_HEADER, false); // 헤더는 불필요
      curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); // 리다이렉션 따라가기

      // User-Agent 설정 (최신 버전으로 변경)
      curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36');
      // Referer와 Origin 헤더를 실제 타겟 사이트에 맞추기
      curl_setopt($ch, CURLOPT_REFERER, 'www.midjourney.com');
      curl_setopt($ch, CURLOPT_HTTPHEADER, array(
      'Origin: www.midjourney.com',
      'Accept-Language: en-US,en;q=0.9', // 추가적인 헤더
      'Accept-Encoding: gzip, deflate, br'
      ));
      // 쿠키와 세션 처리 (필요한 경우)
      // curl_setopt($ch, CURLOPT_COOKIE, 'name=value; name2=value2');
      // SSL/TLS 설정 (보안을 위해 가능한 활성화 상태를 유지)
      curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
      curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
      // cURL 실행
      $image_data = curl_exec($ch);
      $http_status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
      // cURL 세션 종료
      curl_close($ch);
      $err_text = "";
      if($image_data) {
      $err_text = "Image is Success Download But ";
      }
      // HTTP 상태 코드에 따른 세분화된 에러 처리
      switch ($http_status) {
      case 200:
      // 이미지 접근이 성공적인 경우, 추가 로직을 실행하거나 성공 메시지를 반환할 수 있습니다.
      break;
      case 403:
      return new WP_Error('image_access_forbidden', $err_text . 'The provided image URL points to a resource that is forbidden for access.', array('status' => 403));
      break;
      case 404:
      return new WP_Error('image_not_found', $err_text . 'The provided image URL does not point to an accessible image. Please provide a valid image URL.', array('status' => 404));
      break;
      case 500:
      case 502:
      case 503:
      case 504:
      return new WP_Error('server_error', $err_text . 'The server encountered an error while trying to access the provided image URL. Please try again later.', array('status' => $http_status));
      break;
      default:
      return new WP_Error('unknown_error', $err_text . 'An unknown error occurred while trying to access the provided image URL.', array('status' => $http_status));
      }
      // 이미지 데이터가 있는지 확인
      if (!$image_data) {
      return new WP_Error('image_not_accessible', 'No image data found. Please check the image source.', array('status' => 404));
      }
      // 이미지 데이터로부터 이미지 정보를 얻어냅니다.
      $image_info = @getimagesizefromstring($image_data);
      if ($image_info !== false) {
      // $image_data가 이미지인 경우의 처리
      $is_allowed_extension = true;
      } else {
      // 이미지가 아닌 경우의 처리
      $is_allowed_extension = false;
      }
      if (!$is_allowed_extension) {
      return new WP_Error('unsupported_image_type', 'Unsupported image type.', array('status' => 400));
      }
      $upload_dir = wp_upload_dir();
      $unique_file_name = "";
      $filename = "";
      do {
      // 고유한 파일 이름 생성
      $unique_file_name = uniqid('image-', true) . '-' . rand(1000, 9999) . '.jpg';
      $filename = $upload_dir['path'] . '/' . $unique_file_name;
      } while (file_exists($filename));
      // Imagick 객체를 사용하여 이미지 로드
      $imagick = new Imagick();
      $imagick->readImageBlob($image_data);
      // 이미지 사이즈 체크 및 조절
      $need_resize = ($imagick->getImageWidth() > 1000 || $imagick->getImageHeight() > 1000) && $should_resize;
      if ($need_resize) {
      $imagick->resizeImage(1000, 1000, Imagick::FILTER_LANCZOS, 1, true);
      }
      // 모든 이미지를 JPG로 변환
      $imagick->setImageFormat('jpg');
      $imagick->setImageCompression(Imagick::COMPRESSION_JPEG);
      $imagick->setImageCompressionQuality(75); // 압축률 설정, 필요에 따라 조절
      // 최종 파일 확장자를 jpg로 변경
      $ext = 'jpg';
      // 변경된 이미지 데이터를 파일로 저장
      if ($imagick->writeImage($filename)) {
      $imagick->clear();
      $imagick->destroy();
      } else {
      return new WP_Error('image_conversion_error', 'Failed to convert and save image.', array('status' => 500));
      }
      // 이미지를 미디어 라이브러리에 등록
      $attachment = array(
      'guid' => $upload_dir['url'] . '/' . basename($filename),
      'post_mime_type' => 'image/' . $ext,
      'post_title' => preg_replace('/\.[^.]+$/', '', basename($filename)),
      'post_content' => '',
      'post_status' => 'inherit'
      );
      $attach_id = wp_insert_attachment($attachment, $filename);
      require_once(ABSPATH . 'wp-admin/includes/image.php');
      $attach_data = wp_generate_attachment_metadata($attach_id, $filename);
      wp_update_attachment_metadata($attach_id, $attach_data);
      return new WP_REST_Response(array('url' => $upload_dir['url'] . '/' . basename($filename)), 200);
      }
      add_action( 'rest_api_init', function() {
      register_rest_route( 'custom', '/update-post', array(
      'methods' => 'POST',
      'callback' => 'update_post_contents',
      'args' => array(
      'post_id' => array(
      'required' => true,
      'validate_callback' => function($param, $request, $key) {
      return is_numeric( $param );
      }
      ),
      'content' => array(
      'required' => true,
      ),
      ),
      ) );
      } );
      function update_post_contents( $request ) {
      $post_id = $request['post_id'];
      $content = $request['content'];
      // 게시글 ID와 새 내용으로 게시글 업데이트
      $post_update = wp_update_post( array(
      'ID' => $post_id,
      'post_content' => $content,
      ), true );
      if ( is_wp_error( $post_update ) ) {
      return new WP_Error( 'fail_to_update', 'Post Update Failed', array( 'status' => 500 ) );
      }
      return new WP_REST_Response( array( 'message' => 'Post Updated Successfully', 'status' => 200 ) );
      }

    • @user-yc8gr1sx9g
      @user-yc8gr1sx9g 3 місяці тому

      정말 감사합니다 ㅎㅎ