tag:crieit.net,2005:https://crieit.net/tags/Twitter/feed 「Twitter」の記事 - Crieit Crieitでタグ「Twitter」に投稿された最近の記事 2023-06-01T15:35:19+09:00 https://crieit.net/tags/Twitter/feed tag:crieit.net,2005:PublicArticle/18442 2023-06-01T15:35:19+09:00 2023-06-01T15:35:19+09:00 https://crieit.net/posts/Twitter-64783c27d7c13 個人開発でTwitter広告を使っていい感じになった例 <p>時々Twitter広告を使ってみたけど全然だった、という話をよく見ますが、最近僕も使っていてなんとなくいい感じに運用できている気がするので紹介します。とにかく、Twitter広告は超おすすめなサービスだと思っています。</p> <h2 id="Twitter広告を使ってるサービス"><a href="#Twitter%E5%BA%83%E5%91%8A%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E3%82%8B%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9">Twitter広告を使ってるサービス</a></h2> <p>AIバトラーというChatGPTのAI審判がバトルを判定してくれる異能力バトルゲームを運営しているのですが、そちらの集客のため利用しています。</p> <p><a target="_blank" rel="nofollow noopener" href="https://ai-battle.alphabrend.com/">AIバトラー</a></p> <h2 id="何がそんなにおすすめなのか"><a href="#%E4%BD%95%E3%81%8C%E3%81%9D%E3%82%93%E3%81%AA%E3%81%AB%E3%81%8A%E3%81%99%E3%81%99%E3%82%81%E3%81%AA%E3%81%AE%E3%81%8B">何がそんなにおすすめなのか</a></h2> <p>Twitter広告は他の広告サービスと異なるところがあります。それは…、そう、広告がツイートなのです!</p> <p>つまり、広告が表示され、それをユーザーがクリックして流入を得ることができるだけでなく、いいねやRTによってさらに集客力が加速するのです。しかもその場合、どうもクリック単価が安くなるっぽいのです。実際どうなのかはわかりませんが、もしかしたら広告として表示した場合以外のクリックには料金がかからないのかもしれません。</p> <p>実際どうなのかぼくもあまり良く仕組みはわかっていません。ただ、広告を始めたばかりの頃はクリック単価100円とか200円とかかかっていました。1日1000円の予算でやっているので、4,5回クリックされたらもうそれで終わりです。一瞬で1000円が溶けます。月あたり3万円です。</p> <p>ところが今はクリック単価数円です。1000円で流入が当時の100倍くらいになっています。</p> <p>どの広告サービスも予算内でなるべく多くの流入になるよう徐々に調整してくれます。Twitter広告はその効果が半端ないのです。続ければ続けるほど効率がと効果が上がっていきます。</p> <p>とにかく、自分で育てていく広告サービス、とでもいうのでしょうか。僕もみんなの体験談や、時々5000円くらい使って試した経験からTwitter広告はむちゃくちゃ高い、という印象しかなかったのですが、今回じっくり使ってみて完全にその印象はくつがえりました。やばいサービスです。</p> <h2 id="コツ"><a href="#%E3%82%B3%E3%83%84">コツ</a></h2> <p>だからこそ色々とコツはある気がします。適当な広告を出していても多分うまく行かないパターンはあると思います。</p> <h3 id="興味を引きやすい広告にする"><a href="#%E8%88%88%E5%91%B3%E3%82%92%E5%BC%95%E3%81%8D%E3%82%84%E3%81%99%E3%81%84%E5%BA%83%E5%91%8A%E3%81%AB%E3%81%99%E3%82%8B">興味を引きやすい広告にする</a></h3> <p>これはどの広告サービスを使う場合も同じだと思いますが、特にTwitter広告の場合はいいねやRTしてもらいやすそうな、Twitterで過ごしているユーザーの興味を引くようなツイートを作りましょう。</p> <p>例えばすごい! と思うような動画や画像をつけることも良いですし、面白いツイートの内容でも良いと思います。僕の場合は完全にChatGPTの流行りに乗った感じです。</p> <h3 id="ターゲッティングを調整する"><a href="#%E3%82%BF%E3%83%BC%E3%82%B2%E3%83%83%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0%E3%82%92%E8%AA%BF%E6%95%B4%E3%81%99%E3%82%8B">ターゲッティングを調整する</a></h3> <p>Twitter広告を始めてみるとわかりますが、かなりターゲッティング方法がバラエティに富んでいます。例えば下記のような基本的な項目。</p> <ul> <li>性別</li> <li>年齢</li> <li>居住地</li> <li>Android、iOS</li> <li>ユーザーの興味関心</li> </ul> <p>その他にも特定の機種、キーワード、面白いのがフォロワーが似ているアカウント、というもので、あるTwitterアカウントを指定するとそのユーザーのフォロワーに似ている人がターゲッティングされます。僕もAIやChatGPTについてよく発信している人をターゲッティングしています。(あわよくばそのインフルエンサーに届いて利用してもらえたらいいなとかも思いました)</p> <p>とにかく一度この辺は試してみると面白いです。</p> <p>その他、場所としても検索結果に表示するなどの設定もあったりします。なのでこのターゲッティング方法を逆に利用して宣伝しやすいサービスを作ってみる、というのも面白いかもしれません。逆に言うと、サービスを作る上でどうやって集客を行うか? このあたりの知識を深めるためにも一度やってみるのがおすすめです。集客方法を知らないのにむやみにサービスを作っても利用してもらえなければ悲しいです。</p> <h2 id="そもそも個人開発サービスで広告使うのってどうなの?"><a href="#%E3%81%9D%E3%82%82%E3%81%9D%E3%82%82%E5%80%8B%E4%BA%BA%E9%96%8B%E7%99%BA%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%81%A7%E5%BA%83%E5%91%8A%E4%BD%BF%E3%81%86%E3%81%AE%E3%81%A3%E3%81%A6%E3%81%A9%E3%81%86%E3%81%AA%E3%81%AE%EF%BC%9F">そもそも個人開発サービスで広告使うのってどうなの?</a></h2> <p>どう思われますか? 僕は以前は微妙だな…と思っていました。やはりお金がかかって大変だし、可能であればうまいことやってバズらせて無料でガンガン集客できる方法を編み出して集客したい、と思っていました。売れてるサービス開発者のようにかっこよくサービスを流行らせたいです。</p> <p>でも実際やってみて、それはほとんど絵空事であると思っています。もちろんそのやり方でうまくいく場合もあると思いますが、稀です。たとえたまたまTwitterでバズってむちゃくちゃいいね&RTされたとしても、その後うまく運用できなければ一瞬輝いたようにみえるだけど終わってしまうサービスもいくつもあったと思います。うまくいった人はうまくいったとそれを正解のように言いますが、じゃあ全部うまくいくかというとそうではありません。その人達も色々やって、うまくいくものもいかないものもあるのです。(もちろんうまくいくものをそうやって作れる人はそれなりに確率をあげる方法を知っているので間違いということもありません)</p> <p>まず自分はちっぽけな存在であることを認識する必要があります。自分はそんな世の中に影響力を持った人間ではありません。何もせずにガンガンサービスを使ってもらうことなんてできません。</p> <p>じゃあどうやってサービスを使ってもらうのか? やはり行き着くところは広告です。</p> <p>そもそも個人開発サービスでうまく行かない理由の一つに、自分のサービスを使いたいと思うであろうユーザーに届かないということなのです。サービス集客のためにTwitterのフォロワーを増やすとかみんなやると思いますが、それではユーザーには届きません。自分をフォローしてくれている人にしか届かないのです。Twitterのつながりは、サービス開発者とユーザーとの関係では無いのです。ぶっちゃけ意味がありません。</p> <p>広告はその問題を解決してくれます。設定により、ダイレクトに自分のサービスをユーザーに届けることができます。しかも流行り廃り関係なく、利用していればいつでも届けてくれます。この効果はとても大きく重要なものです。</p> <h2 id="利用するときのコツ"><a href="#%E5%88%A9%E7%94%A8%E3%81%99%E3%82%8B%E3%81%A8%E3%81%8D%E3%81%AE%E3%82%B3%E3%83%84">利用するときのコツ</a></h2> <p>とはいえ広告です。お金がかかります。大事なことは <code>広告利用料金 < 収益</code> を目指すことです。これができなければひたすら赤字のため意味がありません。つまり、収益化する方法がある、というのは大前提になります。収益化する方法が無いのに広告で集客してもひたすらお金を捨てるだけです。</p> <p>ちなみにこれはすぐに達成できなくても構いません。例えばですが、リリース当初 <code>広告利用料金 > サブスクリプション収益</code> となってしまっていても、毎月の解約率が100%でないかぎり、いつか逆転する可能性があります。そこまで含めて計算しましょう。とはいえ、情勢や見込みユーザーの現象により必ずしもサブスクリプション契約数が毎月同じだけ増えるとは限りません。最近これくらいのスピードで成長してるからだいたいこれくらいには達成できるだろう…という計算はよく裏切られます。その場合は達成できなくなる可能性があります。広告を使う以上、続けるか、終わるか、判断を迫られる時もいつかは来ます。</p> <p>僕のサービスもOpenAIのAPIをつかっているので突然諸々の収益方法が停止されられたりしたらひたすら広告費も含めた経費だけが膨れていくため、すぐに停止しないと大変なことになります。そういったことも想像しておくことが必要です。</p> <p>お金を使って貰う場合は買い切りとサブスクリプションがありますが、これはどちらでも良いと思います。ただ、買い切りの場合はサブスクリプションと違って自動的に継続はされないので、何度も購入してもらえる魅力的な商品を売ってファンになってもらう必要はあると思います。リピーターになってくれる人がほとんどいない買い切りサービスなどは運営は厳しいのではないかと思います(もちろんそれでも収益のほうが高い状態を維持できるのであれば問題ないかもしれませんが)</p> <p>とにかく、毎月しっかり計算をしましょう。</p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>Twitter広告ほんと最高でおすすめです。おじいちゃんになっても使い続けたいサービスです。</p> <p>…とはいえやっぱ最初は高いし実際どうなるかやってみるまでわからないので新しい広告を作る時は怖いと思います。</p> <p>また、途中も色々書きましたがうまく行かないパターンも多いと思います。その場合は最悪の場合サービスを止めたり終了する勇気も必要です。</p> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/17942 2022-01-17T18:28:45+09:00 2022-01-17T18:48:17+09:00 https://crieit.net/posts/inyoo-jp 「引用」をいい感じに展開してくれるサービス「inyoo.jp」をリリースしました <p><a href="https://crieit.now.sh/upload_images/c582b9550ad9d33818f4e53305ff22bd61e53b56e38dc.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/c582b9550ad9d33818f4e53305ff22bd61e53b56e38dc.png?mw=700" alt="image" /></a></p> <p><a target="_blank" rel="nofollow noopener" href="https://inyoo.jp/">inyoo.jp</a> は「引用」をいい感じに展開してくれる短縮 URL サービスです。</p> <p>ウェブの記事などを読んでいて、文章の一部を SNS に「引用」をしたくなったとき、該当箇所を選択して右クリックから「選択箇所へのリンクをコピー」して、</p> <p><a href="https://crieit.now.sh/upload_images/67935bf49b91593abd392ee7c82d06f561e5354a51b5b.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/67935bf49b91593abd392ee7c82d06f561e5354a51b5b.png?mw=700" alt="image" /></a></p> <p>このリンクの URL を <a target="_blank" rel="nofollow noopener" href="https://inyoo.jp/">inyoo.jp</a> のトップページのフォームに貼り付けて「引用を作成」を押します。</p> <p><a href="https://crieit.now.sh/upload_images/884882a729245b2440210c3229a7c2f061e5356a09fbf.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/884882a729245b2440210c3229a7c2f061e5356a09fbf.png?mw=700" alt="image" /></a></p> <p>作成された「引用」を Twitter や Facebook などにシェアすると、こんな感じで引用部分のテキストが画像でいい感じに展開されます!</p> <p><a href="https://crieit.now.sh/upload_images/79a6e1024aa7241cac2bc45fe29eeeca61e5358281ca9.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/79a6e1024aa7241cac2bc45fe29eeeca61e5358281ca9.jpg?mw=700" alt="image" /></a></p> <p>ちなみにこのリンクは開くとページが自動的にスクロールされて、引用の該当箇所がハイライトされるようになっています。説明を読んだだけだと何がなんだか分からないかなと思いますので、ぜひ試しに使ってみてくださいね!</p> <p><a href="https://crieit.now.sh/upload_images/a19f82238ceb31dc74ef1f732740713461e535b70842f.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/a19f82238ceb31dc74ef1f732740713461e535b70842f.png?mw=700" alt="image" /></a></p> <p>また、Google Chrome 以外のブラウザや、スマートフォンからの利用方法など、詳しい使い方は <a target="_blank" rel="nofollow noopener" href="https://inyoo.jp/">トップページ</a> の「使い方」に詳しく書いてありますので、そちらをご参照ください。</p> <h2 id="ここまで読んでくれた方へ"><a href="#%E3%81%93%E3%81%93%E3%81%BE%E3%81%A7%E8%AA%AD%E3%82%93%E3%81%A7%E3%81%8F%E3%82%8C%E3%81%9F%E6%96%B9%E3%81%B8">ここまで読んでくれた方へ</a></h2> <p>ぜひ試しに <a target="_blank" rel="nofollow noopener" href="https://inyoo.jp/">inyoo.jp</a> を使ってみてください!!</p> <p>ご意見や不具合の報告、フィードバックや改善の要望など、何かありましたら Twitter で <a target="_blank" rel="nofollow noopener" href="https://twitter.com/inyoo_jp">@inyoo_jp</a> までメンションか DM をお願いします!また、いいねやコメント、SNS での共有などをしていただけると今後の励みになります。</p> <p>よろしくお願いします!!!</p> inyoo.jp tag:crieit.net,2005:PublicArticle/17787 2021-11-26T13:02:23+09:00 2021-11-26T13:02:23+09:00 https://crieit.net/posts/Twitter-Web 【Twitter連携サービス】連続ツイートをスレッドまるごとダウンロードできるWebサービスを公開しました <p>Twitterを見ていると、とても参考になる連続ツイートをしている人がいらっしゃいます。</p> <p>私はそういうツイートを見つけるとブックマークしておき、後でいつでも見返せるようにしていました。</p> <p>ただ、そういう有益なツイートも、もしツイートしたご本人がツイ消ししたり、アカウント自体が消えてしまったりすると <strong>後で見れなくなってしまいます</strong>。</p> <p>そこで、<strong>連続ツイートをWordファイルとしてダウンロードしておけるWebサービス</strong> を開発いたしました。</p> <p><strong>「ツイスレバックアップ」</strong> というサービスです。<br /> <a target="_blank" rel="nofollow noopener" href="https://twibackup.com/">https://twibackup.com/</a></p> <h1 id="作ったきっかけ"><a href="#%E4%BD%9C%E3%81%A3%E3%81%9F%E3%81%8D%E3%81%A3%E3%81%8B%E3%81%91">作ったきっかけ</a></h1> <p>個人開発者界隈で有名(と私は勝手に思ってる)な方が、先日個人開発について参考になる連続ツイートをされていました。</p> <p><a target="_blank" rel="nofollow noopener" href="https://twitter.com/sesere115/status/1459167602094133254">https://twitter.com/sesere115/status/1459167602094133254</a></p> <p>「ヒトリッター」というサービスを土日に開発しながら、サービスをリリースしていく中で考えるべきことを連続ツイートする内容です。</p> <p>定期的に読み返したいような内容だったのですが、ふと「もしせせりさんがツイ消ししたら見れなくなるよな…」「念のため手元にずっと残しておきたいな」と思いました。</p> <p>そこで、連続ツイートをスレッドまるごとダウンロードする、つまり手元にバックアップしておくためのサービス「ツイスレバックアップ」を開発しようと思い立ちました。<br /> (※せせりさんはよくツイ消しする人、という意味ではありません。)</p> <h1 id="なぜWordファイル?"><a href="#%E3%81%AA%E3%81%9CWord%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%EF%BC%9F">なぜWordファイル?</a></h1> <p>PDFファイルでのダウンロードも考えたのですが、以下の理由によりWordファイルでのダウンロードにしました。</p> <ul> <li>画像もWordファイル内に入れ込めるので便利。(これはPDFでも出来ますが)</li> <li>あとで自分でメモを追記できる方が便利。(PDFだとメモしずらい)</li> <li>なんならそのままマニュアル化しても良い。</li> </ul> <h1 id="このサービスを使うと何が嬉しい?"><a href="#%E3%81%93%E3%81%AE%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%82%92%E4%BD%BF%E3%81%86%E3%81%A8%E4%BD%95%E3%81%8C%E5%AC%89%E3%81%97%E3%81%84%EF%BC%9F">このサービスを使うと何が嬉しい?</a></h1> <p>このサービスを使うと、やはり一番は <strong>「いつ連続ツイートが消える(見れなくなる)か分からない不安」</strong> から開放されるのが嬉しいですね。<br /> 自分で開発して、自分で使ってます 笑</p> <p>あと、Wordファイルでダウンロードしておけば仲間内での共有等も格段にしやすくなりますね。</p> <h1 id="今後の展開"><a href="#%E4%BB%8A%E5%BE%8C%E3%81%AE%E5%B1%95%E9%96%8B">今後の展開</a></h1> <p>今後はPDFファイルでのダウンロード等、ニーズに合わせて機能追加していきたいと思います。</p> <p>なにか欲しい機能があれば教えてください!</p> hoku tag:crieit.net,2005:PublicArticle/17599 2021-08-14T13:55:16+09:00 2021-08-14T13:55:16+09:00 https://crieit.net/posts/how-to-restore-colors-of-web-twitter WEB版Twitterの色を元に戻すぞ <p>Twitterの仕様がまた変更されました。個人的には頻繁に仕様を変えて色々とっかえひっかえ試していき、より良いものを追求するという姿勢自体には賛成ですが、今回の仕様変更はちょっと困るなあ、という感じです。</p> <p>今回の仕様変更は配色の変更です。これはとても困ります。特に白と黒逆やろ!と思います。絶対間違えて誤フォローや誤フォロー解除が発生しまくるでしょう。なので、私の環境ではWEB版だけでも戻します。内容としては<a target="_blank" rel="nofollow noopener" href="https://boyi.sh/2021/04/18/hide-trends-and-topics-on-web-twitter/">WEB版Twitterのトレンドとかトピックを非表示にする</a>と同じようなことをします。</p> <p>使うのは<a target="_blank" rel="nofollow noopener" href="https://chrome.google.com/webstore/detail/stylebot/oiaejidbmkiecgbjeifoejpgmdaleoha?hl=ja">Stylebot</a>という拡張機能。分かる人向けに書くと、サイトごとに独自のCSSを記述して上書きして見た目を好きなように変更したり、見たくない要素を非表示にしたりできるというものです。</p> <p>Chromeの拡張機能ですが、対応するブラウザで使えます。私の場合はBraveというブラウザでも問題なく使用できています。Braveに関して興味があれば<a target="_blank" rel="nofollow noopener" href="https://boyi.sh/2021/06/02/changed-my-main-browser-from-chrome-to-brave/">メインブラウザをChromeからBraveに変えた話</a>をご覧ください。</p> <p>Stylebotで以下のコードを貼り付けてください。色は好みに合わせて自由にカスタマイズできます。詳しくはHTMLカラーコードなどでググってください。</p> <pre><code class="css">/* フォロー中の背景色 */ div.css-18t94o4.css-1dbjc4n.r-1niwhzg.r-1ccsd61.r-sdzlij.r-1phboty.r-rs99b7.r-15ysp7h.r-4wgw6l.r-1ny4l3l.r-ymttw5.r-o7ynqc.r-6416eg.r-lrvibr { background-color: #09f; } /* 保存の背景色 */ div.css-18t94o4.css-1dbjc4n.r-14lw9ot.r-42olwf.r-sdzlij.r-1phboty.r-rs99b7.r-16y2uox.r-6gpygo.r-1b7u577.r-peo1c.r-1ps3wis.r-1ny4l3l.r-1udh08x.r-1guathk.r-1udbk01.r-o7ynqc.r-6416eg.r-lrvibr.r-3s2u2q.r-1glkqn6 { background-color: #09f; } /* 保存の文字色 */ div.css-18t94o4.css-1dbjc4n.r-14lw9ot.r-42olwf.r-sdzlij.r-1phboty.r-rs99b7.r-16y2uox.r-6gpygo.r-1b7u577.r-peo1c.r-1ps3wis.r-1ny4l3l.r-1udh08x.r-1guathk.r-1udbk01.r-o7ynqc.r-6416eg.r-lrvibr.r-3s2u2q.r-1glkqn6 div { color: white } /* 未フォローの背景色 */ div.css-18t94o4.css-1dbjc4n.r-14lw9ot.r-42olwf.r-sdzlij.r-1phboty.r-rs99b7.r-15ysp7h.r-4wgw6l.r-1ny4l3l.r-ymttw5.r-o7ynqc.r-6416eg.r-lrvibr { background-color: transparent; border: solid 2px white; } /* 未フォローの文字色 */ div.css-18t94o4.css-1dbjc4n.r-14lw9ot.r-42olwf.r-sdzlij.r-1phboty.r-rs99b7.r-15ysp7h.r-4wgw6l.r-1ny4l3l.r-ymttw5.r-o7ynqc.r-6416eg.r-lrvibr div{ color: white; } </code></pre> <p>今回行ったのは、開発者モードで該当の要素を見つけ出してStylebotで設定の付与です。Stylebotでも要素選択は可能なのですが、開発者モードで行った方が小回りがきいてやりやすいです。</p> <p>本来は共通クラスのみ抜き出して少ない記述でやりたかったのですが、あまりにも面倒くさくてベタ書きしました。</p> <p>上記コードはTwiterの仕様変更で無効になる可能性がありますが、その時はまた同じことをすればいいです。</p> <p>それでは素敵なTwitterライフを!</p> あぱしょに tag:crieit.net,2005:PublicArticle/17071 2021-05-08T00:09:10+09:00 2021-05-08T00:09:10+09:00 https://crieit.net/posts/Twitter-Evernote-Obsidian TwitterのツイートログをEvernote経由でObsidianに落とし込んでいる手順 <p>やりたいことはつまりTwitterのログをObsidianで保存したいということなのだが、なぜEvernoteを経由という無駄っぽいことをしているかと言えば、元々Evernoteでツイートを管理していたからだ。個人的にはもはやEvernoteを使うことはなく、以前のログ置き場にしかなっていない。元々のやり方を踏襲しているのでEvernoteを経由しているだけで、別にEvernoteを経由する意味は本質的にない。</p> <p>が、さすがに老舗のサービスだけあって、連携したサービスやソフトも多くあるので、それらを使うことで、プログラミングをすることなく、TwitterのログをObsidianに保存するという目的は達成できたので、とりあえず今はそのようにしている。この記事は、その手順のメモである。そのうちにはEvernote部分を取っ払いたい。</p> <h2 id="ツイエバでTwitter→Evernote"><a href="#%E3%83%84%E3%82%A4%E3%82%A8%E3%83%90%E3%81%A7Twitter%E2%86%92Evernote">ツイエバでTwitter→Evernote</a></h2> <p>まずTwitter→Evernoteだが、これは「<a target="_blank" rel="nofollow noopener" href="http://twieve.net/ja">ツイエバ – TwitterのツイートやメンションをEvernoteとEmailへ</a>」のサービスを利用している。</p> <p>なぜツイエバを使っているのかと言えば、もともとEvernoteに情報を集約させていたからだ。しかし、昨今あまりにもEvernoteが使いづらくなり、先行きも不安しかなく、正直もはやEvernoteを経由する意味はない。</p> <p>まぁでも、老舗なだけあってなお種々のサービスとの連携があるのは強みかもしれない。</p> <h2 id="evernote2mdでEvernote→Obsidian"><a href="#evernote2md%E3%81%A7Evernote%E2%86%92Obsidian">evernote2mdでEvernote→Obsidian</a></h2> <p>さて、問題はEvernoteからどうやってObsidianにするか。だが、<strong>これはつまりEvernoteのノートをいかにしてmarkdown形式に変換するか、という問いに等しい</strong>。</p> <p>まずEvernoteのノートをローカルに落とすには、Evernoteのデスクトップアプリで、普通に「エクスポート」を使って.enex形式で落とせば良い。いつの間にか、50個までしかまとめてエクスポートできなくなっていた……そういうことするから、みんな離れていくんやで……。</p> <p>問題は.enexをどうやって.mdに変換するかだが。</p> <p>これはGitHubで公開されている「<a target="_blank" rel="nofollow noopener" href="https://github.com/wormi4ok/evernote2md">wormi4ok/evernote2md: Convert Evernote .enex files to Markdown</a>」がもっとも楽だと思う。</p> <p>インストールはREADME.mdにあるとおり、Macなら</p> <pre><code>brew install evernote2md </code></pre> <p>でOK。</p> <p>で、使い方は、以下。</p> <pre><code>evernote2md <エクスポートした.enex> </code></pre> <p>これを実行すると、notesディレクトリができていて、その下にmd形式に変換されたノートがある。あとは、これをObsidianのユーザーディレクトリ下に打ち込むだけ。</p> <p>これでけっこううまいこと変換される。</p> <p><img src="https://hack-le.com/wp-content/uploads/2021/04/974761fbbb0af2b29c4eb96a730c83e0-700x555.jpg" alt="" /></p> <p>うん。満足。</p> <h2 id="着々とEvernoteからの乗り換えが進む"><a href="#%E7%9D%80%E3%80%85%E3%81%A8Evernote%E3%81%8B%E3%82%89%E3%81%AE%E4%B9%97%E3%82%8A%E6%8F%9B%E3%81%88%E3%81%8C%E9%80%B2%E3%82%80">着々とEvernoteからの乗り換えが進む</a></h2> <p>既にメモ用途としてはほとんどObsidianにうつっている。</p> <p>Web Clipさえも、最近はnoteを使っているが、しかしWeb Clip、なんだかもう使わなくなっちゃったな……。</p> <p>Evernote、もうダメかもなぁ。昨年末の大改造は大不評だった。みんな、課金体系とかじゃなくて、アプリの安定性や機能に文句を言っていた。僕もそう。</p> <p>以前はプレミアム会員だったこともあるのだけれどね。。。</p> tama tag:crieit.net,2005:PublicArticle/16860 2021-04-18T21:20:18+09:00 2021-04-18T21:20:18+09:00 https://crieit.net/posts/hide-trends-and-topics-on-web-twitter WEB版Twitterのトレンドとかトピックを非表示にする <p>TwitterのWEB版では、右側に「<strong>いまどうしてる?</strong>」(トレンド)や「<strong>おすすめユーザー</strong>」「<strong>おすすめトピック</strong>」などが表示されています。</p> <p><a href="https://crieit.now.sh/upload_images/65effe08f1a79b8612a3b12b55d32f4d607c236e54886.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/65effe08f1a79b8612a3b12b55d32f4d607c236e54886.jpg?mw=700" alt="001" /></a></p> <p>トレンドはともかく、トピックに関しては全然使っていないのでハッキリいって邪魔なんですよね。そもそも機能として不要と思うのですが、それに加えてフォローしていないトピックも突然TLに出てきて本当に邪魔です。不必要な情報が視界に入るとイライラするので消してしまいます。</p> <p>今回はGoogle Chromeで拡張機能を使用しますが、Microsoft EdgeやFirefoxなどもChromeの拡張機能を使用できるので、同じ方法で対応できます。</p> <p>使用する拡張機能は「Stylebot」という拡張機能です。</p> <p>手順は次の通りです。</p> <ol> <li>拡張機能「Stylebot」をインストール</li> <li>非表示にしたい部分を選択して非表示設定</li> </ol> <p>簡単ですね。</p> <p>まずは、<a target="_blank" rel="nofollow noopener" href="https://chrome.google.com/webstore/detail/stylebot/oiaejidbmkiecgbjeifoejpgmdaleoha?hl=ja">こちら</a>からアクセスし、「Chromeに追加」をクリックして有効化してください。</p> <p><a href="https://crieit.now.sh/upload_images/cd98267a2f4eaad828b5094c23951f48607c23831136c.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/cd98267a2f4eaad828b5094c23951f48607c23831136c.jpg?mw=700" alt="002" /></a></p> <p>次に、Twitterを開いて、拡張機能より「Stylebot」を開きます。</p> <p><a href="https://crieit.now.sh/upload_images/363a77cfcbdd967c9a726185ca7380c6607c2398049a2.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/363a77cfcbdd967c9a726185ca7380c6607c2398049a2.jpg?mw=700" alt="003" /></a></p> <p><a href="https://crieit.now.sh/upload_images/176a09eea04e0c7b6a7f69a5f8eb16f5607c23a6b42d0.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/176a09eea04e0c7b6a7f69a5f8eb16f5607c23a6b42d0.jpg?mw=700" alt="004" /></a></p> <p>すると、次のような画面が開きます。</p> <p><a href="https://crieit.now.sh/upload_images/b88cad9daa78e0e5db8aaca4192ee206607c23c0ed61a.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/b88cad9daa78e0e5db8aaca4192ee206607c23c0ed61a.jpg?mw=700" alt="005" /></a></p> <p>非表示にしたい項目を選択して、「レイアウト」の「隠す」を選択します。</p> <p><a href="https://crieit.now.sh/upload_images/196fd4c4281c826ce76ebfd172f11217607c23ce5bd48.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/196fd4c4281c826ce76ebfd172f11217607c23ce5bd48.jpg?mw=700" alt="006" /></a><br /> <a href="https://crieit.now.sh/upload_images/e5901628e902ce86a1c4dde554e40e23607c23d812d45.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/e5901628e902ce86a1c4dde554e40e23607c23d812d45.jpg?mw=700" alt="007" /></a></p> <p>選択した「<strong>いまどうしてる?</strong>」が非表示になりました。</p> <p><a href="https://crieit.now.sh/upload_images/f4c4eee5776e9e0de0e5957a8bcad0ce607c23e427211.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/f4c4eee5776e9e0de0e5957a8bcad0ce607c23e427211.jpg?mw=700" alt="008" /></a></p> <p>同様に他の項目を選択すると、スッキリしました。</p> <p><a href="https://crieit.now.sh/upload_images/a30e5e8f27015c7c3c311d898a99c7fb607c23f209ae8.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/a30e5e8f27015c7c3c311d898a99c7fb607c23f209ae8.jpg?mw=700" alt="009" /></a></p> <p>この「Stylebot」は他にも文字の色などもカスタマイズすることができます。自分好みのTwitter環境にして、素敵なツイ廃ライフをどうぞ!</p> <p>ちなみに、手っ取り早く「<strong>いまどうしてる?</strong>」「<strong>おすすめユーザー</strong>」「<strong>おすすめトピック</strong>」を非表示にする場合、「Stylebot」の下の<code><> コード</code>のところに以下をコピペしてください。</p> <p>なお、Twitterの仕様などにより無効になったりレイアウトが崩れたりする可能性があります。</p> <pre><code class="css">.css-1dbjc4n.r-1ysxnx4.r-k0dy70.r-1867qdf.r-1phboty.r-rs99b7.r-1ifxtd0.r-1udh08x { display: none; } </code></pre> あぱしょに tag:crieit.net,2005:PublicArticle/16069 2020-09-24T09:55:18+09:00 2020-09-24T10:58:25+09:00 https://crieit.net/posts/Windows-5f6bee763d7a0 Windowsでスクショをツイートする個人的に楽な方法 <p>普段何気なく行っていましたが、WindowsでスクショをとってTwitterやSlackに画像を投稿するので一番簡単で慣れている方法があるのでせっかくなので共有してみます。ちなみにこの記事にも同様の方法で貼っています。</p> <h2 id="追記"><a href="#%E8%BF%BD%E8%A8%98">追記</a></h2> <p>この記事を書いた後にWin+Shift+Sを教えてもらいました。とても簡単でおすすめです!</p> <p>おわり</p> <h2 id="スクショをとる"><a href="#%E3%82%B9%E3%82%AF%E3%82%B7%E3%83%A7%E3%82%92%E3%81%A8%E3%82%8B">スクショをとる</a></h2> <p>まずはスクショキーを無心でポチッと押してスクショを取ります。</p> <h2 id="ペイント3Dで切り取る"><a href="#%E3%83%9A%E3%82%A4%E3%83%B3%E3%83%883D%E3%81%A7%E5%88%87%E3%82%8A%E5%8F%96%E3%82%8B">ペイント3Dで切り取る</a></h2> <p>加工はペイント3Dで行います。</p> <h3 id="ペイント3Dを開く"><a href="#%E3%83%9A%E3%82%A4%E3%83%B3%E3%83%883D%E3%82%92%E9%96%8B%E3%81%8F">ペイント3Dを開く</a></h3> <p>ペイント3Dをちゃちゃっと開きます。しょっちゅう使うのでタスクバーかスタートメニューに登録しておくと楽です。</p> <p><a href="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5f6bebebc539b.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5f6bebebc539b.png?mw=700" alt="" /></a></p> <h3 id="貼り付ける"><a href="#%E8%B2%BC%E3%82%8A%E4%BB%98%E3%81%91%E3%82%8B">貼り付ける</a></h3> <p>開いたらそのままCtrl+Vで貼り付けます。</p> <p><a href="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5f6bec9318a22.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5f6bec9318a22.png?mw=700" alt="" /></a></p> <h3 id="範囲指定する"><a href="#%E7%AF%84%E5%9B%B2%E6%8C%87%E5%AE%9A%E3%81%99%E3%82%8B">範囲指定する</a></h3> <p>トリミングしたい部分を範囲指定します。貼り付けた状態ですでに全体が範囲指定されているので、一旦画像の外をクリックして範囲を解除してから範囲指定します。</p> <p><a href="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5f6bed0c00e13.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5f6bed0c00e13.png?mw=700" alt="" /></a></p> <p>あとはトリミングボタンを押すとトリミングされます。</p> <p><a href="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5f6bed2b4bb86.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5f6bed2b4bb86.png?mw=700" alt="" /></a></p> <p>トリミングできました。</p> <p><a href="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5f6bed9eaf802.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5f6bed9eaf802.png?mw=700" alt="" /></a></p> <p>一応キャンバスボタンでサイズを見てみます。</p> <p><a href="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5f6bedbec7eec.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5f6bedbec7eec.png?mw=700" alt="" /></a></p> <p>ちょっと大きすぎるので横幅600くらいまで縮小しても良いかもしれません。Ctrl+Aで全体選択し、Shiftを押しながらサイズ調整すると縦横比を維持したままリサイズできます。リサイズ後は再度トリミングで余白を切り取ります。</p> <h2 id="コピーする"><a href="#%E3%82%B3%E3%83%94%E3%83%BC%E3%81%99%E3%82%8B">コピーする</a></h2> <p>トリミングやリサイズが完了したら、Ctrl+Aで全選択、Ctrl+Cでクリップボードにコピーします。これでもうTwitterなどには貼り付けられる状態です。</p> <h2 id="貼り付ける"><a href="#%E8%B2%BC%E3%82%8A%E4%BB%98%E3%81%91%E3%82%8B">貼り付ける</a></h2> <p>ツイート欄を出してからCtrl+Vで貼り付けるだけです。ファイルに保存したりもしなくていいので後片付けも気にしなくても良いです。すごく楽です!</p> <p><a href="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5f6bee3909698.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5f6bee3909698.png?mw=700" alt="" /></a></p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>とにかくディスクも汚さないし一瞬でスクショを投稿できるのですごく早くて楽です。ぜひ試してみてください。</p> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/15888 2020-05-08T19:25:38+09:00 2020-05-08T19:25:38+09:00 https://crieit.net/posts/b3c547c34df6393a1f86f8072aaf510a アニメのレコメンドサービスを作りました。 <p><a href="https://crieit.now.sh/upload_images/b0f24117575660588f657c26c91d370e5eb525f79a7b2.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/b0f24117575660588f657c26c91d370e5eb525f79a7b2.png?mw=700" alt="" /></a></p> <h1 id="サービスURL"><a href="#%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9URL">サービスURL</a></h1> <p><a target="_blank" rel="nofollow noopener" href="https://annict-suggest.netlify.app/">https://annict-suggest.netlify.app/</a></p> <p><a href="https://crieit.now.sh/upload_images/102e92a702e89059033b1dff5b0f87c55eb5336deb10d.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/102e92a702e89059033b1dff5b0f87c55eb5336deb10d.jpg?mw=700" alt="" /></a></p> <h1 id="アニメの類似性をどう計算するか"><a href="#%E3%82%A2%E3%83%8B%E3%83%A1%E3%81%AE%E9%A1%9E%E4%BC%BC%E6%80%A7%E3%82%92%E3%81%A9%E3%81%86%E8%A8%88%E7%AE%97%E3%81%99%E3%82%8B%E3%81%8B">アニメの類似性をどう計算するか</a></h1> <h2 id="コサイン類似度"><a href="#%E3%82%B3%E3%82%B5%E3%82%A4%E3%83%B3%E9%A1%9E%E4%BC%BC%E5%BA%A6">コサイン類似度</a></h2> <p>人工知能を使わずにアニメのレコメンドサービスを作ろうと思ったのがきっかけです。<br /> <a href="https://crieit.net/posts/5308d8a3ed140ecc15e1310dad28e9e9">ユークリッド距離は触ったことがある</a>ので、他の指標としてコサイン類似度が面白そうだと思いました。<br /> ユークリッド距離は2点間の距離、コサイン類似度は2点のベクトル同士の角度です。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://www.albert2005.co.jp/knowledge/data_mining/cluster/cluster_summary">クラスター分析の手法①(概要)</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/tetsutaroendo/items/61942d25ae2a017831f2">コサイン類似度を利用し、集団の類似性を測ってみる</a></li> </ul> <h3 id="使った指標"><a href="#%E4%BD%BF%E3%81%A3%E3%81%9F%E6%8C%87%E6%A8%99">使った指標</a></h3> <p>約3300の作品に対して「見た」「見てない」のベクトルを作ってコサイン類似度を算出しようとしました。<br /> 「あにこれ」のように成分分析されているタグの類似度を計算するのもありだと思いました。</p> <h3 id="挫折"><a href="#%E6%8C%AB%E6%8A%98">挫折</a></h3> <p>導入は比較的楽なように思えたのですが、計算量が尋常ではありませんでした。事前にフィルタリングを何もかけていなかったため、3300レコードx3300レコードの計算をしようとしていて、あまりに時間がかかるので諦めました。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/harperfu6/items/3238d8f78c8a8d8cf114">アイテムの類似性について考察してみる</a></li> </ul> <h3 id="結局SQL"><a href="#%E7%B5%90%E5%B1%80SQL">結局SQL</a></h3> <p>例:ID4342の作品を見たユーザを抽出して、<br /> それらのユーザが他に見た作品のうち共通している人数が多い順に30件を取得する。</p> <pre><code class="sql">select sum(st2.watch_status) as count, st2.work_id, w.title from status st2 -- ID:4342の作品を見たユーザを取得 inner join ( select st.user_id from status st where st.work_id = 4342 )st3 on st2.user_id = st3.user_id inner join works w on w.annict_id = st2.work_id -- 作品自身を除く where st2.work_id != 4342 group by st2.work_id order by count desc limit 30 </code></pre> <h1 id="今回使った技術"><a href="#%E4%BB%8A%E5%9B%9E%E4%BD%BF%E3%81%A3%E3%81%9F%E6%8A%80%E8%A1%93">今回使った技術</a></h1> <ul> <li>GraphQL(Annict API)</li> <li>ReactJS</li> <li>NodeJS(TypeScript)</li> <li>twitterAPI</li> <li>netlify</li> </ul> <h2 id="データの棲み分け"><a href="#%E3%83%87%E3%83%BC%E3%82%BF%E3%81%AE%E6%A3%B2%E3%81%BF%E5%88%86%E3%81%91">データの棲み分け</a></h2> <p>最新のデータが欲しい場合はAnnictAPI(GraphQL)、分析データが欲しい場合はDBから読み込み、というようにデータの棲み分けを行っています。</p> <h2 id="GraphQLでエイリアスを使う"><a href="#GraphQL%E3%81%A7%E3%82%A8%E3%82%A4%E3%83%AA%E3%82%A2%E3%82%B9%E3%82%92%E4%BD%BF%E3%81%86">GraphQLでエイリアスを使う</a></h2> <p><a target="_blank" rel="nofollow noopener" href="https://developers.annict.jp/graphql-api/reference/">Annict API</a><br /> ユーザが見たアニメと見ているアニメ両方が欲しい場合、<br /> エイリアスを使うと複数条件が記述できる。</p> <pre><code class="javascript">query { user(username:"${username}"){ annictId, works(state:WATCHED){ nodes{ annictId title } } ing:works(state:WATCHING){ nodes{ annictId title } } } } </code></pre> <h2 id="CSP(コンテンツセキュリティポリシー)"><a href="#CSP%28%E3%82%B3%E3%83%B3%E3%83%86%E3%83%B3%E3%83%84%E3%82%BB%E3%82%AD%E3%83%A5%E3%83%AA%E3%83%86%E3%82%A3%E3%83%9D%E3%83%AA%E3%82%B7%E3%83%BC%29">CSP(コンテンツセキュリティポリシー)</a></h2> <p>アニメのOGPがない場合は公式twitterアカウントの画像を使用しているが、CSPなどで同じサイトでないコンテンツは表示できなくなったので、<br /> URLに「twitter」が含まれる場合はサーバにプロキシさせて画像を読み込むようにした。</p> <h1 id="ご意見"><a href="#%E3%81%94%E6%84%8F%E8%A6%8B">ご意見</a></h1> <p><a target="_blank" rel="nofollow noopener" href="https://ikens.net/ckoshien_tech/annict-suggest?v=1">こちら</a>から使ってみた感想・ご意見をお寄せください。</p> ckoshien tag:crieit.net,2005:PublicArticle/15828 2020-04-14T20:37:13+09:00 2020-04-14T20:37:13+09:00 https://crieit.net/posts/React-NodeJS-Passport-twitter React/NodeJS/Passportでtwitterログインを実装してみた <p>こちらの記事をベースにしてReactJS/NodeJSのシステムにtwitterログインを組み込んでみた。<br /> <a target="_blank" rel="nofollow noopener" href="https://qiita.com/itagakishintaro/items/e5a0481b51e6a17b304c">https://qiita.com/itagakishintaro/items/e5a0481b51e6a17b304c</a></p> <p>主に異なるのは型指定が緩めな<code>なんちゃって</code>typescriptを使っているところか。</p> <h1 id="実装したもの"><a href="#%E5%AE%9F%E8%A3%85%E3%81%97%E3%81%9F%E3%82%82%E3%81%AE">実装したもの</a></h1> <h2 id="蓋々交換機能"><a href="#%E8%93%8B%E3%80%85%E4%BA%A4%E6%8F%9B%E6%A9%9F%E8%83%BD">蓋々交換機能</a></h2> <p><a target="_blank" rel="nofollow noopener" href="https://cap-baseball.com/cap_exchange">https://cap-baseball.com/cap_exchange</a></p> <h1 id="技術的なこと"><a href="#%E6%8A%80%E8%A1%93%E7%9A%84%E3%81%AA%E3%81%93%E3%81%A8">技術的なこと</a></h1> <h2 id="passport-config.ts"><a href="#passport-config.ts">passport-config.ts</a></h2> <pre><code class="javascript">export default function passportConfig() { var TWITTER_CONSUMER_KEY = "*****"; var TWITTER_CONSUMER_SECRET = "*******"; var passport = require("passport"), TwitterStrategy = require("passport-twitter").Strategy; // Sessionの設定 passport.serializeUser(function (user, done) { done(null, user); }); passport.deserializeUser(function (obj, done) { done(null, obj); }); passport.use( new TwitterStrategy( { consumerKey: TWITTER_CONSUMER_KEY, consumerSecret: TWITTER_CONSUMER_SECRET, callbackURL: "https://********/auth/twitter/callback", }, function (token, tokenSecret, profile, done) { passport.session.user = profile; // tokenとtoken_secretをセット profile.twitter_token = token; profile.twitter_token_secret = tokenSecret; process.nextTick(function () { return done(null, profile); }); } ) ); } </code></pre> <h2 id="auth.ts"><a href="#auth.ts">auth.ts</a></h2> <p>NodeJSでtwitter認証からのコールバックなどを担当するコントローラ。</p> <pre><code class="javascript">import * as express from "express"; import * as session from 'express-session'; import { Request } from "./interface/express.Request"; const passport = require("passport"); export class Auth{ public router: express.Router; constructor() { this.router = express.Router(); this.router.get("/twitter", passport.authenticate('twitter')); this.router.get("/twitter/success", this.success); this.router.get("/twitter/callback", passport.authenticate('twitter', { successRedirect: 'https://******/api/v2/auth/twitter/success', failureRedirect: 'https://******/' }) )} private success(req:Request,res:express.Response):void{ if(req.session.passport !== undefined){ res.json(req.session.passport.user.username); }else{ res.sendStatus(401); } } } </code></pre> <h2 id="express.Requestインターフェースの拡張"><a href="#express.Request%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%BC%E3%83%95%E3%82%A7%E3%83%BC%E3%82%B9%E3%81%AE%E6%8B%A1%E5%BC%B5">express.Requestインターフェースの拡張</a></h2> <pre><code class="javascript">import * as Express from 'express'; export interface Request extends Express.Request { session:any; } </code></pre> <h2 id="app.ts"><a href="#app.ts">app.ts</a></h2> <pre><code class="javascript">import { Auth } from './auth'; import * as session from 'express-session'; import passportConfig from './passport-config'; passportConfig(); const passport = require("passport"); app.use(passport.initialize()); app.use(passport.session()); app.use( session({ secret: '********', resave: false, saveUninitialized: false, cookie:{ httpOnly: true, secure: true, maxage: 1000 * 60 * 30 } }) ); </code></pre> <h2 id="ReactでNodeJS/Passportから認証情報を受け取る"><a href="#React%E3%81%A7NodeJS%2FPassport%E3%81%8B%E3%82%89%E8%AA%8D%E8%A8%BC%E6%83%85%E5%A0%B1%E3%82%92%E5%8F%97%E3%81%91%E5%8F%96%E3%82%8B">ReactでNodeJS/Passportから認証情報を受け取る</a></h2> <p>credentialsオプションが必要。</p> <pre><code class="javascript"> const response = await fetch('/auth/twitter/success', { method:'GET', credentials: "include", headers: { Accept: "application/json", "Content-Type": "application/json", "Access-Control-Allow-Credentials": true } }); </code></pre> <h2 id="認証が終わったタイミングで認証情報を受け取る"><a href="#%E8%AA%8D%E8%A8%BC%E3%81%8C%E7%B5%82%E3%82%8F%E3%81%A3%E3%81%9F%E3%82%BF%E3%82%A4%E3%83%9F%E3%83%B3%E3%82%B0%E3%81%A7%E8%AA%8D%E8%A8%BC%E6%83%85%E5%A0%B1%E3%82%92%E5%8F%97%E3%81%91%E5%8F%96%E3%82%8B">認証が終わったタイミングで認証情報を受け取る</a></h2> <p>認証のためのウインドウを開き、<br /> そのウインドウが閉じられたタイミングで親の画面をリロードする。<br /> リロードの際に認証情報を受け取っている。</p> <pre><code class="javascript">onClick={()=>{ const authWindow = window.open('/auth/twitter','newTab'); var timer = setInterval(function() { if(authWindow.closed) { clearInterval(timer); window.location.reload(); } }, 1000); <span>}</span><span>}</span> </code></pre> ckoshien tag:crieit.net,2005:PublicArticle/15701 2020-01-29T07:32:39+09:00 2020-01-29T22:37:21+09:00 https://crieit.net/posts/Twitter-5e30b687e9491 インフルエンサーも教えてくれないTwitterでフォロワーを増やす方法 <p>個人でサービスやアプリを作ったりする人は年々増えていると思いますが、それらのリリースのあとに問題となるのが「集客」です。その解決の一つとしてTwitterアカウントで運用を行っている方も多いのではないかと思います。また、業務でもマーケティングのためにTwitterアカウントを運用されている方、もしくは会社も増えていると思います。</p> <p>その際に問題となるのが「フォロワー数」です。効率よく集客を含めたマーケティングを行うためには一度の発言でなるべく多くのかたに声が届くようにできる方が望ましいでしょう。そのフォロワー数の増やし方を考察してみました。</p> <p><strong>当記事は有料記事です。記事の後半は有料となっています。</strong></p> <h2 id="記事を書いている人は?"><a href="#%E8%A8%98%E4%BA%8B%E3%82%92%E6%9B%B8%E3%81%84%E3%81%A6%E3%81%84%E3%82%8B%E4%BA%BA%E3%81%AF%EF%BC%9F">記事を書いている人は?</a></h2> <p>僕はしがない一般人で、個人でWebサービスを色々作ったりしています。Twitterのフォロワー数は現時点でもうすぐ2千になるという感じで、はっきり言ってしまうと「別にフォロワー数は多いというわけでもないし、増える早さもめちゃくちゃ遅い」です。</p> <p>それどころか現在はフォロワーを増やそうという気も全くありませんし、何も対策なども行っていません。</p> <p>もちろんTwitterで活動をはじめた頃はフォロワーを増やせば増やすほどサービスへの集客もやりやすくなるしいいことづくめだし頑張ろう! とやっていました。しかし頑張ってもそんなに増えるもんでもないですし、頑張ってるその横で他の人はもっととんでもない早さでフォロワー数を増やしていきます。</p> <p>そのため途中からやる気を失いました。というか、その早くフォロワー数を増やしている人たちを見ることで増やし方がなんとなく分かってきたのですが、はっきりいってやってられないと思いました。なぜかというと、大変すぎるのです。</p> <p>僕はものぐさだし、そもそもやりたいのはサービスを作って活性化させることです。別に根本的な話を考えるとフォロワー数を増やしたいわけではありません。そのためフォロワーを増やすことに時間も費やしたくないですし、そんな大変なことをいちいちやってられないという結論に至りました。もしやったとしても嫌になって途中でやめてしまってどのみち今のようなのんびり気ままスタイルに変えてしまうでしょう。</p> <p>ということで、僕自身はフォロワーを増やすことが得意でもなんでもない単なる一般人です。</p> <h2 id="じゃあ一体何を書く気なのか?"><a href="#%E3%81%98%E3%82%83%E3%81%82%E4%B8%80%E4%BD%93%E4%BD%95%E3%82%92%E6%9B%B8%E3%81%8F%E6%B0%97%E3%81%AA%E3%81%AE%E3%81%8B%EF%BC%9F">じゃあ一体何を書く気なのか?</a></h2> <p>そんな人間がどうやってフォロワー数を増やす方法を書くのかというと、前述の通りまわりのフォロワー数が多い方やフォロワー数の増え方が早い方を色々と見てきたので、そこから得たことをヒントとして色々と考察したことを書こうと思います。</p> <p>そのため実践した方法でもないですし本当に役立つ情報なのかは不明です。ですので有料部分の価格は100円ですし、まあ一応暇つぶしで見てみようか、と思った方だけ購入していただければいいかなと思って書きました。</p> <h3 id="有名な方々が書いている増やし方との違い"><a href="#%E6%9C%89%E5%90%8D%E3%81%AA%E6%96%B9%E3%80%85%E3%81%8C%E6%9B%B8%E3%81%84%E3%81%A6%E3%81%84%E3%82%8B%E5%A2%97%E3%82%84%E3%81%97%E6%96%B9%E3%81%A8%E3%81%AE%E9%81%95%E3%81%84">有名な方々が書いている増やし方との違い</a></h3> <p>ただ、前述の通り地道なやり方をメインに書いていくため、恐らくインフルエンサー等がよく書いている「フォロワー数のふやし方!」的な記事にはあまり書かれてはいないような方法なのではないかと思います。</p> <h3 id="インフルエンサーのフォロワー数のふやし方は?"><a href="#%E3%82%A4%E3%83%B3%E3%83%95%E3%83%AB%E3%82%A8%E3%83%B3%E3%82%B5%E3%83%BC%E3%81%AE%E3%83%95%E3%82%A9%E3%83%AD%E3%83%AF%E3%83%BC%E6%95%B0%E3%81%AE%E3%81%B5%E3%82%84%E3%81%97%E6%96%B9%E3%81%AF%EF%BC%9F">インフルエンサーのフォロワー数のふやし方は?</a></h3> <p>そもそもフォロワー数が多い人の中にはフォロワー数が多くなってからさらに増えた方法が書かれている場合もあったりします。そういうのははっきり言って普通に人には無益でしょう。</p> <p>そうではなく、フォロワー数何人までの間はこういった方法で増やして、その後の何人までの間はこう、といった感じで書いてくれている方もいたりします。まだそちらの方がマシだとは思います。</p> <p>ただ、個人的にはそのあたりもあまり有益には感じません。というのも、彼らはそういった方法でフォロワー数をふやしているわけではないと思うからです。具体的に言うと、下記の2パターンのふやし方がメインなのではないかと思います。</p> <h4 id="裏で色々やっている"><a href="#%E8%A3%8F%E3%81%A7%E8%89%B2%E3%80%85%E3%82%84%E3%81%A3%E3%81%A6%E3%81%84%E3%82%8B">裏で色々やっている</a></h4> <p>フォロワー数が何万とかのインフルエンサーは結構みんな裏で色々やっています。というか、そもそもTwitterが裏みたいなものなので実際には表というべきかもしれません。マーケティングのためにしっかりフォロワー数を増やそうとしている方々は、そもそもTwitterばっかやってふやしているわけがないと思います。</p> <p>現実世界で色々な営業を行い、それをTwitterと絡めて更に効果を高めるような総合的なマーケティングを行っているのではないかと思います。そもそも元々の所属や経歴がすごい方だったりする事が多いですし。正直それを真似ることは不可能ですし、真似たところで同じような効果は得られないでしょう。彼らが発信しているのはあくまでもそういったバックグラウンドがあってこその意味があるフォロワー数を増やすテクニックです。</p> <h4 id="とんでもないことをやっている"><a href="#%E3%81%A8%E3%82%93%E3%81%A7%E3%82%82%E3%81%AA%E3%81%84%E3%81%93%E3%81%A8%E3%82%92%E3%82%84%E3%81%A3%E3%81%A6%E3%81%84%E3%82%8B">とんでもないことをやっている</a></h4> <p>とはいえ人類の全員がものすごいバックグラウンドを持っているわけではありません。そのためものすごいバックグラウンドを自ら作り出してフォロワー数をふやしているパターンもあります。例えば無職なのにすごいとか、フリーランスですごいとか、何もしてないのにすごいとか、とにかく普段普通に生活している上では誰も絶対に普通やらないようなことをやって目立つことで、フォロワーを増やしているパターンです。</p> <p>ちなみに自ら作り出して、と言いましたが、わざわざ作らなくても、普段その方が好きでやっていることがとてもすごいことで、単に人の目を惹いてフォロワーを自然に増やしてしまう場合もあります。</p> <p>個人的に前者はあまり興味を持てませんが、後者は本当に技術的にだったりアイデア的にだったり、本当におどろくようなことをしていて、見ていて楽しいですし尊敬できます。</p> <h4 id="真似できない"><a href="#%E7%9C%9F%E4%BC%BC%E3%81%A7%E3%81%8D%E3%81%AA%E3%81%84">真似できない</a></h4> <p>ということで、上記のようなインフルエンサーがフォロワー数を増やす方法は明白なのですが、はっきり言って真似できるものではありません。逆にそういった方々が発信しているフォロワー数を増やす方法を真似したところで再現はできません。</p> <p>ツイート文の作り方だったりとか、個人的には全く参考にする意味はないと考えています。もちろんやってはいけない、というものでもないため使うかどうかは自由です。</p> <p>とくに他のインフルエンサーに頻繁にリプライしたり引用ツイートする、といった手法は、正直インフルエンサーにとっては見ず知らずの人から毎回謎のウケ狙いのリプライ等が送られてきても迷惑なだけでしょうし、そもそもそういったリプライが頻繁に行われるので興味を失っているでしょう。個人的にはそこまでする意味は全然ないかなと思います。もちろんこちらも自由ではありますが。</p> <h2 id="有料部分について"><a href="#%E6%9C%89%E6%96%99%E9%83%A8%E5%88%86%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6">有料部分について</a></h2> <p>最初の方にも書きましたが、書いた本人が実際に実践していない方法です。性格的に向いていないのと、大変なことなのでやってられませんし興味も持てません。どうしても気になってしまうという方のみ買っていただいた方が良いと思います。</p> <p>また、これはインフルエンサーが書いたりするような裏技でもなんでもありません。ひたすら地道に続ければ増える、というだけの努力の方法を書いてあるだけです。ですのであまり期待はしないでください。</p> <p>また、読んで参考になる部分があってもあまり鵜呑みにしないでください。急にTwitterでの活動スタイルを変えると「あの人急にどうしたの…?」と思われるでしょう。もちろん完全にマーケティング用のTwitterアカウントなどであればそれで良いと思いますが、普段遣いアカウントで急にいつもと違うことをし始めると友達を失う可能性もあります。あくまで、自分らしくTwitterをやるのが一番だと思います。</p> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/15652 2019-12-29T11:22:37+09:00 2019-12-29T11:22:37+09:00 https://crieit.net/posts/Node-js-Twitter Node.jsで画像/動画つきツイートをTwitterに投稿すると大変だった... <p>JavaScriotでツイートしたいなと思って、いろいろ試していたら、<br /> 30秒以上動画つきツイートが結構めんどくさかったので、その時の備忘録。</p> <h3 id="Node.jsでTwitter APIを使う"><a href="#Node.js%E3%81%A7Twitter+API%E3%82%92%E4%BD%BF%E3%81%86">Node.jsでTwitter APIを使う</a></h3> <p>Node.jsでTwitter APIを使うときは、<a target="_blank" rel="nofollow noopener" href="https://github.com/desmondmorris/node-twitter">desmondmorris/node-twitter</a>を使うのが良さそう</p> <h4 id="インストール"><a href="#%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB">インストール</a></h4> <pre><code class="shell">$ npm install twitter </code></pre> <h4 id="ツイートしてみる"><a href="#%E3%83%84%E3%82%A4%E3%83%BC%E3%83%88%E3%81%97%E3%81%A6%E3%81%BF%E3%82%8B">ツイートしてみる</a></h4> <p>文字だけをツイートするのは、こんな感じ。</p> <pre><code class="typescript">import Twitter from "twitter"; // 初期化 const client = new Twitter({ consumer_key: TWITTER_CONSUMER_KEY, consumer_secret: TWITTER_CONSUMER_SECLET, access_token_key: ACCESS_TOKEN_KEY, access_token_secret: ACCESS_TOKEN_SECRET }); // 文字だけをツイート async function tweet(text: string) { const tweet = await client.post("statuses/update", { status: text }); } tweet("ツイート").then(); </code></pre> <p>Twitterクラスに<code>.post()</code>や、<code>.get()</code>が用意されているので、<br /> <a target="_blank" rel="nofollow noopener" href="https://developer.twitter.com/en/docs">Twitter APIのドキュメント</a>を見ながら、呼び出していく感じ。</p> <p>ツイートするのは<a target="_blank" rel="nofollow noopener" href="https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/post-statuses-update">POST statuses/update</a>なのでドキュメントを参照。</p> <h3 id="画像つきでツイートしてみる"><a href="#%E7%94%BB%E5%83%8F%E3%81%A4%E3%81%8D%E3%81%A7%E3%83%84%E3%82%A4%E3%83%BC%E3%83%88%E3%81%97%E3%81%A6%E3%81%BF%E3%82%8B">画像つきでツイートしてみる</a></h3> <p>画像とか動画とかメディアつきだとちょっとめんどくさく...</p> <p>ツイートと一緒に画像をアップロードできないので、</p> <ol> <li>最初に画像をアップロードしてからmediaIdを取得し、</li> <li>mediaIdと一緒に<code>statuses/update</code>でツイート</li> </ol> <p>という段階的な感じになる。</p> <pre><code class="typescript">import Twitter from "twitter"; const client = // 略 async function tweetWithImage(text: string, filePath: string) { const data = require('fs').readFileSync(filePath); // 画像をアップロード const media = await client.post('media/upload', {media: data}); // mediaIdをパラメタに追加して、ツイート const params = { status: text, media_ids: media.media_id_string }; const tweet = await client.post("statuses/update", params); } tweetWithImage("ツイート", "./imange.jpg").then(); </code></pre> <p>複数の画像をつけたい場合は、それぞれアップロードして、<br /> <code>media_ids</code>にカンマ区切りでmediaIdを指定する。</p> <p>ただ、この<code>media/upload</code>を1度だけ呼び出すシンプルな方法には制限があり、<br /> GIFや動画はアップロードできない...</p> <h3 id="30秒以下の動画付きツイートをしてみる"><a href="#30%E7%A7%92%E4%BB%A5%E4%B8%8B%E3%81%AE%E5%8B%95%E7%94%BB%E4%BB%98%E3%81%8D%E3%83%84%E3%82%A4%E3%83%BC%E3%83%88%E3%82%92%E3%81%97%E3%81%A6%E3%81%BF%E3%82%8B">30秒以下の動画付きツイートをしてみる</a></h3> <p>動画やGIFをアップロードしたい場合は、<a target="_blank" rel="nofollow noopener" href="https://developer.twitter.com/en/docs/media/upload-media/uploading-media/chunked-media-upload">Chunked media upload</a>という形でアップロードする必要がある。</p> <p>この方法は、大きく3ステップに分かれている</p> <ol> <li>初期化: command=INIT</li> <li>アップロード: command=APPEND</li> <li>完了: commaind=FINALIZE</li> </ol> <pre><code class="typescript">import Twitter from "twitter"; const client = // 略 async function tweetWithChunkedMedia(text: string, filePath: string) { const mediaType = 'video/mp4'; const mediaData = require('fs').readFileSync(filePath); const mediaSize = require('fs').statSync(filePath).size; // 動画をアップロード: INIT const media = await client.post('media/upload', { command : 'INIT', total_bytes: mediaSize, media_type : mediaType }); // INITでmediaIdが発行されるので、取得しておく const mediaId = media.media_id_string; // 動画をアップロード: UPLOAD await client.post('media/upload', { command : 'APPEND', media_id : mediaId, media : mediaData, segment_index: 0 }); // 動画をアップロード: FINALIZE await client.post('media/upload', { command : 'FINALIZE', media_id: mediaId }); // mediaIdをパラメタに追加して、ツイート const params = { status: text, media_ids: mediaId }; const tweet = await client.post("statuses/update", params); } tweetWithChunkedMedia("ツイート", "./video.mp4").then(); </code></pre> <p>動画やGIFのような大きいサイズのメディアは、分割してアップロードできるこの仕組みを使うっぽい。</p> <p>ただ、30秒以上の動画や1MB(チャンクサイズ上限)を超える場合は、<br /> INIT時に<code>media_category</code>を指定して、非同期アップロードをしないといけない。</p> <h3 id="30秒を超える動画付きツイートをしてみる"><a href="#30%E7%A7%92%E3%82%92%E8%B6%85%E3%81%88%E3%82%8B%E5%8B%95%E7%94%BB%E4%BB%98%E3%81%8D%E3%83%84%E3%82%A4%E3%83%BC%E3%83%88%E3%82%92%E3%81%97%E3%81%A6%E3%81%BF%E3%82%8B">30秒を超える動画付きツイートをしてみる</a></h3> <p>30秒を超える動画は、media_categoryをつけ、非同期アップロードで対応しないといけない。</p> <p>media_categoryは、<code>tweet_image</code>, <code>tweet_gif</code>, <code>tweet_video</code>を指定できるので、<br /> アップロードするメディアに合わせて指定する。</p> <p>また、チャンクサイズの上限が1MBなので、APPENDでデータをPOSTする際には注意。<br /> 1MB以上の場合は、1MB以下になるように分割し、segment_indexでindexを指定する。</p> <pre><code class="typescript">import Twitter from "twitter"; const client = // 略 async function tweetWithChunkedMedia(text: string, filePath: string) { const mediaType = 'video/mp4'; const mediaData = require('fs').readFileSync(filePath); const mediaSize = require('fs').statSync(filePath).size; // 動画をアップロード: INIT const media = await client.post('media/upload', { command : 'INIT', total_bytes: mediaSize, media_type : mediaType, media_category: "tweet_video" // media_categoryを指定 }); const mediaId = media.media_id_string; // 動画をアップロード: UPLOAD await client.post('media/upload', { command : 'APPEND', media_id : mediaId, media : mediaData, segment_index: 0 }); // 動画をアップロード: FINALIZE await client.post('media/upload', { command : 'FINALIZE', media_id: mediaId }); // 動画をアップロード: STATUS while(true) { // アップロードのステータスをポーリング const status = await client.get('media/upload', { command : 'STATUS', media_id: mediaId }); if (status.processing_info.state == "succeeded") { // 完了したら、ポーリングを終了 break; } else if (status.processing_info.state == "failed") { // エラーになったら、例外を投げる throw new Error(status.processing_info.error.message); } else { // 処理中(in_progress)の場合は、指定された秒数分待つ await sleep(status.processing_info.check_after_secs + 1); } } // mediaIdをパラメタに追加して、ツイート const params = { status: text, media_ids: mediaId }; const tweet = await client.post("statuses/update", params); } function sleep(time: number) { return new Promise((resolve, reject) => { setTimeout(() => resolve(), time * 1000); }); } tweetWithChunkedMedia("ツイート", "./video.mp4").then(); </code></pre> <p>かなりハマったのが以下の2点。</p> <ol> <li>STATUSはFINALIZEしてからじゃないと、404が返ってくる</li> <li>FINALIZEのレスポンスにもprocessing_infoがあるが、<br /> STATUSをしないと永遠にpending状態。(STATUSを呼ぶと処理が始まる)</li> </ol> <p>このあたり、ドキュメントに詳しい説明がなくて、かなりハマった...</p> <h3 id="axiosを使って外部URLのメディアをツイートする"><a href="#axios%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E5%A4%96%E9%83%A8URL%E3%81%AE%E3%83%A1%E3%83%87%E3%82%A3%E3%82%A2%E3%82%92%E3%83%84%E3%82%A4%E3%83%BC%E3%83%88%E3%81%99%E3%82%8B">axiosを使って外部URLのメディアをツイートする</a></h3> <p>Cloud Storageにある画像/動画を含めてツイートしたかったので、<br /> axiosを使って外部URLを取得する処理を加えてみたのがこれ。</p> <p><code>new TwitterApi().postTweet("ツイート", ["https://..."]);</code><br /> みたいに呼び出すと、ダウンロード/アップロード/ツイートできる。(はず...)</p> <pre><code class="typescript">import Twitter from "twitter"; import axios from "axios"; /** * スリープ処理 * @param time スリープする秒数 */ function sleep(time: number) { return new Promise((resolve, reject) => { setTimeout(() => resolve(), time * 1000); }); } export default class TwitterApi { private client: Twitter; constructor() { this.client = new Twitter({ consumer_key: // TWITTER_CONSUMER_KEY, consumer_secret: // TWITTER_CONSUMER_SECLET, access_token_key: // ACCESS_TOKEN_KEY, access_token_secret: // ACCESS_TOKEN_SECRET }); } /** * ツイートするメイン処理 * @param text ツイート文 * @param medias 添付する外部URLのリスト */ public async postTweet(text: string, medias: string[] = []) { let mediaIds: string[] = []; if (medias.length > 0) { // メディアファイルがあれば、アップロードしてmediaIdを取得 mediaIds = await Promise.all( medias.map(async v => await this.uploadMedia(v.url)) ); } const res = await this.tweet(text, mediaIds); } /** * メディアのアップロード処理 * @param url メディアのURL */ private async uploadMedia(url: string) { // axiosを使って、メディアのデータを取得 const res = await axios.create({ responseType: "arraybuffer" }).get(url); const mediaData: ArrayBuffer = res.data; const mediaSize = res.headers["content-length"]; const mediaType = res.headers["content-type"]; // INIT: mp4かgifなら、media_categoryを指定する const initParams = { command: "INIT", total_bytes: mediaSize, media_type: mediaType }; if (mediaType == "video/mp4") { initParams["media_category"] = "tweet_video"; } else if (mediaType == "image/gif") { initParams["media_category"] = "tweet_gif"; } const data = await this.client.post("media/upload", initParams); const mediaId = data.media_id_string; // APPEND: 500Bくらいにチャンクを分けてアップロードする const chunkSize = 500000; const chunkNum = Math.ceil(mediaSize / chunkSize); for (let index = 0; index < chunkNum; index++) { const chunk = mediaData.slice(chunkSize * index, chunkSize * (index + 1)); const resAppend = await this.client.post("media/upload", { command: "APPEND", media_id: mediaId, media: mediaData.slice(chunkSize * index, chunkSize * (index + 1)), segment_index: index }); } // FINALIZE const resFinalize = await this.client.post("media/upload", { command: "FINALIZE", media_id: mediaId }); if (!resFinalize.processing_info) { // media_categoryをしていないと、processing_infoがない return mediaId; } else if (resFinalize.processing_info.state == "succeeded") { return mediaId; } else if (resFinalize.processing_info.state == "failed") { throw new Error(resFinalize.processing_info.error.message); } // STATUS while (true) { const resStatus = await this.client.get("media/upload", { command: "STATUS", media_id: mediaId }); if (resStatus.processing_info.state == "succeeded") { return mediaId; } else if (resStatus.processing_info.state == "failed") { throw new Error(resStatus.processing_info.error.message); } else { await sleep(resStatus.processing_info.check_after_secs + 1); } } } /** * ツイート処理 * @param text ツイート文 * @param mediaIds メディアIDのリスト */ private async tweet(text: string, mediaIds: string[] = []) { const params = { status: text }; if (mediaIds.length > 0) params["media_ids"] = mediaIds.join(","); const tweet = await this.client.post("statuses/update", params); return tweet; } } </code></pre> <p>若干ハマったのが、以下の2点</p> <ol> <li>axiosで取得する場合は、<code>{ responseType: "arraybuffer" }</code>でcreateしないといけない</li> <li>cloudStrageでデータを取得できないので、downloadURLを取得しておかないといけない</li> </ol> <p>Twitter APIむずい...</p> <p>以上!!</p> <h2 id="こんなのつくってます!!"><a href="#%E3%81%93%E3%82%93%E3%81%AA%E3%81%AE%E3%81%A4%E3%81%8F%E3%81%A3%E3%81%A6%E3%81%BE%E3%81%99%21%21">こんなのつくってます!!</a></h2> <p>積読用の読書管理アプリ 『積読ハウマッチ』をリリースしました!<br /> <a target="_blank" rel="nofollow noopener" href="https://tsundoku.site">積読ハウマッチ</a>は、Nuxt.js+Firebaseで開発してます!</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/572d4947-f40b-e4dc-1c9c-bc584cd2a66c.png" width="200"/></p> <p>もしよかったら、遊んでみてくださいヽ(=´▽`=)ノ</p> <p>要望・感想・アドバイスなどあれば、<br /> 公式アカウント(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/MemoryLoverz">@MemoryLoverz</a>)や開発者(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/kira_puka">@kira_puka</a>)まで♪</p> <h1 id="参考にしたサイト様"><a href="#%E5%8F%82%E8%80%83%E3%81%AB%E3%81%97%E3%81%9F%E3%82%B5%E3%82%A4%E3%83%88%E6%A7%98">参考にしたサイト様</a></h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/mpyw/items/7bedf8c23de286cef0f9">TwitterAPIのアップロード系エンドポイントまとめ (140秒動画対応) - Qiita</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://developer.twitter.com/en/docs/tutorials/uploading-media">Uploading media — Twitter Developers</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://developer.twitter.com/en/docs/media/upload-media/uploading-media/media-best-practices">Media best practices — Twitter Developers</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://developer.twitter.com/en/docs/media/upload-media/uploading-media/chunked-media-upload">Chunked media upload — Twitter Developers</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://stackoverflow.com/questions/12740659/downloading-images-with-node-js">Downloading images with node.js - Stack Overflow</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://developer.twitter.com/en/docs/media/upload-media/api-reference/get-media-upload-status">GET media/upload (STATUS) — Twitter Developers</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/ksh-fthr/items/ba7c80252edad0e7c66c">[axios] 画像データのレスポンスを取得する際にハマった話 - Qiita</a></li> </ul> きらぷか@積読ハウマッチ/SSSAPIなど tag:crieit.net,2005:PublicArticle/15649 2019-12-29T11:18:43+09:00 2019-12-29T11:18:43+09:00 https://crieit.net/posts/Twitter-URL Twitterでツイートできる文字数を正確に数える(絵文字もURLも) <p>Nuxt.jsでツイートするアプリを作りたいなと思い、<br /> 文字数ってどうやって計算するんだろ?って思ったら、<br /> 公式でライブラリ(<a target="_blank" rel="nofollow noopener" href="https://github.com/twitter/twitter-text">twitter-text</a>)が用意されているらしいので、使ってみたときの備忘録</p> <p>Java版/Ruby版/JavaScript版/Objective-C版などいろいろあるらしい。</p> <h4 id="インストール"><a href="#%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB">インストール</a></h4> <pre><code class="shell">$ npm install twitter-text </code></pre> <h4 id="使い方"><a href="#%E4%BD%BF%E3%81%84%E6%96%B9">使い方</a></h4> <pre><code class="javascript">const twitter = require('twitter-text'); // ツイートするテキスト const tweetText = "This is a test tweet"; // twitter-textで計算 const result = twitter.parseTweet(tweet); console.log(result) /* Returns: { weightedLength: 20, permillage: 71, valid: true, displayRangeEnd: 19, displayRangeStart: 0, validRangeEnd: 19, validRangeStart: 0 } */ // 日本語版の場合、文字数を2で割るとツイッターと同じになる。 const textLength = result.weightedLength / 2; </code></pre> <p>以上!!</p> <h2 id="こんなのつくってます!!"><a href="#%E3%81%93%E3%82%93%E3%81%AA%E3%81%AE%E3%81%A4%E3%81%8F%E3%81%A3%E3%81%A6%E3%81%BE%E3%81%99%21%21">こんなのつくってます!!</a></h2> <p>積読用の読書管理アプリ 『積読ハウマッチ』をリリースしました!<br /> <a target="_blank" rel="nofollow noopener" href="https://tsundoku.site">積読ハウマッチ</a>は、Nuxt.js+Firebaseで開発してます!</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/572d4947-f40b-e4dc-1c9c-bc584cd2a66c.png" width="200"/></p> <p>もしよかったら、遊んでみてくださいヽ(=´▽`=)ノ</p> <p>要望・感想・アドバイスなどあれば、<br /> 公式アカウント(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/MemoryLoverz">@MemoryLoverz</a>)や開発者(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/kira_puka">@kira_puka</a>)まで♪</p> <h1 id="参考にしたサイト"><a href="#%E5%8F%82%E8%80%83%E3%81%AB%E3%81%97%E3%81%9F%E3%82%B5%E3%82%A4%E3%83%88">参考にしたサイト</a></h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/PND/items/17e87b8839c9099d2e70#twitter-text-%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA">ツイートの文字数を <strong>厳密に</strong> 数える方法 - Qiita</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/twitter/twitter-text/tree/master/js">twitter-text/js at master · twitter/twitter-text</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://developer.twitter.com/en/docs/developer-utilities/twitter-text">twitter-text Parser — Twitter Developers</a></li> </ul> きらぷか@積読ハウマッチ/SSSAPIなど tag:crieit.net,2005:PublicArticle/15608 2019-12-15T08:56:58+09:00 2019-12-16T09:00:03+09:00 https://crieit.net/posts/8fd51af2c82ad86fea6c891b4c3564f6 シモネタサイトを全力で作ったよ <p>この記事は<a target="_blank" rel="nofollow noopener" href="https://qiita.com/advent-calendar/2019/kuso-app2">クソアプリ2 Advent Calendar 2019</a> 15日目の記事です。14日目は<a target="_blank" rel="nofollow noopener" href="https://twitter.com/endo_hizumi">endo_hizumi</a>さんの「<a target="_blank" rel="nofollow noopener" href="https://qiita.com/endo_hizumi/items/2de99fafa424e04b4375">お前らのクソアプリは間違えてる</a>」。わーい!寿司が回ってるよ!ぼくの思ってた廻る寿司となんか違う!</p> <h1 id="クソアプリが作れない"><a href="#%E3%82%AF%E3%82%BD%E3%82%A2%E3%83%97%E3%83%AA%E3%81%8C%E4%BD%9C%E3%82%8C%E3%81%AA%E3%81%84">クソアプリが作れない</a></h1> <p>ということで、年末のお祭りを名目に、肩の力を抜いてなんでも公開するといいよ!という趣旨のクソアプリカレンダー、ずっと眺めていたのですが、なかなか参加できなかったのですよね。あっという間にうまるのもあるけど、それ以上に、自分マジメなので、そういうのを作るのが苦手なのです。</p> <p>どれくらいマジメかというと、中学高校と学級委員長を歴任していました。委員長は何がいいかというと、委員長という役割をしておけば、クラスのなかに居場所がなくなることがないのですよね!</p> <p>そんな人間にクソアプリを作れだと?無理に決まっている。予約日が15日なのに一週間も前に完成させて<a target="_blank" rel="nofollow noopener" href="https://blog.nabettu.com/entry/bugbash">バグバッシュ</a>しちゃったじゃないか!</p> <h1 id="誰でもできるクソアプリ"><a href="#%E8%AA%B0%E3%81%A7%E3%82%82%E3%81%A7%E3%81%8D%E3%82%8B%E3%82%AF%E3%82%BD%E3%82%A2%E3%83%97%E3%83%AA">誰でもできるクソアプリ</a></h1> <p>こうなることはわかっていたので、対策を立てておきました。出来栄えがクソなんじゃなくて、ネタがクソならクソアプリ。小学生のごとくウンコとかオシッコとか言っておけばなにを作ってもクソアプリになるに違いない!</p> <p>【警告】 以降、<strong>全力でシモネタ</strong>です。そういうのがお好きでない方はこれ以上読まないことをおすすめします。</p> <p><img src="https://placehold.jp/50x300.png" alt="空白" /></p> <p>(警告したからね?もう遠慮しないよ!?)</p> <p><img src="https://placehold.jp/50x300.png" alt="空白" /></p> <p>というわけで作りました。</p> <h1 id="ギガントおちんちんランキング"><a href="#%E3%82%AE%E3%82%AC%E3%83%B3%E3%83%88%E3%81%8A%E3%81%A1%E3%82%93%E3%81%A1%E3%82%93%E3%83%A9%E3%83%B3%E3%82%AD%E3%83%B3%E3%82%B0">ギガントおちんちんランキング</a></h1> <p><a href="https://crieit.now.sh/upload_images/9d08c6d37e5274805ec35413f543d5ed5df44bceb3d25.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/9d08c6d37e5274805ec35413f543d5ed5df44bceb3d25.png?mw=700" alt="image.png" /></a><br /> <a target="_blank" rel="nofollow noopener" href="https://mogya.github.io/ochinRank/">ギガントおちんちんランキング</a>🎉</p> <p>おちんちんの太さを測って入力すると、日本で何番目くらいの太さなのかわかります。<br /> <strong>もちろん</strong>結果をTwitterでシェアしてみんなに自慢することが出来ます。</p> <h1 id="なんで太さ?"><a href="#%E3%81%AA%E3%82%93%E3%81%A7%E5%A4%AA%E3%81%95%EF%BC%9F">なんで太さ?</a></h1> <p>いにしえの昔、<a target="_blank" rel="nofollow noopener" href="http://pandora.nu/pha/ochinchin/">ハイパーオチンチンランキング</a> というサイトがあったのです。<br /> おちんちんの長さを入力すると順位を調べてくれるこのサイト、それはもうバズりました。</p> <p><a href="https://crieit.now.sh/upload_images/9d08c6d37e5274805ec35413f543d5ed5df44e0e9b814.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/9d08c6d37e5274805ec35413f543d5ed5df44e0e9b814.png?mw=700" alt="image.png" /></a><br /> <a target="_blank" rel="nofollow noopener" href="https://b.hatena.ne.jp/entry/pandora.nu/pha/ochinchin/">https://b.hatena.ne.jp/entry/pandora.nu/pha/ochinchin/</a></p> <p>しかし、このサイトが登場したその当時、SNSは今ほど一般的ではなくて、どんなサイトでもtwitterシェアボタンがあるのが当たり前という時代ではなかったのです。</p> <p>これだけ面白いサイトが!ここにシェアボタンがあれば!絶対バズるのに!</p> <p>何回か持論を展開したのですが、みんなおもしろネタとして受け取るばっかりで作る人がいないので自分で作ることにしました。</p> <p>リスペクト的な意味を込めて、長さじゃなくて太さにしてあります。</p> <h1 id="技術的な話"><a href="#%E6%8A%80%E8%A1%93%E7%9A%84%E3%81%AA%E8%A9%B1">技術的な話</a></h1> <p>大前提として、このサイトを<strong>いつまでもメンテしたくない</strong>というのがあります。今クソアプリ用のテンションで吹っ切れてるからおちんちんとか平気で書いているけど、年が明けてこれのメンテナンスを継続する自信はない。おちんちんのことは完全に忘れて次のサービスに取り掛かりたい。</p> <p>自前サーバに載せてなんかの弾みにうっかり消して「もぎゃさん、おちんちんが落ちてるんですけど?」とかいうメンションをもらったりしたら、確実にそんなサイトはなかったことにしてしまう気がします。</p> <p>だからといってFirebaseみたいなのも、お財布に火が付きそうで怖い。</p> <p>放っておいたらいつまでも維持されて、アクセス従量課金じゃないサーバ。</p> <p>あります。<a target="_blank" rel="nofollow noopener" href="https://pages.github.com/">Github pages</a>です。Githubのアカウントさえあれば無料で使えて、リポジトリを消さない限り維持される、高負荷でもそうそう落ちたりしない、理想のサーバ!</p> <p>Github Pagesは静的サイトしか保持してくれないので、開発言語はJS<br /> しかありえない。去年作った<a target="_blank" rel="nofollow noopener" href="https://github.com/mogya/qiitaRank">qiitaRank</a> では、index.htmlにvue.jsをscriptタグで取り込む簡単実装だったので、これをコピペしてサクッと作りました。</p> <h2 id="OGP問題"><a href="#OGP%E5%95%8F%E9%A1%8C">OGP問題</a></h2> <p>Twitterシェアが命のサービスなので、シェアしたときの見た目が大事です。くすっと笑ってぼくも使ってみようと思えるサイトでないといけない。そのためにはOGP画像が必須ですが、Twitterのクローラーはそんなに賢くないので、JSで生成した動的画像をOGPとして使うことが出来ません。<br /> かといってSSRみたいな高度なことをするためにはホストするサーバが必要、CircleCIで頑張る...うー、めんどくさいな。</p> <p>3日くらい考えてひらめきました。「そもそも動的である必要はないよね」</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/mogya/ochinRank/tree/master/static">ソースコード</a>見ていただくとわかりますが、結果ページは独立したHTMLファイルになっています。結果ページの役割は、TwitterにOGP画像を返すこと。人間がアクセスしてきたらトップページにリダイレクトさせます。</p> <pre><code><meta property="og:image" content="https://mogya.github.io/ochinRank/img/cannon_ogp.png" /> <meta property="og:description" content="あなたの太さはどれくらい?全国平均どれくらいなのか見てみよう!" /> <meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:site:id" content="mogya" /> <title>ギガントおちんちんランキング</title> </head> <body> <script> window.location.href = './' </script> </body> </html> </code></pre> <p>違いはOBP画像だけなんだから、nuxt.jsみたいなの使えば生成できそうですが、意識低く、結果と同じだけのHTMLファイルをそのまま保持しています。</p> <p>あとはindex.htmlで、結果に応じてシェアするURLを分けてあげれば出来上がりです。</p> <p><a href="https://crieit.now.sh/upload_images/9d08c6d37e5274805ec35413f543d5ed5df45a1ddac79.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/9d08c6d37e5274805ec35413f543d5ed5df45a1ddac79.png?mw=700" alt="image.png" /></a></p> <h2 id="IE対策"><a href="#IE%E5%AF%BE%E7%AD%96">IE対策</a></h2> <p>サイトの趣旨がとてもオチンチンなので、エンジニアではない方がアクセスすることを想定する必要があります。IEで開くと...動かん。</p> <p>Vue.jsのコードは近代的なブラウザを前提にしているので、IEで動かすのは難しいのです。<a target="_blank" rel="nofollow noopener" href="https://polyfill.io/v3/">Polyfill.io</a>入れたら動くんでしょ?と思っていたのですが、scriptタグで取り込むvue.jsは、script部分で新しい記法(テンプレートリテラルとか)をつい使っちゃうので、かなり無理があります。</p> <p>しばらく頑張ったのですが、どうにもならんので、さっき回避したはずのNuxt.jsを入れてgenerateモードでページを生成してもらうことにしました。このやり方ならbabelがIEの差異を吸収したページを生成してくれるので、IEでも動かすことが出来ます。</p> <h2 id="Nuxt.js on Github pages"><a href="#Nuxt.js+on+Github+pages">Nuxt.js on Github pages</a></h2> <p>Nuxt.jsで生成したページをGithub pagesで動かす方法は<a target="_blank" rel="nofollow noopener" href="https://ja.nuxtjs.org/faq/github-pages/">ここ</a>に載っています(vue.js周りのドキュメント充実度すごいよね)</p> <p>簡単にいうと、URLがexample.com/_nuxt/hoge.js みたいなんじゃなくて https://mogya.github.io/ochinRank/ という具合にサブディレクトリになるので、それを考慮したHTMLが生成されるように設定する必要があります。</p> <pre><code>export default { router: { base: '/<repository-name>/' } } </code></pre> <p>あと、github pagesは基本レポジトリにあげたコードを全部公開してしまいます。generateしたファイルだけ公開するために、「masterブランチのdocsディレクトリ下だけを公開」モードを使うことにしました。</p> <p><a href="https://crieit.now.sh/upload_images/9d08c6d37e5274805ec35413f543d5ed5df4580c9685d.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/9d08c6d37e5274805ec35413f543d5ed5df4580c9685d.png?mw=700" alt="image.png" /></a></p> <p>nuxt.config.jsの設定を変更して、distの代わりにdocsディレクトリにHTMLを生成するようにします。</p> <pre><code>export default { generate: { dir: 'docs' }, </code></pre> <p>docsディレクトリはそんな使い方をするものじゃない?クソアプリだからね。</p> <h1 id="シモネタライセンス問題"><a href="#%E3%82%B7%E3%83%A2%E3%83%8D%E3%82%BF%E3%83%A9%E3%82%A4%E3%82%BB%E3%83%B3%E3%82%B9%E5%95%8F%E9%A1%8C">シモネタライセンス問題</a></h1> <h2 id="いらすとやライセンス"><a href="#%E3%81%84%E3%82%89%E3%81%99%E3%81%A8%E3%82%84%E3%83%A9%E3%82%A4%E3%82%BB%E3%83%B3%E3%82%B9">いらすとやライセンス</a></h2> <p>当初このサイト、イラストは安定の<a target="_blank" rel="nofollow noopener" href="https://www.irasutoya.com/">いらすとや</a> を使わせていただく予定でした。自由に使えてなんでも揃ってるぼくらの素材サイト。くすっと笑うのにトーンもあってるよね。しかし。</p> <p><a href="https://crieit.now.sh/upload_images/9d08c6d37e5274805ec35413f543d5ed5df45b1c85a78.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/9d08c6d37e5274805ec35413f543d5ed5df45b1c85a78.png?mw=700" alt="image.png" /></a></p> <p><strong>ですよね</strong>。おちんちんランキングはジョークサイトであってアダルトサイトじゃないと思っているのですが、こういうのは結局の所いらすとやさんがどう受け止めるかの問題になってしまいます。<br /> いらすとやさんがあれだけ寛容な条件を用意してくれているのに、「ぼくはアダルトサイトじゃないと思いました」っていう名目で利用を強行して炎上したりすると、いらすとやさんにご迷惑がかかりそう。</p> <p>ということでお金出してイラストレーターさんに描いてもらいました。クソアプリなのに!<br /> <a target="_blank" rel="nofollow noopener" href="https://crowdworks.jp/public/jobs/4578221">【一点で応募可能】 Twitterのシェア用画像5点の依頼/外注|イラスト作成の仕事</a></p> <p>でもおかげでなかなかいいデザインになったと思います。太さに応じてTwitterシェアしたときの画像が変わるようになっているので、ぜひシェアして見てくださいね。</p> daisuke furukawa tag:crieit.net,2005:PublicArticle/15457 2019-10-07T09:13:01+09:00 2019-10-07T09:31:08+09:00 https://crieit.net/posts/GAS-5d9a830d875a5 GASでいいねしたツイートをいいねの数とリツイートの数と一緒に記録するやつ <p>特定のユーザーのいいねしたツイート一覧を、ツイートに付いたいいねの数とリツイートの数も一緒に記録したくなって作りました。トリガー機能を使うと、定期的にいいねが記録されるようになります。</p> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/yanagiha/items/40c7f0cc140eace11bd8">GASで自分のツイートを取得してスプレッドシートに記録するやつ<br /> </a><br /> この記事も合わせて読むとわかりやすいかもしれません。</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406061/d311791c-7655-7d87-1dba-cb3ee543444d.png" alt="スクリーンショット 2019-10-07 3.40.44.png" /></p> <p>このように記録されます。</p> <pre><code class="javascript"><br /><br />function getFav(){ var service = twitter.getService(); var json = service.fetch("https://api.twitter.com/1.1/favorites/list.json?screen_name=いいねを取得したいアカウントのscreen name&count=100"); var array = JSON.parse(json); var ss = SpreadsheetApp.getActiveSpreadsheet(); var sheet = ss.getSheetByName('いいねを記録したいシートの名前'); var lastRow = sheet.getLastRow() +1; var lastId = sheet.getRange("F2").getValue(); for(var i = 0; i <= array.length -1; i++) { var int = parseInt(i); if(i === 0){ var recId = array[int]["id"]; sheet.getRange("G2").setValue(recId); } var id = array[int]["id"]; if(id > lastId){ var time = array[int]["created_at"]; var userId = array[int]["user"]["id_str"]; var json = service.fetch("https://api.twitter.com/1.1/users/show.json?user_id="+userId+"&include_entities=false"); var array2 = JSON.parse(json); var screenName = array2["screen_name"]; var text = array[int]["text"]; var favorite_count = array[int]["favorite_count"]; var retweet_count = array[int]["retweet_count"]; sheet.getRange(lastRow,1).setValue(time); sheet.getRange(lastRow,2).setValue(screenName); sheet.getRange(lastRow,3).setValue(text); sheet.getRange(lastRow,4).setValue(favorite_count); sheet.getRange(lastRow,5).setValue(retweet_count); sheet.getRange(lastRow,6).setValue(id); } lastRow = lastRow + 1; } </code></pre> <p>いいねを100件取得して、idが以前記録したツイートより大きければ記録する…といった感じです。<br /> 何かあったら気軽にコメントください。</p> yanagiha tag:crieit.net,2005:PublicArticle/15456 2019-10-07T09:10:43+09:00 2019-10-07T09:10:43+09:00 https://crieit.net/posts/GAS GASで自分のツイートを取得してスプレッドシートに記録するやつ <p>急にツイ消ししたくなった場合に備えて、自分のツイートをグーグルスプレッドシートに記録しておくことにしました。</p> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/golyat/items/6b430986dbfd8dd1c239">GASでTwitterの投稿とタイムライン取得</a><br /> こちらの記事を参考にして書いてみました。</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406061/43b4b722-3b85-bb5e-be81-133be3f4d987.jpeg" alt="20190828150010.jpg" /><br /> 画像のように記録されます。</p> <pre><code class="javaScript"><br /> var twitter = TwitterWebService.getInstance( '**********', // 作成したアプリケーションのConsumer Key '**********' // 作成したアプリケーションのConsumer Secret ); // 認証を行う(必須) function authorize() { twitter.authorize(); } // 認証をリセット function reset() { twitter.reset(); } // 認証後のコールバック(必須) function authCallback(request) { return twitter.authCallback(request); } function getMyTweets() { var service = twitter.getService(); var json = service.fetch("https://api.twitter.com/1.1/statuses/user_timeline.json?screen_name=ツイートを取得したいユーザーのid&count=30"); var array = JSON.parse(json); var ss = SpreadsheetApp.getActiveSpreadsheet(); var sheet = ss.getSheetByName('ツイートを保存したいシートの名前'); var lastRow = sheet.getLastRow() +1; var lastId = sheet.getRange("D2").getValue(); for(var i = 0; i <= array.length -1; i++) { var int = parseInt(i); if(i === 0){ var recId = array[int]["id"]; sheet.getRange("D2").setValue(recId); } var id = array[int]["id"]; if(id > lastId){ var time = array[int]["created_at"]; var text = array[int]["text"]; sheet.getRange(lastRow,1).setValue(time); sheet.getRange(lastRow,2).setValue(text); sheet.getRange(lastRow,3).setValue(id); } lastRow = lastRow + 1; } } </code></pre> <p>自分のツイートを最新のやつから30件まで取得して、idが以前取得したツイートより大きければスプレッドシートに書き込む……という感じです。</p> <p>ここ違うよ〜とかもっと良い書き方あるよ〜って場合は気軽にコメントください。</p> yanagiha tag:crieit.net,2005:PublicArticle/15445 2019-10-01T20:31:32+09:00 2019-10-01T20:31:32+09:00 https://crieit.net/posts/puppeteer-Twitter puppeteer初心者がTwitterブックマークをエクスポートするツールを作りながら、使い方をまとめてみた <p>ふと、puppeteerがおもしろそうだなと思い、前から欲しかった<br /> TwitterブックマークをJSONファイルにエクスポートするツールを題材に、<br /> いろいろ遊んでみた時に備忘録。</p> <p>puppeteerはサクッと使えるので、すてき(<em>´ω`</em>)</p> <h3 id="作ったもの"><a href="#%E4%BD%9C%E3%81%A3%E3%81%9F%E3%82%82%E3%81%AE">作ったもの</a></h3> <p>こんな感じで勝手に操作してエクスポートしてくれます(<em>´ω`</em>)</p> <blockquote class="twitter-tweet"><p lang="ja" dir="ltr">puppeteerで自動ログインして、ブクマをJOSNでエクスポートできるように(*´ω`*)わかりやすいように背景色を変えたりしてる(*´ω`*) <a target="_blank" rel="nofollow noopener" href="https://t.co/UJiGAiw5KN">pic.twitter.com/UJiGAiw5KN</a></p>— 積読ハウマッチ📚きらぷか (@kira_puka) <a target="_blank" rel="nofollow noopener" href="https://twitter.com/kira_puka/status/1178913910432256002?ref_src=twsrc%5Etfw">October 1, 2019</a></blockquote> <p>最終的なソースコードはGitHubで公開中。<br /> - <a target="_blank" rel="nofollow noopener" href="https://github.com/memory-lovers/export_twitter_bookmarks_puppeteer">memory-lovers/export_twitter_bookmarks_puppeteer: Twitter Bookmark Export Tool using Puppeteer</a></p> <p>ただ、注意事項がたくさんですが。。(-_-;)</p> <hr /> <h3 id="puppeteerの使い方"><a href="#puppeteer%E3%81%AE%E4%BD%BF%E3%81%84%E6%96%B9">puppeteerの使い方</a></h3> <h4 id="インストール"><a href="#%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB">インストール</a></h4> <pre><code class="console">$ npm install -S puppeteer </code></pre> <h4 id="基本的な雛形"><a href="#%E5%9F%BA%E6%9C%AC%E7%9A%84%E3%81%AA%E9%9B%9B%E5%BD%A2">基本的な雛形</a></h4> <p>基本的にはこんな感じ。</p> <ol> <li>ブラウザを起動</li> <li>ページを作成</li> <li>なんか処理する</li> <li>ブラウザの終了</li> </ol> <pre><code class="javascript">const puppeteer = require("puppeteer"); const fs = require("fs"); async function main() { let browser = null; try { // ブラウザの起動 browser = await puppeteer.launch(); // ページの作成 const page = await browser.newPage(); // 何らかの処理 } catch (error) { console.error(`Error: ${error}`, error); } finally { // ブラウザの終了 if (!!browser) await browser.close(); } } main().then(); </code></pre> <h4 id="puppeteerでできること"><a href="#puppeteer%E3%81%A7%E3%81%A7%E3%81%8D%E3%82%8B%E3%81%93%E3%81%A8">puppeteerでできること</a></h4> <h5 id="ブラウザの起動/停止"><a href="#%E3%83%96%E3%83%A9%E3%82%A6%E3%82%B6%E3%81%AE%E8%B5%B7%E5%8B%95%2F%E5%81%9C%E6%AD%A2">ブラウザの起動/停止</a></h5> <pre><code class="javascript">// ブラウザの起動: headlessで起動 const browser = await puppeteer.launch(); // ブラウザの起動: headlessじゃなく起動 const browser = await puppeteer.launch({ headless: false, slowMo: 10 }); // ブラウザの終了 await browser.close(); </code></pre> <p><code>headless: false</code>にすると、ブラウザが立ち上がって、動作確認画できる。<br /> <code>slowMo: 10</code>の値を大きくすると、スローモーションのように操作がゆっくりになる。</p> <h5 id="ページの開く/閉じる"><a href="#%E3%83%9A%E3%83%BC%E3%82%B8%E3%81%AE%E9%96%8B%E3%81%8F%2F%E9%96%89%E3%81%98%E3%82%8B">ページの開く/閉じる</a></h5> <pre><code class="javascript">// 新規ページの作成 const page = await browser.newPage(); // 画面サイズの設定 await page.setViewport({ width: 1280, height: 1200 }); // ページを閉じる await page.close(); </code></pre> <h5 id="指定したURLへ移動"><a href="#%E6%8C%87%E5%AE%9A%E3%81%97%E3%81%9FURL%E3%81%B8%E7%A7%BB%E5%8B%95">指定したURLへ移動</a></h5> <pre><code class="javascript">// 指定したURLへ移動 await page.goto("https://www.google.com", { waitUntil: "networkidle2" }); // 指定したURLへ移動: waitを設定 await page.goto("https://www.google.com", { waitUntil: "networkidle2" }); </code></pre> <p>オプションの<code>waitUntil</code>を指定すると、その条件が満たされるまでwaitする。<br /> 指定できるのは、以下の4つ。</p> <ul> <li><code>load</code>: <code>load</code>イベントが発火するまで</li> <li><code>domcontentloaded</code>: <code>DOMContentLoaded</code>イベントが発火するまで</li> <li><code>networkidle0</code>: ネットワーク接続が0個である状態が500ミリ秒続いたとき</li> <li><code>networkidle2</code>: ネットワーク接続が2個である状態が500ミリ秒続いたとき</li> </ul> <p>SPAとかの場合は、<code>networkidle2</code>とかまで待つと良さそう。</p> <p>参考: <a target="_blank" rel="nofollow noopener" href="https://codeday.me/jp/qa/20190513/814601.html">PuppeteerによるJavaScriptレンダリングされたHTMLの取得 - コードログ</a></p> <h5 id="要素の取得"><a href="#%E8%A6%81%E7%B4%A0%E3%81%AE%E5%8F%96%E5%BE%97">要素の取得</a></h5> <pre><code class="javascript">// 最初の`.button`の要素を取得 const button = await page.$('.button'); // すべての`.button`の要素を取得 const buttonList = await page.$$('.button'); </code></pre> <p>実際は<a target="_blank" rel="nofollow noopener" href="https://pptr.dev/#?product=Puppeteer&version=v1.20.0&show=api-class-elementhandle">ElementHandle</a>が返ってくる。</p> <p>1件取得と全件取得があるので注意。<br /> セレクタの書き方は<a target="_blank" rel="nofollow noopener" href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors">CSS selectors</a>が使える。</p> <p><a target="_blank" rel="nofollow noopener" href="https://developer.mozilla.org/en-US/docs/Web/API/Document/evaluate">XPATH</a>で書ける<code>page.$x();</code>というのもある。</p> <h5 id="要素のクリック"><a href="#%E8%A6%81%E7%B4%A0%E3%81%AE%E3%82%AF%E3%83%AA%E3%83%83%E3%82%AF">要素のクリック</a></h5> <pre><code class="javascript">// クリック: ページからセレクタで指定 await page.click('.button'); // クリック: ElementHandlerからクリック const button = await page.$('.button'); await button.click(); // クリック: ページからElementHandlerを使ってevaluate const button = await page.$('.button'); await page.evaluate(v => v.click(), button) // クリック: ElementHandlerからevaluateでクリック const button = await page.$('.button'); await button.evaluate(v => v.click()) </code></pre> <p>クリックなど、JavaScriptを実行する方法はいくつかある。<br /> SPAなサイトだとうまく行かない場合があるが、<code>page.evaluaate</code>などを使うとうまくいく時がある。</p> <h5 id="入力する"><a href="#%E5%85%A5%E5%8A%9B%E3%81%99%E3%82%8B">入力する</a></h5> <pre><code class="javascript">// テキストを入力する: ページからセレクタで指定 await page.type('#text-input', "Hello"); // テキストを入力する: ElementHandlerで指定 const inputText = await page.$('#text-input'); await inputText.type("Hello"); </code></pre> <h5 id="待つ/waitする"><a href="#%E5%BE%85%E3%81%A4%2Fwait%E3%81%99%E3%82%8B">待つ/waitする</a></h5> <pre><code class="javascript">// 1000ms待つ await page.waitFor(1000); // 指定した要素が表示されるまで待つ await page.waitForSelector(`.foo`); // or await page.waitFor('.foo'); // 条件を満たすまで待つ await page.waitFor(() => !!document.querySelector('.foo')); // 移動するまで待つ await Promise.all([ page.waitForNavigation(), page.click('a.my-link'), ]); // or const navigationPromise = page.waitForNavigation(); await page.click('a.my-link'), await navigationPromise; </code></pre> <h5 id="その他もろもろ"><a href="#%E3%81%9D%E3%81%AE%E4%BB%96%E3%82%82%E3%82%8D%E3%82%82%E3%82%8D">その他もろもろ</a></h5> <p><code>evaluate</code>を使うとHTML要素に対して実行できるので、いろいろできる</p> <pre><code class="javascript"><br />// innerTextを取得 const innerText = await elm.evaluate(node => node.innerText); // textContentを取得 const textContent = await elm.evaluate(node => node.textContent); // href属性の取得 const href = await elm.evaluate(node => node.href); // 背景色変更 await elm.evaluate((v, color) => (v.style.backgroundColor = color), "gray"); // URLの取得 const url = await page.evaluate(_ => location.origin); // スクロール: 1画面分 await page.evaluate(_ => window.scrollBy(0, window.innerHeight)); // スクロール: 指定要素まで await page.evaluate(elm => window.scrollBy(0, elm.getBoundingClientRect().top), elm); </code></pre> <h5 id="スクリーンショットの取得"><a href="#%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%E3%81%AE%E5%8F%96%E5%BE%97">スクリーンショットの取得</a></h5> <pre><code class="javascript">// スクリーンショットの取得: 表示範囲のみ await page.screenshot({ path: "screenshot.png" }); // スクリーンショットの取得: フルページを指定 await page.screenshot({ path: "screenshot.png", fullPage: true }); // スクリーンショットの取得: 指定要素のみ const element = await page.$('h1'); await element.screenshot({path: 'screenshot_h1.png'}); </code></pre> <h5 id="描画されたHTMLの取得"><a href="#%E6%8F%8F%E7%94%BB%E3%81%95%E3%82%8C%E3%81%9FHTML%E3%81%AE%E5%8F%96%E5%BE%97">描画されたHTMLの取得</a></h5> <pre><code class="javascript">const fs = require("fs"); // HTMLの取得: ページ全体 const html = await page.content(); fs.writeFileSync("output.html", html); // HTMLの取得: 指定要素のみ const bodyHandle = await page.$('body'); const html_body = await page.evaluate(body => body.innerHTML, bodyHandle); fs.writeFileSync("output_body.html", html_body); </code></pre> <h3 id="エクスポートするツールを作ってみる"><a href="#%E3%82%A8%E3%82%AF%E3%82%B9%E3%83%9D%E3%83%BC%E3%83%88%E3%81%99%E3%82%8B%E3%83%84%E3%83%BC%E3%83%AB%E3%82%92%E4%BD%9C%E3%81%A3%E3%81%A6%E3%81%BF%E3%82%8B">エクスポートするツールを作ってみる</a></h3> <p>やりたいことは、こんな感じ。</p> <ol> <li>ブラウザ起動</li> <li>ログイン</li> <li>ブックマークページに移動</li> <li>以下繰り返し: 取得できる情報がなくなるまで <ul> <li>ブックマークの情報を取得</li> <li>ブックマークの削除</li> </ul></li> <li>取得した情報を.jsonファイルに書き出し</li> <li>ブラウザの停止</li> </ol> <h4 id="メインの処理はこんな感じ"><a href="#%E3%83%A1%E3%82%A4%E3%83%B3%E3%81%AE%E5%87%A6%E7%90%86%E3%81%AF%E3%81%93%E3%82%93%E3%81%AA%E6%84%9F%E3%81%98">メインの処理はこんな感じ</a></h4> <pre><code class="javascript">async function exportBookmarkMain() { let browser = null; try { // ブラウザの起動 browser = await puppeteer.launch({ headless: false, slowMo: 10 }); // ページの作成 const page = await browser.newPage(); await page.setViewport({ width: 1280, height: 1200 }); // ログイン: ログインページに移動&認証 await login(page); // ブックマークのエクスポート: ブックマークページに移動&ツイート上の取得 const bookmarks = await getTwitterBookmarks(browser, page); console.log(`bookmarks size is ${bookmarks.length}`); // 取得した情報の書き出し const timestamp = dayjs().format("YYYYMMDD_HHmmss"); const outputFile = `twitter_bookmarks_${timestamp}.json`; fs.writeFileSync(`output/${outputFile}`, JSON.stringify(bookmarks)); } catch (error) { console.error(`Error: ${error}`, error); } finally { // ブラウザの停止 if (!!browser) await browser.close(); } } </code></pre> <h4 id="ログイン処理"><a href="#%E3%83%AD%E3%82%B0%E3%82%A4%E3%83%B3%E5%87%A6%E7%90%86">ログイン処理</a></h4> <pre><code class="javascript">/** * ログイン処理 */ async function login(page) { // dotenvからアカウント情報の取得 const account = process.env.TWITTER_ACCOUNT; const password = process.env.TWITTER_PASSWORD; // 指定したURLへ移動: waitを設定 await page.goto("https://twitter.com/", { waitUntil: "networkidle2" }); await page.waitForSelector(`.LoginForm > .LoginForm-username > .text-input`); // アカウントとパスワード入力 await page.type(`.LoginForm > .LoginForm-username > .text-input`, account); await page.type(`.LoginForm > .LoginForm-password > .text-input`, password); // ログインボタンを押して、ページ遷移するまで待つ const navigationPromise = page.waitForNavigation(); await page.click(` .LoginForm > .EdgeButton`); await navigationPromise; } </code></pre> <h4 id="ブックマークのエクスポート処理"><a href="#%E3%83%96%E3%83%83%E3%82%AF%E3%83%9E%E3%83%BC%E3%82%AF%E3%81%AE%E3%82%A8%E3%82%AF%E3%82%B9%E3%83%9D%E3%83%BC%E3%83%88%E5%87%A6%E7%90%86">ブックマークのエクスポート処理</a></h4> <p>くり返す処理はこんな感じ。<br /> ツイートは<code><article></code>タグのようなので、それを起点に処理を進めていく。</p> <pre><code class="javascript">async function getTwitterBookmarks(browser, page) { const bookmarks = []; try { // ブックマークに移動 const bookmarksURL = "https://twitter.com/i/bookmarks"; await page.goto(bookmarksURL, { waitUntil: "networkidle2" }); // ブックマークしたツイートのHTML要素の取得 const articles = await page.$$("article"); for (let i = 0; i < articles.length; i++) { const article = articles[i]; // ツイートまでスクロール await page.evaluate(elm => window.scrollBy(0, elm.getBoundingClientRect().top), article); await page.waitFor(1000); // articleから情報を取得(別処理) const data = await toArticleData(browser, page, article); bookmarks.push(data); // ブックマークの削除(別処理) await deleteBookmark(browser, page, article); } } catch (error) { console.error(`** Error occuerred: ${error}`, error); } return bookmarks; } </code></pre> <p>無限ローディングを持つような場合、適宜スクロールしないと要素が表示されないので、<br /> ツイートごとにスクロールしている。</p> <h5 id="ブックマークしたツイートから情報を取得"><a href="#%E3%83%96%E3%83%83%E3%82%AF%E3%83%9E%E3%83%BC%E3%82%AF%E3%81%97%E3%81%9F%E3%83%84%E3%82%A4%E3%83%BC%E3%83%88%E3%81%8B%E3%82%89%E6%83%85%E5%A0%B1%E3%82%92%E5%8F%96%E5%BE%97">ブックマークしたツイートから情報を取得</a></h5> <p>かなりTwitterの仕様によっているけど</p> <ol> <li>取得したい要素を特定して、</li> <li>その要素を取得するセレクタを書き、</li> <li>innterTextやtextContentで文字を取得する</li> </ol> <p>といった、感じのことをしている。</p> <pre><code class="javascript">async function toArticleData(browser, page, article) => { // 初期化 const articleData = { accountName: "", accountId: "", accountURL: "", tweetText: "", tweetURL: "", links: [] }; // ツイートしたユーザのアカウント名とTwitterIdを取得 const account = "div > div:nth-of-type(2) > div:nth-of-type(2) > div:nth-of-type(1)"; const accountName = await article.$(`${account} a > div:nth-of-type(1) > div:nth-of-type(1)`); const accountId = await article.$(`${account} a > div:nth-of-type(1) > div:nth-of-type(2)`); articleData.accountName = await accountName.evaluate(node => node.innerText); articleData.accountId = await accountId.evaluate(node => node.innerText); // ツイートの内容を取得 const tweetData = "div > div:nth-of-type(2) > div:nth-of-type(2)"; const tweet = await article.$(`${tweetData} > div:nth-of-type(2)`); const tweetText = await tweet.evaluate(node => node.innerText); articleData.tweetText = tweetText; // ツイートに含まれるリンク(<a>)をすべて取得 const aTags = await article.$$(`${tweetData} a`); for (let i = 0; i < aTags.length; i++) { const aTag = aTags[i]; const text = await aTag.evaluate(node => node.textContent); const link = await aTag.evaluate(node => node.href); articleData.links.push({ link: link, text: text }); } // <a>の1つ目はユーザのURL articleData.accountURL = articleData.links[0].link; // <a>の2つ目はツイートのURL articleData.tweetURL = articleData.links[1].link; articleData.links.splice(0, 2); return articleData; }; </code></pre> <h5 id="ブックマークの削除"><a href="#%E3%83%96%E3%83%83%E3%82%AF%E3%83%9E%E3%83%BC%E3%82%AF%E3%81%AE%E5%89%8A%E9%99%A4">ブックマークの削除</a></h5> <pre><code class="javascript">async deleteBookmark(browser, page, article) { const waitTime = 1500; // 待ち時間 // 削除対象までスクロール await page.evaluate(elm => window.scrollBy(0, elm.getBoundingClientRect().top), article); await page.waitFor(1000); // 「ツイートを共有」ボタンをクリック const button = await article.$("div[aria-label='ツイートを共有']"); await page.evaluate(v => v.click(), button); // すこし待つ await page.waitFor(waitTime); // クリックするとメニューが出てくるので、取得 const menuItems = await page.$$("div[role='menuitem']"); // 非公開アカウントかどうかにより、メニューの数が変わるの処理を分ける if (menuItems.length === 3) { // 通常、メニューが3つあり、2つ目が削除ボタン await menuItems[1].click(); await page.waitFor(waitTime); } else if (menuItems.length === 1) { // 非公開の場合は、削除ボタンのみ表示 await menuItems[0].click(); await page.waitFor(waitTime); } }; </code></pre> <p>こんな感じで、「要素を探す→クリック→少し待つ」のくり返し。<br /> ただ、ブラウザで操作しているときでも、削除されないときがある。。</p> <h2 id="使ってみた感想"><a href="#%E4%BD%BF%E3%81%A3%E3%81%A6%E3%81%BF%E3%81%9F%E6%84%9F%E6%83%B3">使ってみた感想</a></h2> <p>スクレイピング自体始めてだったけど、puppeteer自体がすごくよく、簡単に使うことができた(<em>´ω`</em>)</p> <p>ただ、Twitterみたいなのを対象にするのは結構大変だった。。</p> <h5 id="1. どうセレクタを書けば、期待する要素をとってこれるのかを考えないといけない"><a href="#1.+%E3%81%A9%E3%81%86%E3%82%BB%E3%83%AC%E3%82%AF%E3%82%BF%E3%82%92%E6%9B%B8%E3%81%91%E3%81%B0%E3%80%81%E6%9C%9F%E5%BE%85%E3%81%99%E3%82%8B%E8%A6%81%E7%B4%A0%E3%82%92%E3%81%A8%E3%81%A3%E3%81%A6%E3%81%93%E3%82%8C%E3%82%8B%E3%81%AE%E3%81%8B%E3%82%92%E8%80%83%E3%81%88%E3%81%AA%E3%81%84%E3%81%A8%E3%81%84%E3%81%91%E3%81%AA%E3%81%84">1. どうセレクタを書けば、期待する要素をとってこれるのかを考えないといけない</a></h5> <p>特にscoped CSSを使っていて、class名がないdivばかりだとつらい</p> <h5 id="2. SPAなど動的に変わる部分が多いサイトだと、クリックなどがうまく動かないことがある"><a href="#2.+SPA%E3%81%AA%E3%81%A9%E5%8B%95%E7%9A%84%E3%81%AB%E5%A4%89%E3%82%8F%E3%82%8B%E9%83%A8%E5%88%86%E3%81%8C%E5%A4%9A%E3%81%84%E3%82%B5%E3%82%A4%E3%83%88%E3%81%A0%E3%81%A8%E3%80%81%E3%82%AF%E3%83%AA%E3%83%83%E3%82%AF%E3%81%AA%E3%81%A9%E3%81%8C%E3%81%86%E3%81%BE%E3%81%8F%E5%8B%95%E3%81%8B%E3%81%AA%E3%81%84%E3%81%93%E3%81%A8%E3%81%8C%E3%81%82%E3%82%8B">2. SPAなど動的に変わる部分が多いサイトだと、クリックなどがうまく動かないことがある</a></h5> <p>対象サイトのJavaScriptが正しく動作しない場合がある。。</p> <h5 id="3. 実行や動作確認に時間がかかるので、テストにかなり時間がかかる"><a href="#3.+%E5%AE%9F%E8%A1%8C%E3%82%84%E5%8B%95%E4%BD%9C%E7%A2%BA%E8%AA%8D%E3%81%AB%E6%99%82%E9%96%93%E3%81%8C%E3%81%8B%E3%81%8B%E3%82%8B%E3%81%AE%E3%81%A7%E3%80%81%E3%83%86%E3%82%B9%E3%83%88%E3%81%AB%E3%81%8B%E3%81%AA%E3%82%8A%E6%99%82%E9%96%93%E3%81%8C%E3%81%8B%E3%81%8B%E3%82%8B">3. 実行や動作確認に時間がかかるので、テストにかなり時間がかかる</a></h5> <p>あと、サイトのデザインが変わると追従対応しないといけない。。<br /> 便利だけど、かなり大変そうな感じ(<em>´ω`</em>)</p> <p>けど、ポイントを守ればかなり便利だなと、今更ながら体感(<em>´ω`</em>)</p> <h2 id="こんなのつくってます!!"><a href="#%E3%81%93%E3%82%93%E3%81%AA%E3%81%AE%E3%81%A4%E3%81%8F%E3%81%A3%E3%81%A6%E3%81%BE%E3%81%99%21%21">こんなのつくってます!!</a></h2> <p>積読用の読書管理アプリ 『積読ハウマッチ』をリリースしました!<br /> <a target="_blank" rel="nofollow noopener" href="https://tsundoku.site">積読ハウマッチ</a>は、Nuxt.js+Firebaseで開発してます!</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/572d4947-f40b-e4dc-1c9c-bc584cd2a66c.png" width="200"/></p> <p>もしよかったら、遊んでみてくださいヽ(=´▽`=)ノ</p> <p>要望・感想・アドバイスなどあれば、<br /> 公式アカウント(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/MemoryLoverz">@MemoryLoverz</a>)や開発者(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/kira_puka">@kira_puka</a>)まで♪</p> <h1 id="参考にしたサイト様"><a href="#%E5%8F%82%E8%80%83%E3%81%AB%E3%81%97%E3%81%9F%E3%82%B5%E3%82%A4%E3%83%88%E6%A7%98">参考にしたサイト様</a></h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/kanoe/items/9043a81d28a1b733b2e2">Puppeteerのセットアップから使い方まで〜ブラウザ操作の自動化〜 - Qiita</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://pptr.dev/">Puppeteer v1.20.0</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://codeday.me/jp/qa/20190513/814601.html">PuppeteerによるJavaScriptレンダリングされたHTMLの取得 - コードログ</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/unhurried/items/56ea099c895fa437b56e#1-%E4%B8%80%E5%AE%9A%E6%99%82%E9%96%93%E3%83%8D%E3%83%83%E3%83%88%E3%83%AF%E3%83%BC%E3%82%AF%E9%80%9A%E4%BF%A1%E3%81%AE%E3%81%AA%E3%81%84%E3%81%93%E3%81%A8%E3%81%A7%E5%AE%8C%E4%BA%86%E3%82%92%E5%88%A4%E5%AE%9A%E3%81%99%E3%82%8B">puppeteerを使ったスクレイピング - Qiita</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://gist.github.com/nnks1010/10abf3032933609d1b2bd5b99fc12586">絶対顔本スクレイピングするマン</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/go_sagawa/items/85f97deab7ccfdce53ea">puppeteerでの要素の取得方法 - Qiita</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://stocker.jp/diary/nth-child/">CSSのnth-childとnth-of-typeについて基本から学ぼう | Stocker.jp / diary</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/tomi_linka/items/a68cf7840c3da002c6e0#%E3%83%AC%E3%83%B3%E3%83%80%E3%83%AA%E3%83%B3%E3%82%B0%E3%82%92%E5%BE%85%E3%81%A4">puppeteerでスクレイピング - Qiita</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/rh_taro/items/32bb6851303cbc613124#%E9%81%B7%E7%A7%BB%E3%82%92%E5%BE%85%E3%81%A4-%E3%81%9D%E3%81%AE2">puppeteerでよく使うであろう処理の書き方 - Qiita</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/AYA_iro/items/e5dd0956b3ba82f6bf31">puppeteerを体験してみた - Qiita</a></li> </ul> きらぷか@積読ハウマッチ/SSSAPIなど tag:crieit.net,2005:PublicArticle/15042 2019-05-31T21:35:43+09:00 2019-05-31T21:35:43+09:00 https://crieit.net/posts/Twitter-TL TwitterのTLを見せあいっこできるミニサービスを作りました <p>Twitterの自分のタイムラインは自分しか見ることができない特別な世界ですが、それをシェアして見せあいっこすることができる「TLシェア」というミニサービスを作りました。</p> <p><a href="https://crieit.now.sh/upload_images/8c23e18a7a344fca6bd592674e642b445cf11f36c8d08.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/8c23e18a7a344fca6bd592674e642b445cf11f36c8d08.png?mw=700" alt="TLシェアのイメージ" /></a></p> <h2 id="使っているもの"><a href="#%E4%BD%BF%E3%81%A3%E3%81%A6%E3%81%84%E3%82%8B%E3%82%82%E3%81%AE">使っているもの</a></h2> <ul> <li>PHP7.3</li> <li>Laravel5.8</li> <li>Vue.js2.5</li> <li>Buefy</li> <li>Google Compute Engine (f1-micro)</li> <li>Google Cloud SQL (MySQL5.7)</li> </ul> <h2 id="どうやっているのか"><a href="#%E3%81%A9%E3%81%86%E3%82%84%E3%81%A3%E3%81%A6%E3%81%84%E3%82%8B%E3%81%AE%E3%81%8B">どうやっているのか</a></h2> <p>TwitterのAPIは当然のように自分のTLを取得できます。それを自分用ではなく公開用として誰でも閲覧できるように表示している、というだけになります。</p> <h2 id="具体的な処理"><a href="#%E5%85%B7%E4%BD%93%E7%9A%84%E3%81%AA%E5%87%A6%E7%90%86">具体的な処理</a></h2> <h3 id="Laravel Socialite"><a href="#Laravel+Socialite">Laravel Socialite</a></h3> <p><a target="_blank" rel="nofollow noopener" href="https://laravel.com/docs/5.8/socialite">Laravel Socialite</a>を使うと簡単にTwitter認証ができますので、ちょちょいと入れて認証します。今回はミニアプリにする予定だったので、ログイン機能もつけませんでした。認証した時にタイムラインをデータベースに保存します。</p> <p>ただ、一応タイムラインの定期更新機能もあるため、トークンなどは保存しています。</p> <h3 id="タイムラインの定期更新"><a href="#%E3%82%BF%E3%82%A4%E3%83%A0%E3%83%A9%E3%82%A4%E3%83%B3%E3%81%AE%E5%AE%9A%E6%9C%9F%E6%9B%B4%E6%96%B0">タイムラインの定期更新</a></h3> <p>上記で行った処理を、<a target="_blank" rel="nofollow noopener" href="https://laravel.com/docs/5.8/artisan">LaravelのCommand</a>で定期的に実行しているだけです。</p> <p>こんな感じでTLが古くなっている人から優先的に、1時間に1回くらいの頻度でタイムラインを更新していきます。</p> <p>```php:UpdateTimeline.php<br /> public function handle()<br /> {<br /> $user = User::where('is_auto', true)->orderBy('timeline_updated_at')->first();<br /> if (Carbon::parse($user->timeline_updated_at)->gt(Carbon::now()->subHour())) {<br /> return;<br /> }</p> <pre><code> try { Timeline::createAndActivate($user); } catch (\Exception $e) { // 例外を握りつぶす $this->error($e); } DB::table('users')->where('id', $user->id)->update(['timeline_updated_at' => new Carbon]); } </code></pre> <pre><code><br />最後DBを使っているのはupdated_atを更新させないためです。 ### APIの実行 下記を使っています。 [abraham/twitteroauth: The most popular PHP library for use with the Twitter OAuth REST API.](https://github.com/abraham/twitteroauth) こんな感じでTLを取得できます。 ```php $connection = new TwitterOAuth( config('services.twitter.client_id'), config('services.twitter.client_secret'), $userSocial->token, $userSocial->token_secret ); $contents = $connection->get('statuses/home_timeline', ['count' => 200]); </code></pre> <p>home_timelineは1分に1回程度しか呼べませんのでTwitterクライアントを作る時はなかなか厳しい制限ですが、今回のパターンだとさほど更新頻度は必要ないため問題なさそうです。</p> <h2 id="気をつけていること"><a href="#%E6%B0%97%E3%82%92%E3%81%A4%E3%81%91%E3%81%A6%E3%81%84%E3%82%8B%E3%81%93%E3%81%A8">気をつけていること</a></h2> <p>ツイートを公開するにあたりいくつか気をつけていることがあります。ツイートを表示してはいけない、という部分ではありますが、これを提示することで安心して使ってもらえるようにして利用してくれる方を増やす、という部分としても役に立つのではないかと思います。(まあそれはあくまでもガチ運用したい場合になると思いますが)</p> <h3 id="鍵アカウントのツイートを表示しないようにする"><a href="#%E9%8D%B5%E3%82%A2%E3%82%AB%E3%82%A6%E3%83%B3%E3%83%88%E3%81%AE%E3%83%84%E3%82%A4%E3%83%BC%E3%83%88%E3%82%92%E8%A1%A8%E7%A4%BA%E3%81%97%E3%81%AA%E3%81%84%E3%82%88%E3%81%86%E3%81%AB%E3%81%99%E3%82%8B">鍵アカウントのツイートを表示しないようにする</a></h3> <p>自分のタイムラインには鍵アカの人のツイートも表示されてしまいます。そのまま公開すると不正にその方々のツイートを表示してしまうことになりますので、それを表示しないようにする必要があります。</p> <p>具体的には<a target="_blank" rel="nofollow noopener" href="https://developer.twitter.com/en/docs/tweets/data-dictionary/overview/user-object.html">User object</a>に<code>protected</code>という値がありますので、それが<code>true</code>の場合は表示しないようにします。</p> <h3 id="センシティブなツイートを表示しないようにする"><a href="#%E3%82%BB%E3%83%B3%E3%82%B7%E3%83%86%E3%82%A3%E3%83%96%E3%81%AA%E3%83%84%E3%82%A4%E3%83%BC%E3%83%88%E3%82%92%E8%A1%A8%E7%A4%BA%E3%81%97%E3%81%AA%E3%81%84%E3%82%88%E3%81%86%E3%81%AB%E3%81%99%E3%82%8B">センシティブなツイートを表示しないようにする</a></h3> <p>センシティブなツイートを適当に公開してしまうと問題がありますので、それも同様に表示しないようにします。</p> <p><a target="_blank" rel="nofollow noopener" href="https://developer.twitter.com/en/docs/tweets/data-dictionary/overview/tweet-object">Tweet object</a>の<code>possibly_sensitive</code>が<code>true</code>の場合は表示しないようにします。</p> <p>ただ、これに関してはデフォルトでは非表示にして、確認ボタンを押してくれた人だけ閲覧可能、というふうにすれば一般公開されないのでアリかもしれません。</p> <h2 id="こだわった所"><a href="#%E3%81%93%E3%81%A0%E3%82%8F%E3%81%A3%E3%81%9F%E6%89%80">こだわった所</a></h2> <h3 id="Twitterと同じようなUI"><a href="#Twitter%E3%81%A8%E5%90%8C%E3%81%98%E3%82%88%E3%81%86%E3%81%AAUI">Twitterと同じようなUI</a></h3> <p><del>どうでもいい部分なのですが、</del> Twitterっぽく見せた方が安心感があるし、真似てみるのも面白いかなと思い見た目を同じ感じにしてみました。(OGPは表示できませんがそれ以外を)</p> <p>追記)<br /> <a target="_blank" rel="nofollow noopener" href="https://developer.twitter.com/en/developer-terms/display-requirements.html">表示要件</a> が定められているためそちらに従う必要があるようです。(対応できていない部分がありそうですので随時調整していきます)</p> <p><a href="https://crieit.now.sh/upload_images/451944a30f43345ec5385183071afef65cf11f652c7f2.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/451944a30f43345ec5385183071afef65cf11f652c7f2.png?mw=700" alt="TwitterっぽいUI" /></a></p> <p>Buefyをsassで読み込む前にprimaryカラーだけTwitterの色と同じにしています。</p> <pre><code class="scss">$primary: rgb(29, 161, 242) !default; @import '~buefy/src/scss/buefy-build'; </code></pre> <h3 id="アイキャッチ画像"><a href="#%E3%82%A2%E3%82%A4%E3%82%AD%E3%83%A3%E3%83%83%E3%83%81%E7%94%BB%E5%83%8F">アイキャッチ画像</a></h3> <p>最近、ぱっと見て一瞬でサービス内容が分かるかわいい画像づくりにハマっているので今回も冒頭に貼っていたような画像を作りました。トップページに貼ってアクセスしてくれた人にすぐ分かってもらえるようにし、あとTwitterでシェアされた際もOGPとして表示することで興味を持ってもらいやすくしてみました。</p> <p>文字でダラダラ説明を書くよりもぱっと見で分かる画像を作った方が理解してもらえる確率も上がるのではないかと思います。</p> <p>僕はデザイナーでもなく有料のソフトを使っていないので、昔からInkscapeを使っています。自分でガッツリ画像を作るとなるとわかりませんが、フリー素材を切り貼りして画像を作るのであればそれで十分快適だと思います。</p> <p>今回は下記の素材を使いました。おすすめです。</p> <p><a target="_blank" rel="nofollow noopener" href="https://hiyokoyarou.com/">ゆるくてかわいい無料イラスト素材屋「ぴよたそ」</a></p> <p>画像中のフォントは下記です。これも最近かなりハマっています。</p> <p><a target="_blank" rel="nofollow noopener" href="http://modi.jpn.org/font_mushin.php">無心 - フリーフォントのMODI工場</a></p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>ということで、元々思いつきのアイデアをミニサービスとして作る、という感じだったので一瞬で完成しました。(多分一番こだわったUIの調整と画像作成に一番時間がかかっているかもしれません)</p> <p>よろしければ遊んでみてください~。</p> <p><a target="_blank" rel="nofollow noopener" href="https://tlshare.appllis.net/">TLシェア - みんなのTwitterタイムラインを公開しよう</a></p> <p>もし少しでも参考になった部分があったらぜひ「いいね」をお願いします!</p> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/14941 2019-04-21T22:51:18+09:00 2019-04-23T09:27:29+09:00 https://crieit.net/posts/JobLv-BLOG JobLvマネージャーをリリースした話 <h1 id="作ったサイトの紹介"><a href="#%E4%BD%9C%E3%81%A3%E3%81%9F%E3%82%B5%E3%82%A4%E3%83%88%E3%81%AE%E7%B4%B9%E4%BB%8B">作ったサイトの紹介</a></h1> <p>今回作ったサイトは、こちら。<br />  「<a target="_blank" rel="nofollow noopener" href="https://joblv-manager.herokuapp.com/">JobLvマネージャー</a>」です。</p> <p>自分のJob(職業)を管理、LvUpをしながら、<br /> Twitterと連携(現時点では、名前変更)することが出来ます。</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/118481/24d49170-feba-5d5b-c900-aabce0268d16.png" alt="image" /></p> <h1 id="作ろうと思ったキッカケ"><a href="#%E4%BD%9C%E3%82%8D%E3%81%86%E3%81%A8%E6%80%9D%E3%81%A3%E3%81%9F%E3%82%AD%E3%83%83%E3%82%AB%E3%82%B1">作ろうと思ったキッカケ</a></h1> <p>Twitterで繋がったユーザーさんが、名前の後ろに「〇〇Lv1」みたいなのをつけているのを見て思いつきました。<br /> いつの間にか、こっそりLvアップしていたり、新しいものに変わっていたり。<br /> そういう遊び心に興味が湧いて、この仕組みをもっと簡単に、管理や変更ができるようにしたサービスを作ってみたいと思ったのが始まりです。</p> <h1 id="ストックすることの大切さと、一歩踏み出す大切さ"><a href="#%E3%82%B9%E3%83%88%E3%83%83%E3%82%AF%E3%81%99%E3%82%8B%E3%81%93%E3%81%A8%E3%81%AE%E5%A4%A7%E5%88%87%E3%81%95%E3%81%A8%E3%80%81%E4%B8%80%E6%AD%A9%E8%B8%8F%E3%81%BF%E5%87%BA%E3%81%99%E5%A4%A7%E5%88%87%E3%81%95">ストックすることの大切さと、一歩踏み出す大切さ</a></h1> <p>これをタイトルした理由は、本システム、3日(時間だと8時間)で作りました。<br /> 内容からして、これが早いのかわかりませんが、これだけの時間でサービスリリースまでいけたのは、</p> <ul> <li>Railsのおかげ</li> <li>素材サイト様のおかげ</li> <li>API(Twitter)のおかげ</li> <li>今まで色々プロダクトを作ったおかげ(機能のより集め(コピペ))</li> </ul> <p>Railsを使うと、簡単のWebシステムが作れます。<br /> いろんなGemが用意されています。<br /> 規約にそえば、簡単に作れます。</p> <p>素材サイト様、画像にアイコンにフォントまで、いろんなものが無料で使えます。<br /> いつもありがとうございます!</p> <p>API使えば、別のシステムの機能を自分のもののように使えます。どんどん使いましょう!</p> <p>新しいもの、面白そうなものがあったら、とりあえず触ってみましょう!<br /> 形にならなくても問題ありません。いつか使える時が来ます。ストックしておきましょう。<br /> 最新の技術だからと追いかけて、サービスリリースする必要も無いと思います。<br /> 触ってみてストックする事が大事。</p> にゅ〜ぶる tag:crieit.net,2005:PublicArticle/14909 2019-04-09T22:21:31+09:00 2019-04-19T09:16:52+09:00 https://crieit.net/posts/800-Twitter-Negomo 誰でも新卒年収800万宣言できる、Twitter就活サービス「Negomo(ネゴモ)」をリリースした。【個人開発】 <p>「Twitter就活の促進」をテーマにしたサービス「<a target="_blank" rel="nofollow noopener" href="https://negomo.me">Negomo(ネゴモ)</a>」をリリースしました!</p> <blockquote class="twitter-tweet"><p lang="ja" dir="ltr">自分が欲しい報酬額をツイートしてTwitter就活できるサービス「Negomo(ネゴモ)」を作ってみました!下のような画像がツイートできます。Twitterアカウントで利用できるのでぜひ使ってみてください!<a target="_blank" rel="nofollow noopener" href="https://t.co/JRXFe8lHov">https://t.co/JRXFe8lHov</a><a target="_blank" rel="nofollow noopener" href="https://twitter.com/hashtag/Negomo?src=hash&ref_src=twsrc%5Etfw">#Negomo</a> <a target="_blank" rel="nofollow noopener" href="https://twitter.com/hashtag/RT%E6%8B%A1%E6%95%A3%E3%81%8A%E9%A1%98%E3%81%84%E3%81%97%E3%81%BE%E3%81%99?src=hash&ref_src=twsrc%5Etfw">#RT拡散お願いします</a></p>— TaKO8Ki (@takoyaki3160) <a target="_blank" rel="nofollow noopener" href="https://twitter.com/takoyaki3160/status/1114776315847004160?ref_src=twsrc%5Etfw">April 7, 2019</a></blockquote> <p>Twitterアカウントで利用できるので、もしよかったら使ってみてください!</p> <h1 id="リリースしたサービス"><a href="#%E3%83%AA%E3%83%AA%E3%83%BC%E3%82%B9%E3%81%97%E3%81%9F%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9">リリースしたサービス</a></h1> <p><a target="_blank" rel="nofollow noopener" href="https://negomo.me">Negomo(ネゴモ) - もっとTwitter就活しよう!</a></p> <h1 id="サービスの概要"><a href="#%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%81%AE%E6%A6%82%E8%A6%81">サービスの概要</a></h1> <p>「Negomo(リンク)」は、自分が欲しい報酬額をツイートしてTwitter就活できるサービスです。</p> <p>何か技術を身につけている人が、Twitterで欲しい報酬額(時給・年収)を積極的にアピールすることで、適切な報酬をもらえることを理想としています!<br /> もちろん、エンジニア以外の方も大歓迎です!</p> <p>こんな感じのツイートができます。</p> <blockquote class="twitter-tweet"><p lang="ja" dir="ltr">京都でWEB系のインターン探してます!! <a target="_blank" rel="nofollow noopener" href="https://t.co/JRXFe8lHov">https://t.co/JRXFe8lHov</a> <a target="_blank" rel="nofollow noopener" href="https://twitter.com/hashtag/Negomo?src=hash&ref_src=twsrc%5Etfw">#Negomo</a> <a target="_blank" rel="nofollow noopener" href="https://twitter.com/hashtag/Twitter%E5%B0%B1%E6%B4%BB?src=hash&ref_src=twsrc%5Etfw">#Twitter就活</a></p>— TaKO8Ki (@takoyaki3160) <a target="_blank" rel="nofollow noopener" href="https://twitter.com/takoyaki3160/status/1114806623942262784?ref_src=twsrc%5Etfw">April 7, 2019</a></blockquote> <p>参考:<a target="_blank" rel="nofollow noopener" href="https://negomo.me/about">Negomo(ネゴモ)とは</a></p> <h2 id="追記"><a href="#%E8%BF%BD%E8%A8%98">追記</a></h2> <ul> <li><p>単位で「兆」を選ぶことができるようになりました!</p></li> <li><p>時給・月収・年収によって画像の色を変えたほうが見やすいというアドバイスをいただいたので、そのように仕様を変更しました!<br /> 既に画像を作成されている場合は、金額を変えて編集していただくと色が変わります。</p></li> </ul> <p><img width="995" alt="スクリーンショット 2019-04-09 15.46.36.png" src="https://qiita-image-store.s3.amazonaws.com/0/285624/b2f41851-a590-3827-14ed-d74303dfe7e2.png"></p> <h1 id="デザイン"><a href="#%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3">デザイン</a></h1> <p>デザインは<a target="_blank" rel="nofollow noopener" href="https://speakerdeck.com/">SpeakerDeck</a>を参考にしました!</p> <h3 id="トップページ"><a href="#%E3%83%88%E3%83%83%E3%83%97%E3%83%9A%E3%83%BC%E3%82%B8">トップページ</a></h3> <p><img src="https://qiita-image-store.s3.amazonaws.com/0/285624/80f72c3a-d77b-d45f-1ff0-7f9b937d80a2.png" alt="image" /></p> <h3 id="画像作成ページ"><a href="#%E7%94%BB%E5%83%8F%E4%BD%9C%E6%88%90%E3%83%9A%E3%83%BC%E3%82%B8">画像作成ページ</a></h3> <p><a target="_blank" rel="nofollow noopener" href="https://gyazo.com/f5cbe5ee14262aa3025254bae37c6ef7"><img src="https://i.gyazo.com/f5cbe5ee14262aa3025254bae37c6ef7.gif" alt="Image from Gyazo" /></a></p> <p>アピールポイントのフォームの高さは可変的になっていて、コンパクトになるように心がけました。また、ポートフォリオなどのURLが入力された場合は、自動的にリンクに変換されるようになっています。</p> <h3 id="画像表示ページ"><a href="#%E7%94%BB%E5%83%8F%E8%A1%A8%E7%A4%BA%E3%83%9A%E3%83%BC%E3%82%B8">画像表示ページ</a></h3> <p><img src="https://qiita-image-store.s3.amazonaws.com/0/285624/1079aace-eedc-0f02-2549-a342a3dca654.png" alt="image" /></p> <p>作成した画像をクリックすることでツイートできるようになっています。<br /> アピールポイントの欄では、どれがリンクでどれが普通の文字列なのか分かりやすくするようにしました。</p> <h1 id="使用した技術"><a href="#%E4%BD%BF%E7%94%A8%E3%81%97%E3%81%9F%E6%8A%80%E8%A1%93">使用した技術</a></h1> <ul> <li><p>Ruby・Ruby on Rails</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/csquared/IMGKit">IMGKit</a>(HTMLから画像をレンダリングできるgem)</li> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/bgarret/google-analytics-rails">google-analytics-rails</a>(google analyticsが簡単に導入できるgem)</li> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/0sc/activestorage-cloudinary-service">activestorage-cloudinary-service</a>(Active StorageとCloudinaryを繋げるgem)</li> </ul></li> <li><p>JavaScript・jQuery</p></li> <li><a target="_blank" rel="nofollow noopener" href="https://cloudinary.com/">Cloudinary</a><br /> 画像を保存するストレージです。Herokuのアドオンとして利用できるので非常やりやすかったです。Active Storageと連携させて利用しています。</li> <li>Git・GitHub</li> <li>Postgresql</li> <li><a target="_blank" rel="nofollow noopener" href="https://jp.heroku.com/">Heroku</a><br /> 今の所Hobbyプランを利用しています。</li> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/TaKO8Ki/wkhtmltopdf-buildpack">build pack</a><br /> 画像を作る部分の実装で利用しました。</li> <li>アイコン<br /> アイコンは、相変わらずフリー画像にお世話になってます。</li> </ul> <h1 id="苦労した点"><a href="#%E8%8B%A6%E5%8A%B4%E3%81%97%E3%81%9F%E7%82%B9">苦労した点</a></h1> <p>今回苦労したのは、画像生成の部分です。<br /> コードとしては下のような感じで、 HTMLから画像をレンダリングする<a target="_blank" rel="nofollow noopener" href="https://wkhtmltopdf.org/">wkhtmltoimage</a>をRailsで利用できるようになるgem、IMGKitを利用しています。<br /> ローカルでは比較的簡単に動かすことができましたが、Heroku上で動かすのに少し時間がかかりました。<br /> 最終的に、build packを使うのが一番良さそうだと分かったので、元からあったbuild packのレポジトリをforkして少し自分用にカスタマイズして使いました。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/TaKO8Ki/wkhtmltopdf-buildpack">wkhtmltoimageをHerokuで使うためのbuild packカスタマイズ版</a></p> <p>画像作成部分のコード</p> <div> ```ruby def create_image Tempfile.create(["#{@profile.id}", '.png'], :encoding => 'ascii-8bit') do | file | file.write(IMGKit.new(get_html, quality: 20, width: 800).to_png) file.rewind @profile.image.attach(io: file, filename: "q_#{@profile.id}.png", content_type: "image/png") end end def get_html <<~HTML <!DOCTYPE html> <html> <head> <link href="http://fonts.googleapis.com/earlyaccess/notosansjp.css"> <meta charset="UTF-8"> <style> @charset "UTF-8"; html { font-family: sans-serif; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; } body { width: 800px; margin: 0; background: #eee; font-family: 'Noto Sans JP', sans-serif; } .q-frame { width: 100%; background-color: #fff; padding: 25px 25px 10px 25px; } .q-frame .q-body { vertical-align: middle; text-align: center; height: 220px; width: 750px; font-size: 2.6em; background-color: white; padding: 1.3em; border-radius: 3px; display: table-cell; vertical-align: middle; background-image: url(#{assets_image_url('negomo_background.png')}); background-size: 100% 100%; background-repeat: no-repeat; background-position: 50%; box-shadow: 0.5px 3.5px 10px 0px #ccc; } .q-frame .q-icon { font-size: 2.2em; padding: 5px 0px 0px 0px; margin: 0px; color: #333; border-radius: 5px; } .q-frame .q-icon img { width: 150px; margin-top: 7px; } span.word { font-size: 75px; font-weight: bold; color: #7d7458; } span.description { font-size: 30px; font-weight: 100; color: #545454; line-height: 45px; display: block; margin-top: 10px; font-weight: bold; width: 650px; overflow-wrap: break-word; word-wrap: break-word; margin: 0 auto; } </style> </head> <body> <div class="q-frame"> <div class="q-body"> <span class="word">#{@profile.adjust_money_style}</span><br> <span class="description">#{@profile.title}</span> </div> #{} <div class="q-icon"> #{assets_image_tag('negomo.png').html_safe} </div> </div> </body> </html> HTML end ``` </div> <h1 id="リリースする上で心がけたこと"><a href="#%E3%83%AA%E3%83%AA%E3%83%BC%E3%82%B9%E3%81%99%E3%82%8B%E4%B8%8A%E3%81%A7%E5%BF%83%E3%81%8C%E3%81%91%E3%81%9F%E3%81%93%E3%81%A8">リリースする上で心がけたこと</a></h1> <h3 id="アイデアを形にするスピード"><a href="#%E3%82%A2%E3%82%A4%E3%83%87%E3%82%A2%E3%82%92%E5%BD%A2%E3%81%AB%E3%81%99%E3%82%8B%E3%82%B9%E3%83%94%E3%83%BC%E3%83%89">アイデアを形にするスピード</a></h3> <p>少しずつWEBサービスを作ることにも慣れてきたので、自分が思いついたアイデアを形にするスピードを意識するようにしました。それでも、このサービスを作るのに1週間ほど費やしてしまったので、まだまだだなと感じます。</p> <h3 id="ユーザーにとって分かりやすいサービスかどうか"><a href="#%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E3%81%AB%E3%81%A8%E3%81%A3%E3%81%A6%E5%88%86%E3%81%8B%E3%82%8A%E3%82%84%E3%81%99%E3%81%84%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%81%8B%E3%81%A9%E3%81%86%E3%81%8B">ユーザーにとって分かりやすいサービスかどうか</a></h3> <p>今までも「ユーザーにとって分かりやすいサービス作り」を意識するようにはしてきましたが、イマイチ分かりやすさを追求できていないように感じていました。特に、非エンジニアの人に使ってもらうとなると非常に難しいと感じます。<br /> そのため、今回は、大まかに言えば「画像をツイートできるだけのサービス」というシンプルな設計にしました。</p> <h1 id="最後に"><a href="#%E6%9C%80%E5%BE%8C%E3%81%AB">最後に</a></h1> <p>タイトルに「誰でも新卒年収800万宣言できる」と書きましたが、最近、<a target="_blank" rel="nofollow noopener" href="https://twitter.com/komi_edtr_1230?s=17">コミさん</a>という技術的に強い方が新卒年収800万円芸なるものを行なって、話題になっているのを1フォロワーとして眺めていました。エンジニアだけでなく、他の分野にもコミさんのように優れた人材はたくさん居られると思うので、その方達がTwitterなどを利用して、しっかりといい待遇を受けられるようになればいいなと思います。<br /> 僕もその方達に加われるように頑張っていきます!<br /> また、このサービスをきっかけに、もっとTwitter就活が当たり前になれば嬉しいです。</p> <p>ここまで読んでいただきありがとうございました!</p> <p><strong>もしよろしければ、いいねボタンを押していただけたら嬉しいです!</strong></p> <p>Negomo、ぜひ使ってみてください!!</p> <p><a target="_blank" rel="nofollow noopener" href="https://negomo.me">Negomo(ネゴモ) - もっとTwitter就活しよう!</a></p> <h1 id="参考"><a href="#%E5%8F%82%E8%80%83">参考</a></h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://blog.takuchalle.dev/post/2018/12/06/rails_use_wkhtmltoimage/">Rails で質問箱っぽい画像を作るのに wkhtmltoimage を使った</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://razorjack.net/wkhtmltopdf-on-heroku-evaluating-different-installation-options/">wkhtmltopdf on Heroku: evaluating different installation options</a></li> </ul> TaKO8Ki tag:crieit.net,2005:PublicArticle/14891 2019-03-29T23:00:19+09:00 2019-03-29T23:00:19+09:00 https://crieit.net/posts/OGP-Twitter-LINE-8 OGP取得用クローラのユーザーエージェントを、TwitterやLINEなど8サービスで調べてみた <p>SNSなどで、投稿されたWebサイトのリンクがリッチに表示される「OGP (Open Graph Protocol)」機能。<br /> Webサイトに簡単なメタタグを組み込むだけでOGP表示されるため、対応するサイトは増えています。<br /> しかし、OGP用のメタタグは一般ユーザーからは見えないし、サムネイル動的生成などでサーバーに大きな負荷がかかることもあるので、できればOGP取得用クローラ以外にはメタタグを生成しないようにしたいもの。<br /> そこで、UA(ユーザーエージェント)からクローラによるアクセスか判定するために、実際にOGP取得用クローラのUAを記録して調べてみました。</p> <h4 id="結果"><a href="#%E7%B5%90%E6%9E%9C">結果</a></h4> <p>2019年3月28日に記録された文字列をそのまま掲載しています。</p> <div class="table-responsive"><table> <thead> <tr> <th>サービス</th> <th>User Agent</th> </tr> </thead> <tbody> <tr> <td>Twitter</td> <td>Twitterbot/1.0</td> </tr> <tr> <td>Facebook</td> <td>facebookexternalhit/1.1 (+http://www.facebook.com/externalhit_uatext.php)</td> </tr> <tr> <td>LINE</td> <td>facebookexternalhit/1.1;line-poker/1.0</td> </tr> <tr> <td>メッセージ(iOS)</td> <td>Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_1) AppleWebKit/601.2.4 (KHTML, like Gecko) Version/9.0.1 Safari/601.2.4 facebookexternalhit/1.1 Facebot Twitterbot/1.0</td> </tr> <tr> <td>Discord</td> <td>Mozilla/5.0 (compatible; Discordbot/2.0; +https://discordapp.com)</td> </tr> <tr> <td>Skype</td> <td>Mozilla/5.0 (Windows NT 6.1; WOW64) SkypeUriPreview Preview/0.5</td> </tr> <tr> <td>Slack</td> <td>Slackbot-LinkExpanding 1.0 (+https://api.slack.com/robots)</td> </tr> <tr> <td>Plurk</td> <td>Mozilla/5.0 (compatible; PlurkBot/1.0; +https://www.plurk.com/) Firefox/61.0</td> </tr> </tbody> </table></div> <p>もし他にもOGP表示に対応しているサービスをご存知でしたら、<a target="_blank" rel="nofollow noopener" href="https://twitter.com/barley_ural">@barley_ural</a>までご連絡いただければ調査して追記していきます。</p> <p>おわり</p> ウラル