どっかの鳥の日常

主に開発についてのブログです。たまに他愛ないことも。

夏休みが夏休みじゃない

どうしてこうなった

夏休みの方が忙しい。。。
なんでだろうと考えます。
その理由は色々な締切が夏休みにあったからです。
論文提出、卒研中間発表、それに伴うシステムの試作、、、(重いわ)

でも夏休みは授業がないじゃん

いいえ。大学4年生は1年中授業がありません。
卒研があるので、授業を計画的に取っていればほとんど授業が残らないようになっているはずです。(授業は落とさないようにしよう(当たり前))

忙しいね

正直重い締切が重なると思っていませんでした。
マジで辛い。。。
誰か自動生成ツール作ってくれほんとに。
ブログ書いてる暇なんてないんじゃね?と思いつつ辛さを発散したいので書いてます。(全然書いてなかったというのはあるけれど)

結論

理系大学4年生に夏休みはない!!!

4月の振り返りと5月の予定(5月の予定編)

言い訳

どうもこんにちは。LaTexの環境構築に5時間以上かけて結局失敗した鳥です。
ゴールデンウィークも終わり5月も3分の1が終わりました。
ゴールデンウィークに書こうと思っていたら書く気が全く起きなかったのです。
さてさて、言い訳はこれくらいにして書いていきましょう。
予定と言っても予定ではない気がします。(目標などを適当に書きます)

ライブが中止になった

これが一番つらいですね。
一応アイマスPをやっているので、ライブにもちょこちょこ行ったりもするんですが、5月23、24日に開催される予定だった「THE IDOLM@STER MILLION LIVE! 7thLIVE Q@MP FLYER!!!」が中止になりました。
コロナなので仕方ないとはいえ、楽しみにしていたので残念ですね。
コロナが収束したらライブに行きたいなぁ。

idolmaster.jp

自堕落な生活からの脱却(多分できない)

自粛で何もなければ当然生活習慣は乱れます。
一応毎日10時50分にアラームをセットしているのですが、起きることができた試しがありません。(アラームのセット時間が遅いということはおいておきます)
これも問題なのですが、本質はそこではありません。その後に、ダラダラと過ごしているのが本当の問題です。なので、早く起きることは一旦諦めます。
私はベッドにダイブすることが多いのでそれを封じます。とりあえずこれで改善されるはずです。(楽観的)
現実問題モチベーションなどもあるので、その日その日でどうしていくかを考えるのがベストだと思いますけどね。(本末転倒)

Webの基礎勉強

来年は就職(しているはず)なので、何が今の自分には必要だと考えたときに、基礎に返ってやらなければいけないと感じました。
今まではWebアプリを作ったり、それに伴ってインフラを少し勉強したりしていたのですが、目的が作ることだったので、どうしても知識として抜けている部分がありました。そこでそれらの穴埋めをこの時期にやっていこうと思います。
あと、フロントエンドの技術も少しはやる必要があると感じたのでReactは今月中に一回は触ってみようと思います。

5月の予定総括

ライブが中止になったので、その分他の楽しみを見つけていきたいと思いました。
あと、全体的に4月と比べてモチベーションが下がってきているのでここらへんで引き締めていきたいと感じました。モチベーションの維持をするために定期的にブログをあげていきたいです。(そのモチベーションすら下がっていく)
とにかく、何か目標を見つけてそれを他人に言うのがいいと思います。そうすることでやらなきゃという意識が芽生える気がします。

以上で何か書きたいと思って始めた4月の振り返りと5月の予定を終わります。
それでは良い自粛ライフを。
by 鳥

4月の振り返りと5月の予定(4月の振り返り編)

はじめに

こんにちは鳥です()
コロナの影響でほとんど家にいるので、何か雑記的なものも書いてみようかなぁと思いました。
なので、4月の振り返りと5月の予定(希望)について話していきたいと思います。
今回は4月の振り返り編です。

生活面

生活面は基本的に自粛前と後に分けられるかなと思います。まぁ自粛前の時期はすごく短かったのでそんなにないのですが。

前半(自粛前)

