【WordPress】noteの記事をサムネ付きでカード表示する方法

雑記

noteを投稿した後に、自分のブログやHPにリンクを貼りたいときってありますよね。
ただ、そのたびに記事URLをコピーして <a href=…> とタグを書いて……というのは、あまりに面倒です。

そこで今回は、RSS機能を活用して、note記事を自動でカードリンク化する方法 をまとめました。

どんな感じになるかは、当HPのトップページをご覧ください。
「(note)」と書いてある箇所が、今回の方法で表示している部分です。

⚠️ 注意点

  • functions.php子テーマに追記してください(親テーマはアップデートで消えます)
  • 作業前にバックアップ推奨
  • 万一500エラーが出たら、FTP/ファイルマネージャーで直前の変更を戻してください
  • この記事のコードは自己責任でご利用ください

現環境(参考)

WordPressバージョン:6.8.2
テーマ:Cocoon(子テーマ)
ブラウザ:GoogleChrome

前準備 noteのRSS取得

まず、noteのRSSを取得します。
下記URLを参考に、自分のIDに書き換えてアドレスバーに入力してください。

RSSのURLルール:https://note.com/<あなたのID>/rss


例:https://note.com/ichihara/rss

上記を入力して、「This XML file~」の見出しのXMLファイルが見えたらOK。

WordPressでRSSを読み込む

子テーマの外観→テーマファイルエディター→function.phpに直接追記。

まずは正しく読み込むか、最小限のPHPコードを記載して更新する。

// RSSキャッシュ15分
add_filter('wp_feed_cache_transient_lifetime', function(){ return 900; });
// タイムアウト短縮
add_filter('wp_feed_options', function($feed){ $feed->set_timeout(5); });

// ショートコード [note_list url="https://note.com/あなたのID/rss" count="5"]
add_shortcode('note_list', function($atts){
  $atts = shortcode_atts(['url'=>'','count'=>5], $atts);
  if (empty($atts['url'])) return '<!-- note_list: url未指定 -->';

  require_once ABSPATH.WPINC.'/feed.php';
  $rss = fetch_feed( esc_url_raw($atts['url']) );
  if (is_wp_error($rss)) return '<!-- note_list: 取得失敗 -->';

  $items = $rss->get_items(0, (int)$atts['count']);
  if (!$items) return '<p>noteの新着はありません。</p>';

  $html = '<ul class="note-list">';
  foreach($items as $it){
    $html .= sprintf(
      '<li><a href="%s" target="_blank" rel="noopener">%s</a></li>',
      esc_url($it->get_permalink()),
      esc_html($it->get_title())
    );
  }
  $html .= '</ul>';
  return $html;
});

これでWordPress上でショートコードが作成可能になったので、記事や固定ページに下記のショートコードを貼り付けてみる。

