この記事は夏のブログリレー32日目の記事です。
こんにちは! 24Bのきつねでございます。今年traPで新たに企画された『1-Monthon』という1ヶ月規模のハッカソンにてleaQというtraP部員限定のSNSをチーム開発したので、チームを代表してイベントやプロダクトの紹介をしていこうと思います。
1-Monthon とは
1-Monthon(ワンマンソン)はその名の通り1ヶ月をかけて自由にゲームやサービスを開発するイベントです。traPでは毎年6月と12月に2日間のハッカソンが開催されますが、夏休み前半の1ヶ月をかけてハッカソンをしようという企画が今年新たに立ち上がりました。春ハッカソンでは大岡山キャンパス現地での開発がメインでしたが、今回は時期が夏休みとあって帰省をする部員も多く、また開発が長期に渡るためどのチームも基本的にはオンラインでコミュニケーションを取りながら開発を進めることになりました。
また、1-Monthonでは通常の短期ハッカソンとは異なり多くのチームが一学年、あるいは一人で構成されていました。我々第5班は全員が24B(学士1年)の4人チームで、しかも皆集団開発の経験がほとんどないというかなりの挑戦になりましたが、その環境で全力で開発に取り組んだおかげでそれぞれが大きく成長できたと思います。
1ヶ月がかりの本気の開発とあってとてつもないクオリティの作品の数々で、最終発表会は驚きの連続でした。この後も1-Monthonに関する記事がいくつか投稿されると思いますので、そちらもぜひ見ていってください!
leaQ について
UIと仕様
leaQは、主にスクリーンショットを投稿するための部内SNSです。お気付きの通り、名前は『leak(漏洩)』からとっています。とても物騒ですが、ロゴもUIも至って清潔です。
上図はタイムラインの様子です。新たに投稿されたスクリーンショットは全てタイムラインに表示されるようになっています。タイムラインは横スクロールでき、左が新しく、右へ進むほど時系列を遡ることになります。タグの検索結果もほぼ同じ画面構成になっています。
スクリーンショットの投稿時には概要のテキストと複数のタグを自由に登録することができます。traPに存在する7つの班の名称は予め班タグとして特別扱いをしました。具体的にはそれぞれの特色に合わせて色を付け、サイドバーに班タグ検索に飛べるボタンを表示し、アップロード画面で投稿に登録するタグの選択肢にドラフトとして表示しています。これにより、それぞれの班の活動に関連するスクリーンショットの新着投稿をまとめて閲覧しやすくなっています。
投稿にはスタンプをつけたりコメントを書き込んだりすることができます。スタンプはtraPの既存の部内SNSであるtraQで盛んな文化のひとつであり、一般に提供されているもののほかに部員によって日々新たなおもしろカスタムスタンプが作られていますが、その全てがleaQでも使えるようになっています。また、あとで見返したい投稿をブックマークに登録することができます。
leaQの大きな特徴として、ユーザーの最後の投稿から24時間が経過するとそのユーザーから見たタイムラインの更新が停止し、それ以降の投稿は新たにユーザー自身が投稿を作成するまで閲覧できなくなる仕様になっています。これはBeRealのシステムに着想を得て実装を決めました。詳しくは次章で触れています。
コンセプトと命名
初期のコンセプトは『traP版BeReal』でした。BeRealは即時性によるリアルを追求するSNSとして、日本でも特に若年層の間で社会問題になるほどの広がりを見せています。BeRealの特性をおさらいすると、1日1度ランダムな時刻に一斉に通知が届き、それから2分以内に投稿しなければユーザーの投稿が遅刻として表示されるようになっています。また、ユーザー自身が投稿するまでその日の他ユーザーの投稿を見られないという仕様があり、それがユーザーを虜にする中毒性の根拠になっているとも言われています。
さて、traPは巨大な創作サークルであり、顔よりアイコンを認識し、本名より活動名で呼び合う独特の文化を内包しています。今回の1-Monthonに限らず、部員はとくに必要がなければ自宅でPCやペンタブに向かってサークルの創作や開発を進めることが一般的です。従って、ある意味でtraPにおけるリアルはむしろスクリーンの中にあり、traP版BeRealをコンセプトとするSNSはセルフィーよりスクリーンショットを投稿するものであれば良いだろう、というのがこのSNSの発想の原点です。
とはいえ、このコンセプトを追求することで、単なる流行りに乗ったネタ開発の域を超え、実際にtraPの創作活動を助太刀するサービスへと昇華させられる可能性をも感じました。traPは音楽、イラスト、ゲーム制作、サービス開発、競技プログラミングなど幅広い分野の創作・開発で技術を磨いた先人に溢れています。しかし、成果物こそ盛んに共有すれど、彼らが如何にして素晴らしい作品を作り上げるのか、そのリアルな制作風景の共有は思うほど活発ではないように感じます。そこで、このSNSはスクリーンショットの投稿という形で創作におけるノウハウを共有・蓄積していくためのツールとしてサークルに貢献できるのではないかと考えました。例えばDAWの画面を覗けば使われている外部音源の名前を知ることができますし、ペイントソフトの画面を覗けばブラシやレイヤー分けなどヒントが詰まっています。もちろん、創作中の風景のみならずプレイ中のゲームのスクリーンショットなども含め、その画面に映るリアルの投稿を通じて部員同士の交流や技術の高め合いの契機を生む役割を果たせれば本望と考えました。
ただし、スクリーンショットのSNSを作成するならば当然情報漏洩の危険性を警戒すべきです。PCやスマートフォンのスクリーンショットには我々が思うより多くの情報が隠れており、その中には個人情報やプライバシーに係る情報、あるいは開発や契約に関わる機密情報なども含まれている可能性があります。こうした危険に対する自戒の意を込め、またスクリーンショットに隠された創作技術を(必ずしも意図せず)共有するという側面も踏まえ、本SNSをleaQと命名しました。traP限定のサービス故に一般的なSNSに比べて情報漏洩のリスクは抑えられるとはいえ、創作技術をリークしつつ本当に秘密にしておきたい情報はリークしないように気を付けて使っていただけたら嬉しいですね。
開発の流れ・実装
チーム全体
7/13に1-Monthonの軽い説明会とチーム初顔合わせがありました。この日に4人顔を合わせてサービスの案を色々出し合い、その中で『traP版BeRealを作る』という今回の開発に繋がる案が有力視され、後日Discordで開いた自主ミーティングで正式に決定されました。
7/30からの期末試験期間を経て8/7に夏休みに突入し、それから数日後の8/12に1-Monthonの制作が開始されました。制作開始前の8/10に大岡山駅前のサイゼリヤにて自主的に食事会を開くなどしてチームの親睦を深めました。
8/25に中間発表、9/8に最終発表がありました。各々先に旅行の予定を入れていたり、自動車教習や資格試験があったりと忙しい中でしたが、traQでコミュニケーションをとりつつ自分にできることを進めていきました。
フロントエンド(文責:きつね)
フロントエンドではViteが提供する環境とテンプレートを基礎としてVue.jsによる開発に取り組みました。Vite環境は、最初のテンプレートの時点でビルドが出来てブラウザにサンプルページが映り、ソースコードをいじれば即座に反映されてブラウザから様子を見られるという優れものです。
Vue.js の特性
Webサイトはサイドバーやボタン、入力フォームなどといった部品の集まりと見ることができます。それぞれの部品の配置はHTML、外見はCSS、機能はJavaScriptを書いて指定するのが最もメジャーな方法と言えるでしょう。Vue.jsでは、それぞれの部品(コンポーネント)に対してひとつのVueファイルを用意し、その中にHTMLとCSSとJavaScriptの全てを以下のように書きます。
<template>
<!-- ここでHTMLを書く -->
</template>
<script setup></script>
<style scoped>
<!-- ここでCSSを書く -->
</style>
実際に(記事投稿時点で)leaQに組み込まれているVueコンポーネントから実例をお見せすると、例えば SearchBox.vue
は以下のようになっています。
<template>
<SearchIcon class="icons" width="20px" height="auto" />
<input type="text" v-model="searchQuery" placeholder="検索" @keyup.enter="onSearch" />
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import SearchIcon from './SearchIcon.vue';
const searchQuery = ref('')
const router = useRouter();
function onSearch() {
if (searchQuery.value.trim() !== '') {
router.push(`/tags/${encodeURIComponent(searchQuery.value)}`);
}
};
</script>
<style scoped>
input {
height: 40px;
width: 720px;
margin: 0 10px;
}
</style>
こうして作ったコンポーネントは、より上位のコンポーネントから呼び出したり、上位のコンポーネントに向けて情報を送ったりすることができます。例えば、画面全体のコンポーネント MainView.vue
からサイドバーのコンポーネント SideBar.vue
を呼び出し、その中ではさらにサイドバーに連なるボタンのコンポーネント SideButton.vue
を呼び出す、というような具合です。Vue.jsによる開発では、こうしたVueコンポーネントを組み合わせることでUIを作っていきます。
APIとの接続
Webサービスの開発は主にバックエンド開発とフロントエンド開発に分かれます。一般に、バックエンド開発はデータを処理・保存するサーバー側のシステムを、フロントエンド開発はUIを表示するためにユーザー側のデバイスで実行されるシステムを手掛けます。フロントエンドがバックエンドと正確に情報をやりとりをするためにバックエンド側が用意してくれる「操作」と「結果」の組の一覧をAPIと呼びます。飲食店に例えるなら「注文すべき名前」と「届く料理」の組を記したお品書きがAPIにあたります。
一般的にWeb APIでは POST
(送信)GET
(受信)DELETE
(削除)PATCH
(編集)などの限られた方法(HTTPメソッド)を指定してAPIにアクセスします。JavaScriptでは fetch()
関数が使えて、HTTPメソッド・URL・渡したいデータなどを引数に渡して fetch()
を実行することでバックエンドとの情報の送受信ができます。ただし、APIの利用に必要な予備処理を毎回書くのは面倒なので、例えば getPostData(id=15)
とだけ書けば実行できるように、それぞれの処理を関数にまとめて一発で呼び出せるようにしておきます。これがいわゆるAPIラッパーです。
開発ペース
leaQのGitリポジトリ(traP部内のみ共有)のコミットグラフを見返すと、タグ検索(9/4)、ブックマーク(9/6)、スタンプ(9/6)、無限スクロール(9/7)、コメント(9/7)、アップロード画面(9/8 朝)とフロントエンドのメジャーな機能実装のことごとくが最終発表会の9/8の直前数日になされていることが分かります。UIのデザインは概ね私の担当だったのですが、8/25の中間発表の直前にレイアウトの原型が出来上がったはいいものの、8/26から29にかけて文系教養の集中講義が入っていてほとんど開発に関わることができず、それがようやく終わり投稿ごとのコンポーネントの配置を色々と試していたら気が付けば最終発表が目前に迫っていた、という次第です。バックエンド担当が先に立てておいてくれたAPIという道標を頼りにソースコードの乱雑さに目もくれず全力疾走、今思い返しても冷や汗が出ますね。
右も左も分からない状態から始めるならば、少しずつ知識の貯金を作っていくにつれて開発のスピードは速まっていくものだと思います。ただし、これほどの急加速は健康を害しかねないのであまりおすすめはしません。今回運良く完成まで漕ぎ着けることが出来たのは、バックエンド担当がAPIの整備をほとんど終えてくれていたこと、早いうちにVue.jsの扱いをある程度把握できていたことなどの好条件が重なった結果です。
バックエンド(文責:あきも)
技術選定
バックエンドでは部内で標準言語として広く使われているGoを採用しました。フロントエンドもですが、成果物はNeoShowcase(以下NS)にデプロイしています。
NeoShowcaseとは?
traP部員が自由に使える作品公開プラットフォームです。シンプルなWebサイトならカップ麺を作っている間に公開することができるような手軽さと、上級者向けのカスタマイズ性を兼ね備えたサービスとなっています。NSの開発に関するブログ記事もあるので、興味がある方は読んでみてください。
データベースはNSがビルトインでサポートするMariaDBを、GoからのDB操作にはGORMを利用しています。
Cloudflare R2の採用
NSのファイルシステムは再起動で初期化されてしまいます。しかし画像の保存に使えるようなオブジェクトストレージは部内サービスにはないため、外部のサービスを利用する必要がありました。どこのサービスを使うかについては迷う暇もなく、しーぴーさん(@cp20)からのアドバイスが降ってきます。
もちろんこれを見て即決したわけではありませんが、当面はタダで使えそうな上にCloudflareなので信頼性も高いということでCloudflare R2を採用しました。R2はAmazon S3互換なので、ローカルでは同じくS3互換のMinIOを利用しています。
認証
SNSを作る上で難しいポイントの一つがアカウント周りだと思います。が、部員はそれぞれ固有のtraQ IDを持っているのでゼロからアカウントの仕組みを作る必要はありませんでした。
更に、NSには部員認証のオプションがあります。これを必須にすることでNSが勝手に認証してリクエストにユーザー名が書かれたヘッダーを付与してくれるため、非常に簡単にログインユーザーを判別することができます。一方で、この認証方法は開発環境のブラウザで使うのには向いていません。ローカルのブラウザでNSの認証を再現するためには
- 自動的にヘッダーを付与するプロキシサーバーを立てる
- ブラウザ拡張機能でヘッダーを付与する
といった方法が考えられます。そもそもNSに頼らずtraQ IDのOAuthを使うという手もあります。一番楽そうなのは「ブラウザ拡張機能でヘッダーを付与する」ですが開発期間中は思いつかず、「自動的にヘッダーを付与するプロキシサーバーを立てる」はサクッとできそうだったのでこの方法を選択しました。
スタンプ機能
前述の通り、leaQの特徴の一つに「traQのスタンプが使える」というものがあります。スタンプの画像自体はtraQ APIで取得できますが、
- UUIDではなく名前でスタンプを取得する
- スタンプのサジェストを行う
といった要件を満たすため、現在は起動時にtraQから全てのスタンプの情報を持ってくるという力技を使っています。ただこの方法だと新規作成されたスタンプには再起動するまで対応できないので、別の方法を考えても良いかもしれません。
画像の圧縮
leaQは画像主体のサービスであり、Cloudflare R2の無料枠を利用しているのでファイルサイズは小さい方が良いです。初めはWebPにしてサイズを削減しようと思っていましたが、Pure GoではWebPのエンコードができないため一旦見送りました。代わりにPNGやJPEGのままで気持ち程度の圧縮をしています。が、色数の多いPNG画像ではMB単位になってしまい、Macでのスクショ(影が付いてるやつ)は外側が透過されているのでJPEGにするわけにもいきません。ということで、1-Monthon期間が終わってからは改めてWebPの導入準備を進めています。NSにはリポジトリ内のファイルを見て†良い感じ†にビルドしてくれる設定があるので現在はそれを利用していますが、WebPのエンコードにはlibwebpが必要なので今後は自分で書いたDockerfileを使うことになりそうです。
今後の展望
1-Monthonとしての開発は終了しましたが、leaQはチームメンバーによる自主開発が継続して進められています。先日モバイル版に対応し、この後もコメントへの返信やブックマークの共有などの機能拡充を予定しています。
現在のサークル内での認知度はそれほど高くはないですが、今後leaQのユーザー数を徐々に増加させ、最終的にはtraQに次ぐ部内SNSに仕上げようというささやかな野望があります。叶うかどうかは分かりませんが、ひとつのSNSを開発・運営する経験というのは滅多にない貴重なものですので、出来るところまではやってみたいと思っています。
感想
kitsne
チームリーダーとフロントエンドを担当しました。初顔合わせの時に私が軽いノリで提案した『traP版BeReal』がまさかここまで立派なサービスとして日の目を見るとは思っていなかったので、とても嬉しいです。8月中旬は予定が重なりバックエンドに比較して進捗を生めませんでしたが、さみだれくんの協力もあり1-Monthon当日の発表前までソースコードを睨みつつ頑張った結果なんとか間に合わせることができてホッとしています。この1ヶ月の間にVue.jsやFigmaなど初めてのツールや言語にたくさん触れて急成長できた気がします。思うようにいかず焦慮に苛まれることもありましたが、終わってみればとんでもなく楽しい開発体験でした! チームメイトと1-Monthon運営の皆様に感謝です。
samidare
frontendを担当しました。全体を通しての感想としては、もっと制作に貢献すべきだったなといった感じです。普段ゲーム開発をメインにしていてwebアプリ開発は初めてで、右も左も分からないままスタートしました。一応なろう講習会などでVue.jsの書き方を学んだりしましたが、結局chatgpt君ときつね君に頼りっきりになってしまったと思います(てかなんかVue.jsむずくないか.....?)。認証とかサーバー関連のことは全く分からなくて、メールボックスに来るgitからのissue立てやコミットの通知が来るたびにチームメンバーの優秀さを実感してました。一応今回フロントエンドの基礎などを学んだりしましたが、このまま一切活用せず腐らせるのももったいないので、どこかのタイミングでまたwebアプリ開発に取り組んでみたいと思います。改めて、チームメンバーおよび1-Monthonの運営の皆さん、一ヶ月間ありがとうございました!
mumumu
leaQではbackendを担当しました。
春ハッカソンで少しだけフロントエンドの経験はあったものの、バックエンドは何もわからず右も左もわからない状態から始まりました。
chatgptとcopilotとakimo君に頼りっぱのワンマンソンでした。
とにかくAIに投げて動くものを作ってはakimo君にレビューしてもらってました。
認証とか全然分からないのでakimo君がやってくれてマジ助かりました。
AIを取り上げられたらコードをかける自信がないです笑
初めにどうやってデータを持つか、どういうapiが必要なのかを決めておくべきだったなと思ったことと、適当にAIに投げすぎるとほかの部分とうまくかみ合わなくなるので自分で全部を書けはするようになりたいな~というのが反省点です。
最終発表の日かなりギリギリでしたがなんとか動くものが出来て良かったです!!
楽しかった~
akimo
バックエンドを担当したあきもです。初めて触ったプログラミング言語はGoで、高校生の頃はGoで競プロをしていました(ずっと灰だったけど)。Webアプリの開発経験はないもののよく技術系の記事を読んでいたため、開発未経験の割には知識がある謎の人として参加しました。leaQではmumumu君が書いたコードのレビューをしつつ、NSの認証やデプロイ、Cloudflare R2の導入といった外部サービスに依存する部分を中心に担当しています。
悲しいことに僕は集中講義くらいしか外出の予定がなかったため、リアルが充実している3人を横目にパソコンカタカタしていました……。が、1-Monthonがないと夏休みの進捗がnil
になっていたでしょうから参加して良かったです。
反省点はいくつかありますが、一番ひどかったのはNSの認証をローカルで再現するために立てたプロキシサーバーの実装が雑すぎたことでしょうか。度々不具合を起こした挙句、終いには「ローカルでは検証できてないけど本番環境では動くやろ!w」とぶっつけ本番で投稿のテストをすることになってしまいました。
1-Monthonの開発期間は1ヶ月とはいえ、準備期間も1ヶ月用意されていたのでこの辺りは事前に考えておくべきだったと反省しています。
おわりに
とても長くなってしまいましたが、ここまで読んでいただきありがとうございました。traP公式ブログは幅広い分野の創作・開発に関する知見の宝庫です。色々と探索していっていただけると嬉しいです! 夏のブログリレー、明日の担当は@o_ER4先輩、@Alt--er先輩、@mehm8128先輩です。お楽しみに!