4月の初めはまだ外に出れたので、研究のために大学に行ってました。
この頃は、生活リズムもそこまで乱れずに過ごせていたのでこのまま続けれたらいいなぁと思っていました。(叶わぬ夢) 途中で危なそうと思い自主的に家に引きこもったらそのまま出れなくなりました。
このタイミングで安いゲーミングチェアを買いました。今まで使っていた椅子は、ほんとにデスクのおまけでついてくる椅子だったので流石に家にいる時間が長くなるのにこの椅子じゃ作業できないなと思い購入を決意しました。欲しいと思ってから2年経ってようやく購入しました(遅すぎ)。結果、家から出れなくなったので買ってよかったとしみじみ感じました。

後半(自粛後)

自粛後は家で開発したりオンラインでお話したりと一般的な暮らし(?)をしていたのですが、スマホ歩数計を見たら1日1000歩程度しか歩いていないという事実に絶望しました。そりゃ買い物にしか行かないのでそうなりますよね。
動いていない影響からか足腰が急に痛くなりました。よくわからないですね。
なので、週に数回は30分程度の散歩に出ようと思いました。
それと数日前に友人からペルソナ5 ザ・ロイヤルが送られてきたのではじめましたが面白いですね。私はそんなにハイペースでゲームをしないのでクリアまでどれくらいかかるのだろうかと少し心配です。(でも面白い)

技術面

今月は色々な技術に触れた月でした。
技術に触れる理由としては、興味というより将来必要となる可能性があるor研究等で使うという観点で考えるようになりました。

言語

言語としては、GoとPythonを勉強し始めました。
Pythonはpipenvが便利だと思ったのでまた記事にしたいと思っています。
私の言語や技術の好き嫌いは、基本的に導入するときに環境が汚れるかやプロジェクト間で干渉しないなどで見るので、pipenvは好き判定に入りました。
GoもGo Modulesがあるのでプロジェクト間で上手く管理ができると感じました。
まぁだいたいどの言語でもパッケージマネージャーくらいあるんですけどね()

アプリ開発

以前から触っていたのですがRailsをまた勉強し始めました。私の場合Railsはだいたいすぐコケるので苦手ですw
これに加えてDockerコンテナでRails環境を構築しようとしたら結構コケて苦労しました。(まだしている)
それ以外にも、Pythonを使用してDiscord Botを作成したりしてました。 discord.pyの仕様が変わっていたので、ほとんどのブログ記事が参考にできないということもありましたがまぁある程度の形にはなりました。あとは、エラーハンドリングとかの細かいところや機能追加をしていきたいと思っています。

こんな感じで技術的には色々できた月でした。
またここらへんのことをブログに書いていこうと思っています。

4月の振り返り総括

総括としましては、身体はバキバキになったが技術的には結構成長できたかなぁといった一ヶ月でした。なので歩きます。

それでは5月の予定編でお会いしましょう。

Goでサーバサイド導入

はじめに

1年後にGoを使う可能性があるため、勉強がてら久々のブログ更新をしてみようと思いました。
やることは、ほんとにチュートリアル程度のことなので、何もやったことないよーといった人にしか役立たないと思います。(どちらかといえば知見が欲しい)
Goについてはガチ初心者なので、間違った点などがあればご教授ください。
それではお勉強を始めます。

開発環境

Goのインストール

MacなのでHomebrewでインストールします。

$ brew install go

環境変数

環境変数なのですが、Go 1.11 から追加されたGo Modulesにより必要ない可能性もあるのですが念の為設定しておきます。(要らなければ飛ばしてください)

環境変数config.fishに以下を追記します。

# golang
set -x GOPATH $HOME/go
set -x PATH $PATH $GOPATH/bin

bashの場合は.bash_profileに以下を追記で設定できると思います。(not 検証)

# golang
export GOPATH=$HOME/go
export PATH=$PATH:$GOPATH/bin

これでGoの環境は揃いました。

プロジェクトの作成

今回は、先程述べたGo Modulesを使用してモジュール管理をしていきたいと思います。
このページを参考にさせていただきました。 blog.mmmcorp.co.jp

適当にディレクトリを作成して以下のコマンドを打ちます。

$ go mod init <プロジェクト名>

実際の私のプロジェクト名を入れた例はこちらになります。