[note_list url=”https://note.com/あなたのID/rss” count=”5″]

あなたのID部分だけ書き換えておく
count数は表示される数なので、好きな数に指定してOK。

ここでまずはタイトル一覧が出ることを確認します。
これで何も表示されなければ、URLやIDの誤り、またはサーバ側接続を疑って修正・リトライしてください。

一覧をカード表示(サムネつき)に書き換える

上記で動作を確認できたら、先程記入したコードを下記のコードに差し替えます。

// ==== Note RSS → カード表示(画像つき) ====
// RSSキャッシュ15分
add_filter('wp_feed_cache_transient_lifetime', function(){ return 900; });
// fetch_feed タイムアウト
add_filter('wp_feed_options', function($feed){ $feed->set_timeout(5); });

// HTMLから最初の<img>抽出(data-src/srcset対応)
if (!function_exists('_el_first_img_src')){
  function _el_first_img_src($html){
    if (empty($html)) return '';
    $pats = [
      '/<img[^>]+data-src=["\']([^"\']+)["\']/i',
      '/<img[^>]+data-original=["\']([^"\']+)["\']/i',
      '/<img[^>]+srcset=["\']([^"\']+)["\']/i',
      '/<img[^>]+src=["\']([^"\']+)["\']/i',
    ];
    foreach($pats as $p){
      if (preg_match($p,$html,$m)){
        $u = trim($m[1]);
        if (strpos($p,'srcset')!==false){
          $parts = preg_split('/\s*,\s*/',$u);
          $u = preg_replace('/\s+\d+w$/','',$parts[0]);
        }
        if (strpos($u,'//')===0) $u = 'https:'.$u;
        return $u;
      }
    }
    return '';
  }
}

// OGP最終フォールバック(6hキャッシュ・UA付与)
if (!function_exists('_el_fetch_og_image')){
  function _el_fetch_og_image($url){
    if (empty($url)) return '';
    $key = 'el_ogimg_'.md5($url);
    $cached = get_transient($key);
    if ($cached!==false) return $cached;

    $res = wp_remote_get($url, [
      'timeout'=>5,'redirection'=>3,
      'user-agent'=>'Mozilla/5.0 Chrome/124 WordPress/note-feed-cards',
    ]);
    if (is_wp_error($res)){ set_transient($key,'',30*MINUTE_IN_SECONDS); return ''; }

    $html = wp_remote_retrieve_body($res);
    $img = '';
    if (preg_match('/<meta[^>]+property=["\']og:image["\'][^>]+content=["\']([^"\']+)["\']/i',$html,$m)){
      $img = trim($m[1]);
    } elseif (preg_match('/<meta[^>]+name=["\']twitter:image["\'][^>]+content=["\']([^"\']+)["\']/i',$html,$m)){
      $img = trim($m[1]);
    }
    if ($img && strpos($img,'//')===0) $img='https:'.$img;

    set_transient($key,$img,$img?6*HOUR_IN_SECONDS:30*MINUTE_IN_SECONDS);
    return $img;
  }
}

// [note_feed_cards url="https://note.com/あなたのID/rss" count="6" class="note-feed-cards" title_tag="div"]
add_shortcode('note_feed_cards', function($atts){
  $atts = shortcode_atts([
    'url'=>'','count'=>6,'class'=>'note-feed-cards',
    'target'=>'_blank','title_tag'=>'div'
  ], $atts, 'note_feed_cards');
  if (empty($atts['url'])) return '<!-- note_feed_cards: url未指定 -->';

  $allowed = ['p','div','span','strong','em','b','h3','h4'];
  $title_tag = in_array($atts['title_tag'],$allowed,true)?$atts['title_tag']:'div';

  require_once ABSPATH.WPINC.'/feed.php';
  $rss = fetch_feed( esc_url_raw($atts['url']) );
  if (is_wp_error($rss)) return '<!-- note_feed_cards: 取得失敗 -->';

  $items = $rss->get_items(0, max(1,(int)$atts['count']));

  $pick_image = function(SimplePie_Item $item){
    if ($u=_el_first_img_src($item->get_content()))     return $u;
    if ($u=_el_first_img_src($item->get_description())) return $u;

    foreach (['thumbnail','content'] as $tag){
      $media = $item->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS,$tag);
      if (!empty($media[0]['attribs']['']['url'])) return $media[0]['attribs']['']['url'];
    }
    $encs = method_exists($item,'get_enclosures')?$item->get_enclosures():[];
    if (!empty($encs)){
      $e=$encs[0];
      if (method_exists($e,'get_link') && $e->get_link()) return $e->get_link();
      if (!empty($e->link)) return $e->link;
    }
    return _el_fetch_og_image($item->get_permalink());
  };

  ob_start(); ?>
  <div class="note-feed-wrap">
    <div class="<?php echo esc_attr($atts['class']); ?>" role="list">
      <?php if ($items): foreach($items as $item):
        $link  = $item->get_permalink();
        $title = $item->get_title();
        $dateU = $item->get_date('U');
        $body  = $item->get_content();
        $img   = $pick_image($item);

        $raw = wp_strip_all_tags($body);
        $excerpt = function_exists('mb_substr')
          ? trim(mb_substr($raw,0,50)).(mb_strlen($raw)>50?'…':'')
          : wp_trim_words($raw,30,'…');

        $host = parse_url($link, PHP_URL_HOST);
      ?>
      <a class="note-card" role="listitem" href="<?php echo esc_url($link); ?>"
         target="<?php echo esc_attr($atts['target']); ?>" rel="noopener">
        <div class="note-card__media">
          <?php if ($img): ?>
            <img class="note-card__img" src="<?php echo esc_url($img); ?>" alt="" loading="lazy" decoding="async">
          <?php else: ?>
            <div class="note-card__img note-card__img--ph" aria-hidden="true"></div>
          <?php endif; ?>
        </div>
        <div class="note-card__body">
          <<?php echo $title_tag; ?> class="note-card__title"><?php echo esc_html($title); ?></<?php echo $title_tag; ?>>
          <div class="note-card__meta">
            <time class="note-card__date" datetime="<?php echo esc_attr(date_i18n('c',$dateU)); ?>">
              <?php echo esc_html(date_i18n('Y.m.d',$dateU)); ?>
            </time>
            <?php if ($host): ?><span class="note-card__host"><?php echo esc_html($host); ?></span><?php endif; ?>
          </div>
          <?php if ($excerpt): ?><p class="note-card__excerpt"><?php echo esc_html($excerpt); ?></p><?php endif; ?>
        </div>
      </a>
      <?php endforeach; else: ?>
        <p class="note-card__empty">noteの新着はありません。</p>
      <?php endif; ?>
    </div>
  </div>
  <?php return ob_get_clean();
});

