OGP(Open Graph Protocol)とは、WebページのURLがX(旧Twitter)やFacebookなどのSNSに投稿されたときに、タイトルや画像、説明文などの補足情報を表示するための仕組みです。
以下のページでは[現在時刻を X にポストする]をクリックするとこのような現在時刻を含む画像がポストされます。
前提
まずページ内にボタンを設置してクリックするとXに投稿されるようにするだけなら以下の方法でOKです。
index.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>ページのタイトル</title> </head> <body> <a href="https://twitter.com/share?ref_src=twsrc%5Etfw" class="twitter-share-button" data-text="投稿したい文章" data-url="test.html" data-hashtags="設定したいハッシュタグ,カンマ区切りで複数指定可能" data-size="large" data-lang="ja">X</a> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </body> </html> |
ただこの方法だと投稿ボタンを最初は非表示にしておき、動的に表示させようとするとうまく動いてくれません。以下のHTMLでは3秒後に文字は表示されるのですが、肝心のボタンが表示されません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
|
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>タイトル</title> <style> #x { display: none; } </style> </head> <body> <div id = "x"> <p>動的にボタンを表示させたい</p> <a href="https://twitter.com/share?ref_src=twsrc%5Etfw" class="twitter-share-button" data-text="投稿したい文章" data-url="test.html" data-hashtags="設定したいハッシュタグ,カンマ区切りで複数指定可能" data-size="large" data-lang="ja">X</a> </div> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> <script> setTimeout(() => { document.getElementById('x').style.display = 'block'; },3000); </script> </body> </html> |
動的に表示させたい場合は以下のようにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>タイトル</title> <style> #x { display: none; } </style> </head> <body> <button id="x">X</button> <script> document.getElementById('x').addEventListener('click', () => { const text = '投稿したい文章'; const hashtags = '設定したいハッシュタグ'; const url = location.href; //該当するページのurl const encode_url = encodeURIComponent(url); window.open('https://twitter.com/intent/tweet?text=' + text + '&hashtags=' + hashtags + '&url=' + encode_url); }); setTimeout(() => { document.getElementById('x').style.display = 'block'; },3000); </script> </body> </html> |
OGPを設定するときは以下のようにします。この部分はJavaScriptではできないのでHTMLに直接書きます。headタグ内に以下を追加します。これでXに投稿したときに画像等もいっしょに投稿されるようになります。
|
<head> <meta charset="UTF-8"> <meta property="og:title" content="ページのタイトル"> <meta property="og:type" content="ページの種類"> <meta property="og:url" content="ページのURL"> <meta property="og:image" content="画像のURL"> <meta property="og:site_name" content="ホームぺージのタイトル"> <meta property="og:description" content="ページの説明"> <meta property="og:locale" content="言語"> <meta name="twitter:card" content="summary_large_image"> <meta name="twitter:site" content="@ユーザー名"> </head> |
JavaScriptでOGPを書き換えても意味無し
ではJavaScriptでOGPを書き換えることは可能でしょうか? これが可能になれば公開しているゲームを任意のタイミングでキャプチャしたものを解散させたいときに使えそうです。
JavaScriptでOGPを書き換える | cly7796.net ではまさにそれをやろうとしているのですが、結論は「JavaScriptでOGPの書き換えや追加を行っても、facebook・twitter共に認識できない」です。
自分でもあれこれやってみたのですが、やっぱりうまくいきません。
実は https://twitter.com/intent/tweet? のパラメーターに追加する url の部分、これは投稿ボタンを設置したページではなくそれ以外のページであっても機能します。そしてOGPを機能させるためには自分のページではなく対象のページに上記のmetaタグが入っていなければならないのです。そのため対象のページにアクセスした段階ですでに上記のmetaタグが存在する状態でなければなりません。JavaScriptでOGPを書き換えたとしても認識されるはずがないのです。
打開策
ではどうするか? PHPでそのようなページをつくることにします。
canvasに描画されている画像を保存する
これは現在時刻をcanvasに表示するためのHTMLとJavaScriptです。
index.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>OGPに動的に画像を設定する方法</title> <meta name = "viewport" content = "width=device-width, initial-scale = 1.0"> <style> #text, #tag { width: 300px; } </style> </head> <body> <div id = "container"> <div id = "canvas-outer"></div> <p><button id="tw-button">現在時刻を X にポストする</button></p> <p>投稿文(30文字まで)<br><input id = "text" maxlength="30"></p> <p>ハッシュタグ(複数のときはカンマ区切り 20文字まで)<br><input id = "tag" maxlength="20"></p> </div> <script src = "app.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script> </body> </html> |
app.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
|
const $canvasOuter = document.getElementById('canvas-outer'); const $canvas = document.createElement('canvas'); const ctx = $canvas.getContext('2d'); const $text = document.getElementById('text'); const $tag = document.getElementById('tag'); window.onload = () => { initCanvas(); update(); $text.value = 'ただのテストなので絶対にいいねはしないでください。'; $tag.value = 'いいね禁止'; document.getElementById('tw-button').onclick = () => { onClick(); // ボタンをクリックしたらXに投稿されるがこの部分は後述 } } function initCanvas(){ $canvas.width = 300; $canvas.height = 200; $canvasOuter.appendChild($canvas); } function update(){ requestAnimationFrame(() => update()); ctx.fillStyle = '#080'; ctx?.fillRect(0, 0, $canvas.width, $canvas.height); ctx.textBaseline = 'top'; ctx.fillStyle = '#fff'; const text = getTimeText(); ctx.font = '50px Arial'; const x = ($canvas.width - ctx?.measureText(text).width) / 2; ctx?.fillText(text, x, 60); } function getTimeText(){ const now = new Date(); let h = now.getHours(); let m = now.getMinutes(); let s =now.getSeconds(); if(m < 10) m = '0' + m; if(s < 10) s = '0' + s; return `${h} : ${m} : ${s}`; } |
ボタンがクリックされたらcanvasをキャプチャしてこれをPHP側にPOSTします。サーバー側でPNGファイルが生成され、この画像を用いたOGPが設定されたページが生成されます。あとはこのページをXにポストすることでOGPに動的に画像が設定されたことにしてしまおうというわけです。
app.js
|
const now = Date.now(); // ファイル名には現在時刻を用いることにする const data_url = $canvas.toDataURL('image/png'); $.post('./png.php', { now:now, data_url:data_url, }); |
POSTされるDataURL文字列は ‘data:image/png;base64,iVBORw0KGgoAAAANS..’ のように ‘data:image/png;base64,’からはじまるのでそれ以降をデコードして画像ファイルに変換します。ただこれだと危険なスクリプトのような悪意のあるデータが送りつけられるかもしれないので、画像ファイルに変換可能なものであることを確認し、そうでないものはサーバー内には保存されないようにしています。
png.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
<?php save_png(); function save_png(){ $data_url = $_POST['data_url']; $now = $_POST['now']; $base64_data = str_replace("data:image/png;base64,", "", $data_url); $decoded_data = base64_decode($base64_data); if (imagecreatefromstring($decoded_data) !== false) { $filepath = './' . $now. '.png'; if (!$fp = fopen($filepath,"a")){ } flock($fp, LOCK_EX); fwrite($fp, $decoded_data); flock($fp, LOCK_UN); fclose($fp); } } |
OGPが設定されたページを表示させる
page.php?time=’ + (JavaScript内のDate.now()が返した値)にアクセスすると上記で生成したPNGファイルを用いたOGPが設定されたページを表示させる処理を示します。og:image は相対urlではなく絶対urlでなければならないので最初にサーバー上に生成されたPNGファイルのurlを求める処理をしています。
これでこのurlをXにポストすれば投稿文とともに画像も表示されるようになります。またユーザーがXの投稿をクリックしたときもこのページが表示されます。この場合は 0.5秒後に見てほしいページにリダイレクトされるようにしています。
page.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
|
<?php // サーバー上に生成されたPNGファイルのurlを求める処理と引数なしの自分自身のurlを取得する $url = (empty($_SERVER["HTTPS"]) ? "http://" : "https://") . $_SERVER["HTTP_HOST"] . $_SERVER["REQUEST_URI"]; if($url[-1] != '/'){ $pos = strrpos($url, '?'); if($pos !== false) $this_url = substr($url, 0, $pos); } if($url[-1] != '/'){ $pos = strrpos($url, '/'); if($pos !== false) $url = substr($url, 0, $pos + 1); } $time = $_GET['time']; $image_url = $url . $time . '.png'; ?> <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name = "robots" content = "noindex"><!-- このページは検索エンジンに評価してほしくない --> <meta name = "viewport" content = "width=device-width, initial-scale = 1.0"> <title></title> <meta property="og:title" content="鳩でもわかるOGP"> <meta property="og:description" content="OGPに動的に画像を設定する方法を考えます。"> <meta property="og:type" content="website"> <meta property="og:url" content="<?php echo $this_url ?>"> <meta property="og:image" content="<?php echo $image_url ?>"> <meta name="twitter:card" content="summary_large_image"> <meta name="twitter:site" content="@THE_RANRAN"> </head> <body> <script> setTimeout(() => { location.href = './'; }, 500); </script> </body> </html> |
不要になったファイルを削除する
処理を繰り返すたびにPNGファイルがサーバー上に生成されていきます。処理が終わったらこのファイルはいらないので削除したいです。PNGファイルを削除する処理を示します。
ここでも悪意あるデータが送りつけられて意図しないファイルが消えてしまわないように $_POST[‘now’] で取得された文字列がすべて数字であることを確認してからファイル削除の処理をおこなっています。
delete.php
|
<?php delete_png(); function delete_png(){ $now = $_POST['now']; if(is_numeric($now)) unlink($now . '.png'); } |
完成させる
上記の処理をまとめます。[現在時刻を X にポストする]ボタンがクリックされたときに処理を示します。
window.openで生成されたウィンドウが消滅したら投稿の処理は確実に完了したか途中でキャンセルされたことになります。そこでこのタイミングでサーバー上のPNGファイルを削除します。
app.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
function onClick(){ const now = Date.now(); const data_url = $canvas.toDataURL('image/png'); $.post('./png.php', { now:now, data_url:data_url, }).done(data => { const text = $text.value; const hashtags = $tag.value; // page.php はHTMLと同じディレクトリにあるのだが、そのurlを取得しようとしている const arr = location.href.split('/'); arr.length -= 1; const encode_url = encodeURIComponent(arr.join('/') + '/page.php?time=' + now); const new_window = window.open('https://twitter.com/intent/tweet?text=' + text + '&hashtags=' + hashtags + '&url=' + encode_url); const timer = setInterval(() => { // 0.1秒おきにウィンドウが閉じられたかチェックする if(new_window.closed){ clearInterval(timer); console.log('closed'); $.post('./delete.php', { now:now, }).done(d => { console.log('完了'); }); } }, 100); }); } |