$ go mod init github.com/tokutatsu/go-web-app-practice

そうすると、go.modファイルが作成されます。

これで準備は整いました。

さぁコーディング

main.goを作成してコードを書いていきます。

まずはHello Worldから

以下を実行してhttp://localhost:8080/helloにアクセスするとHello World!を返してくれます。

package main

import (
    "fmt"
    "html/template"
    "net/http"
)

func main() {
    http.HandleFunc("/hello", helloHandler)
    http.ListenAndServe(":8080", nil)
}

func helloHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "Hello World!")
}

実行するには、Goはコンパイル言語なのでコンパイルして実行という形が一般的です。
しかし、Goではgo runをすると自動でコンパイルと実行をしてくれるため今回はそちらを使用します。
ブラウザ やcurlhttp://localhost:8080/helloにアクセスすると結果が確認できます。

レンダリング

次にhtml/templateを使ってレンダリングをしていきます。
text/templateというのもあるみたいですが、どうもhtml/templateのほうが良さげなので今回はそちらを使用します。
importにhtml/templatの追加が必要です。 先程のコードのfunc main()に以下を追加します。

http.HandleFunc("/template", templateHandler)

これは、ハンドラの関数を追加しただけです。
では、追加するハンドラの関数を定義していきます。 ここでは、テンプレートファイル(HTMLファイル)を読み込んでそれを描画しています。 また、tmpl.Execute()の第2引数に値を指定してあげることで、テンプレートに埋め込むことができます。
この埋め込みは、構造体やマップでも可能です。

func templateHandler(w http.ResponseWriter, r *http.Request) {
    tmpl := template.Must(template.ParseFiles("./views/index.html"))
    tmpl.Execute(w, nil)
}

素朴なHTML(index.html)↓

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Hello</title>
</head>
<body>
  <h1>Hello World!</h1>
</body>
</html>

結果はこんな感じです。
f:id:tokutatsu:20200402233442p:plain

埋め込みを利用したときには以下のようになります。 f:id:tokutatsu:20200402233636p:plain 変更点は、func templateHandler()のtmpl.Execute(w, nil)tmpl.Execute(w, "tokutatsu")にしたことと、index.html<h1>Hello World!</h1><h1>Hello {{.}}!</h1>にしただけです。(簡単)

jsonを返す

APIではjsonを返すことが多いのでやってみます。
まず、importにencoding/jsonを追加します。
次に、構造体を次のように定義してあげます。

type Human struct {
    Name string
    Age  int
}

そして、先ほどと同様にfunc main()で以下を追加。

http.HandleFunc("/json", jsonHandler)

そして、ハンドラの関数を次のように定義していきます。
はじめに先ほど定義した構造体を初期化します。
次に、jsonを返すためにはContent-Typeヘッダにapplication/jsonを指定する必要があるためセットします。

func jsonHandler(w http.ResponseWriter, r *http.Request) {
    my := Human{
        Name: "tokutatsu",
        Age:  21,
    }
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusOK)
    json.NewEncoder(w).Encode(my)
}

curlで確認した結果がこちらです。

{"Name":"tokutatsu","Age":21}

うまくできましたね。

さいごに

Goの環境すらない状態からWebサーバを構築しました(超簡易)が、標準ライブラリでここまでできるので使い勝手はいいなぁと感じました。
ただ、パッケージの構成は自由度が高いためいい構成などがあれば教えて下さい。
これからいくつかのWebアプリを作りながら、設計などを詰めていければなと。
一応今回のもの↓

github.com

参考ページ

ryomak.info maku77.github.io

Chrome拡張でYouTubeの関連動画を消す

はじめに

この記事は SLP KBIT Advent Calendar 2019 23日目の記事です。 adventar.org

昨年のアドベントカレンダーの記事も良ければどうぞ。 qiita.com

最近Youtubeの見すぎで時間が溶けていることがよくあります。(なんでや)
原因を自分なりに考察()してみたら、関連動画を押し続けて止め時がないことだという答えに行き着きました。
今回は、そんなお悩みを解決するためにChrome 拡張でYoutubeの関連動画を消してみます。
Chrome拡張をやるにあたって私が詰まったところを書いていくので、Chrome拡張に興味がある人はぜひ見ていって下さい。(私は初心者)
こうしたほうがいいよ〜、ということがあれば教えていただけると助かります。