ショートコードは先程と同じもので問題ありません。
画面を更新すると表示が変わると思います。

軽くCSSで形を整えてみる

最低限整えられるCSSコードです。
これでカード化できますが、他CSSと競合する可能性もあるため、お好みでカスタマイズしてください。

.note-feed-wrap{max-width:1040px;margin-inline:auto;padding-inline:12px}
.note-feed-cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:12px}
.note-card{display:block;border:1px solid #e6e6e9;background:#fff;border-radius:10px;overflow:hidden;text-decoration:none;color:inherit;transition:box-shadow .15s ease,border-color .15s ease}
.note-card:hover{box-shadow:0 4px 10px rgba(0,0,0,.1);border-color:#ddd}
.note-card__media{aspect-ratio:16/9;background:#f6f7f9}
.note-card__img{width:100%;height:100%;object-fit:cover;display:block}
.note-card__body{padding:10px 12px;display:grid;gap:6px}
.note-card__title{font-size:.9rem;line-height:1.25;font-weight:600;margin:0;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.note-card__meta{display:flex;gap:8px;align-items:baseline;color:#667;font-size:.75rem}
.note-card__date{font-variant-numeric:tabular-nums}
.note-card__host{padding-left:8px;border-left:1px solid #eee;font-size:.7rem;color:#889}
.note-card__excerpt{font-size:.8rem;line-height:1.4;margin:0;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
@media (max-width:480px){.note-card{width:100%}}

これでnoteを更新すると、自動で記事がカード型で更新されるようになると思います。
環境によりPHPやCSSが噛み合わなかったりしてうまくいかない場合もあるかもしれませんが、その時にはご自身のサイトの仕様に合わせて調整してみてください。

FAQ

1. 変更が反映されない
→ RSSは15分キャッシュされます。すぐ確認したい場合は、ショートコードの url の末尾に ?v=2 などクエリを足してみてください。

2. 画像が出ないカードある
→ 該当noteが本文に画像を含まず、かつOGPが設定されていない場合はプレースホルダ表示になります(仕様)。

3. 表示が崩れてしまう
→ テーマのCSSと競合している可能性があります。配布の最小CSSをベースに、class名の衝突回避(例:.note-card.el-note-cardへリネーム)を検討してください。

noteとブログを手軽に両立したかった!

私は都度更新が大の苦手な怠慢なので、noteを更新するだけでHP上でも更新されんかな~と思い、自分なりのやり方でカスタマイズしてみました。

素人の作ったコードではありますが、お役に立つと嬉しいです。

コメント

タイトルとURLをコピーしました