準備

ファイルの準備

まずはじめに、適当に必要なファイルを作っていきます。
今回の構成はこんな感じです。

.
├─ src
│   ├─ assets
│   │   ├─ css
│   │   │   └─ options.css
│   │   ├─ img
│   │   │   └─ icon.png
│   │   ├─ js
│   │   │   └─ options.js
│   │   └─ options.html
│   ├─ content_scripts
│   │   └─ youtube.js
│   └─ background.js
└─ manifest.json

今後、もう少し拡張したいと思ったので、ディレクトリを結構細かく分割しておきました。(niconicoとかもできたらいいな)
Chrome拡張で(多分)一番重要になるのはmanifest.jsonです。

私的にこの記事が結構わかりやすいと思いました。 qiita.com

↑を参考にmanifest.jsonを書いていきます。

{
  "name": "RelatedVideosBlocker",
  "short_name": "RVB",
  "version": "1.0.0",
  "manifest_version": 2,
  "description": "This Chrome Extension delete related videos",
  "icons": {
    "16": "src/assets/img/icon.png",
    "48": "src/assets/img/icon.png",
    "128": "src/assets/img/icon.png"
  },
  "permissions": [
    "tabs",
    "storage"
  ],
  "background": {
    "scripts": [
      "src/background.js"
    ],
    "persistent": false
  },
  "browser_action": {
    "default_icon": "src/assets/img/icon.png",
    "default_title": "RelatedVideosBlocker",
    "default_popup": "src/assets/options.html"
  },
  "options_ui": {
    "page": "src/assets/options.html",
    "open_in_tab": false
  },
  "content_scripts": [
    {
      "matches": [
        "https://www.youtube.com/watch?v=*"
      ],
      "js": [
        "src/content_scripts/youtube.js"
      ]
    }
  ]
}

参考のページにない項目を説明していきます。

options_ui

以前まではoptions_pageが使われていたのですが、options_uiに変更されました。
options_uiのpageでは、options_pageと同様に、オプション画面で表示する画面を選択します。
open_in_tabは、オプションを開くときに、新しいタブを開くかといったものです。
基本的にfalseでいいと思います。

Chromeの準備

ここまでできたらChrome拡張機能を読み込みます。
Chromeの設定から拡張機能に行きます。
拡張機能の右上にあるディベロッパーモードをオンにします。
そしたら「パッケージ化されていない拡張機能を読み込む」から作成したプロジェクトを選択すれば、読み込みは完了です。
開発中にコードを書き換えた場合は、拡張機能をしっかり更新してあげてください。

f:id:tokutatsu:20191222215027p:plain

ここまでで準備は完了です。

中身の処理を書いていく

ここからは実際に処理を書いていきます。

youtube.js

ここでは、特定のリンク(今回はYouTubeのリンク)に訪れたときの処理を書きます。
今回は、関連動画と広告の削除(非表示)を行います。
基本的に、DOM操作によって要素を消すので、消したいものを探す場合はF12を押してElementsを見てあげましょう。
ここらへんは色々やりようがあると思います。(いいやり方がわからない)
chrome.runtime.sendMessageについては、後のbackground.jsのchrome.runtime.onMessage.addListenerとセットなのでそこで説明します。

// コンテンツ読み込み時に拡張機能が有効か確認して有効なら処理を実行
chrome.runtime.sendMessage({ contents: 'youtube' }, (isAvailable) => {
    if (isAvailable) {
        setTimeout(() => {
            hideRelatedVideos();
            removeAdvertisement();
        }, 1000);

    } else {
        setTimeout(() => {
            showRelatedVideos();
        }, 1000);
    }
});

// 関連動画を表示する
function showRelatedVideos() {
    // 関連動画などが表示されているブロック全体
    const secondary = document.getElementById('secondary');
    // 自動再生のトグル操作を行う要素
    const head = document.getElementById('head');
    // 次の動画と表示されるテキスト
    const upnext = document.getElementById('upnext');
    // 動画終了時に表示される関連動画
    let videos = document.querySelector('.videowall-endscreen');
    // 広告の時間待機する秒数
    const LIMIT = 180;
    // 1秒でカウンタが1増える
    let limit_counter = 0;

    secondary.style.visibility = 'visible';
    head.style.visibility = 'visible';
    upnext.style.visibility = 'visible';

    const interval = setInterval(() => {
        videos = document.querySelector('.videowall-endscreen');
        if (videos != null) {
            videos.style.visibility = 'visible';
            clearInterval(interval);
        }
        if (limit_counter >= LIMIT) {
            clearInterval(interval);
        }
        limit_counter++;
    }, 1000);
}

// 関連動画を非表示にする
function hideRelatedVideos() {
    // 関連動画などが表示されているブロック全体
    const secondary = document.getElementById('secondary');
    // 自動再生のトグル操作を行う要素
    const head = document.getElementById('head');
    // 次の動画と表示されるテキスト
    const upnext = document.getElementById('upnext');
    // 動画終了時に表示される関連動画
    let videos = document.querySelector('.videowall-endscreen');
    // 広告の時間待機する秒数
    const LIMIT = 180;
    // 1秒でカウンタが1増える
    let limit_counter = 0;

    secondary.style.visibility = 'hidden';
    head.style.visibility = 'visible';
    upnext.style.visibility = 'hidden';

    const interval = setInterval(() => {
        videos = document.querySelector('.videowall-endscreen');
        if (videos != null) {
            videos.style.visibility = 'hidden';
            clearInterval(interval);
        }
        if (limit_counter >= LIMIT) {
            clearInterval(interval);
        }
        limit_counter++;
    }, 1000);
}

// 広告の削除
function removeAdvertisement() {
    // 関連動画がある場所の広告
    const playerAds = document.querySelectorAll('#player-ads');

    for (const playerAd of playerAds) {
        playerAd.remove();
    }
}

↓ここの部分、これめっちゃ汚いです。
なぜこのようにしているかというと、YouTubeでは広告があります。
広告動画の間はvideowall-endscreen要素が存在しないため、エラーを吐いてしまいます。
なので、理想は広告動画が終わったときに要素を消してあげたいのですがいい方法が見つかりませんでした。
よって、今回はvideowall-endscreen要素を発見するまで探し続けることにしました。(ザル実装)
一応リミットを設けてあげなければ無限ループしてしまうのでカウンタをつけてあげてます。

const interval = setInterval(() => {
   videos = document.querySelector('.videowall-endscreen');
    if (videos != null) {
        videos.remove();
        clearInterval(interval);
    }
    if (limit_counter >= LIMIT) {
        clearInterval(interval);
    }
    limit_counter++;
}, 1000);

options.htmlとoptions.js

ここでは、オプション画面を作成します。
今回、オプション画面では拡張機能のオンオフを設定します。
CSSはおまけなので省略します。 HTMLではチェックボックスを設置するだけです。(簡単)

<!DOCTYPE html>
<html lang="ja">

<head>
  <meta charset="UTF-8" />
  <link rel="stylesheet" href="css/options.css" />
  <title>Document</title>
</head>

<body>
  <div class="switch">
    <span>youtube</span>
    <label class="switch__label">
      <input id="youtube" type="checkbox" class="switch__input" />
      <span class="switch__content"></span>
      <span class="switch__circle"></span>
    </label>
  </div>
  <script type="text/javascript" src="js/options.js"></script>
</body>

</html>

次にJavaScriptの方を説明します。
まず、HTMLからチェックボックスの要素を持って来ます。
その後、チェックボックスの要素がオンかオフかをChromeのストレージから取り出して、チェックボックスに反映させます。
チェックボックスがクリックされると、Chromeのストレージにクリックされているか否かの状態を保存します。

const youtube = document.getElementById('youtube');

// ストレージから情報を取得
chrome.storage.sync.get(['youtube'], (value) => {
    youtube.checked = value.youtube;
});

// youtubeの有効と無効を切り替え
youtube.addEventListener('click', () => {
    chrome.storage.sync.set({ 'youtube': youtube.checked });
});

CSSも反映させてできたのがこちら。(niconicoは息をしてません) f:id:tokutatsu:20191222184729g:plain

基本は設定に飛ばなくてはいけないのですが、manifest.jsonbrowser_actionにoptions.htmlを指定してるため、アイコンをクリックすれば表示されます。

background.js

background.jsでは、拡張機能の裏で動く処理を記述します。
今回は、オプション画面での拡張機能が有効かを確認して、処理を行うかを判定することを行います。
ここで、youtube.jsで出てきたchrome.runtime.sendMessagechrome.runtime.onMessage.addListenerの関係です。
background.jsのchrome.runtime.onMessage.addListenerは、chrome.runtime.sendMessageで送られるメッセージを受けつけています。
そのため、chrome.runtime.sendMessageの第一引数の値が、requestの中に入ります。
ここでは、そのコンテンツが何かで処理を分けてあげます。
そして、Chromeのストレージから拡張機能が有効であるかをsendResponse()により、chrome.runtime.sendMessageの方に返します。
また、同期的に処理を行う場合は、明示的にtrueを返してあげる必要があります。

// どのコンテンツに飛んだかを確認して有効か無効化を判定する
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
    if (request.contents == 'youtube') {
        chrome.storage.sync.get('youtube', (value) => {
            sendResponse(value.youtube);
        });
    }
    // 同期的に待つ場合は明示的にtrueを返す
    return true;
});

できた!

モザイクだらけの関連動画達が... f:id:tokutatsu:20191222230712p:plain

スッキリ! f:id:tokutatsu:20191222230720p:plain これで完成!...と思いきや...

見事にかかった罠

よっしゃできたぞ!とYouTubeのページをポチポチ...
あれ、非表示にできないぞ。
リロードしたら消えるのに...
YouTubeはSPAでした。(多分、間違っていたらごめんなさい)
SPAとはSingle Page Applicationの略で単一のWebページでアプリケーションを構成する設計構造のことです。
今まで使っていた、Content ScriptsではSPAに対応することが多分できません。
ページの更新をしなければ切り替えができないという状況になります。
そこで、chrome.tabsを使うとSPAのページでも動くみたいなのでこれを使ってみます。
developer.chrome.com

やはり何事にも先駆者はいるものですね。助かります。 r17n.page

先駆者を参考に、background.jsに以下のコードを追加します。
気を付けポイントは、youtube.jsのパスをプロジェクトのルートにするとこです。(background.jsからのパスにしないように)

chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
    if (changeInfo.status === 'complete' && tab.url.indexOf('https://www.youtube.com/watch?v=') > -1) {
        console.log(`updated: ${tab.url}`);
        chrome.tabs.executeScript(
            tabId,
            {
                file: '/src/content_scripts/youtube.js',
            },
            (result) => {
                console.log(`executed: ${result}`);
            }
        );
    }
});

manifest.jsonも少し書き換えます。

{
  "name": "RelatedVideosBlocker",
  "short_name": "RVB",
  "version": "1.0.0",
  "manifest_version": 2,
  "description": "This Chrome Extension delete related videos",
  "icons": {
    "16": "src/assets/img/icon.png",
    "48": "src/assets/img/icon.png",
    "128": "src/assets/img/icon.png"
  },
  "permissions": [
    "tabs",
    "storage",
    "https://www.youtube.com/watch?v=*"
  ],
  "background": {
    "scripts": [
      "src/background.js"
    ],
    "persistent": false
  },
  "browser_action": {
    "default_icon": "src/assets/img/icon.png",
    "default_title": "RelatedVideosBlocker",
    "default_popup": "src/assets/options.html"
  },
  "options_ui": {
    "page": "src/assets/options.html",
    "open_in_tab": false
  }
}

これでSPAのページにも対応することができたはずです。 完成!(動作は基本同じなのでスクショはなしで)

さいごに

結構ゴリ押しでしたが、なんとか形にはなった気がします。
とりあえずバグを減らしていかなければ...と思いつつ、機能追加もしていければいいなと思っています。
バグがあったら教えて下さい...

作成したもの

分量的に主要な部分しか載せられなかったので、ここが微妙といったところはコードを見てくれればいいと思います。(ほとんど載せましたが...) github.com