Ruby on RailsでRedisのLIST操作

RailsでRedisのLIST型操作のコード例が見当たらなかったので備忘録

rpush, lpush で追加

lrange で取得

lrem, rpop, lpop で削除

 

ドキュメントhttps://www.rubydoc.info/github/redis/redis-rb/Redis

 

前提

Gemfileに追記

gem 'redis'

予めRedisインスタンスをnewしておく

REDIS = Redis.new

 

rpushで追加
REDIS.rpush("hogelist", "item-a")
>["item-a"]

REDIS.rpush("hogelist", "item-b")
REDIS.rpush("hogelist", "item-c")
>["item-a", "item-b", "item-c"]

 

配列でまとめて追加もできる

REDIS.rpush("hogelist", ["item-d","item-d","item-d"])
>["item-a", "item-b", "item-c", "item-d", "item-d", "item-d"]

 

lpushで先頭に追加できる

REDIS.lpush("hogelist", "item-X")
REDIS.lpush("hogelist", ["item-Y","item-Y"])
>["item-Y", "item-Y", "item-X", "item-a", "item-b", "item-c", "item-d", "item-d", "item-d"]

 

lrangeで取得

REDIS.lrange("hogelist", 0, -1)
>["item-Y", "item-Y", "item-X", "item-a", "item-b", "item-c", "item-d", "item-d", "item-d"]

 

第二、第三引数で配列インデックスを指定することで部分取得できる。
REDIS.lrange("hogelist", 0, 0)
>["item-Y"]

REDIS.lrange("hogelist", 0, 3)
>["item-Y", "item-Y", "item-X", "item-a"]

REDIS.lrange("hogelist", 3, 3)
>["item-a"]

※インデックスが不正だと空になる
REDIS.lrange("hogelist", 3, 2)
>

 

※通常のgetだとエラーになる

REDIS.get("hogelist")

f:id:ikasekai:20211217225056p:plain

 

削除

delでLIST全削除

REDIS.del("hogelist")
>

 

部分削除

元:["item-Z", "item-Y", "item-Y", "item-X", "item-a", "item-Z", "item-b", "item-c", "item-d", "item-d", "item-d", "item-Z"]から

 

値を指定して削除

REDIS.lrem("hogelist", 0, "item-Z")
>["item-Y", "item-Y", "item-X", "item-a", "item-b", "item-c", "item-d", "item-d", "item-d"]

 

第二引数で削除数を指定 ※上のように0で全部

REDIS.lrem("hogelist", 2, "item-d")
>["item-Y", "item-Y", "item-X", "item-a", "item-b", "item-c", "item-d"]

※実際にある数より多くてもエラーにはならない
REDIS.lrem("hogelist", 3, "item-b")
>["item-Y", "item-Y", "item-X", "item-a", "item-c", "item-d", "item-d", "item-d"]

 

rpopで末尾削除

REDIS.rpop("hogelist")
>["item-Y", "item-Y", "item-X", "item-a", "item-c", "item-d", "item-d"]

 

lpopで先頭削除

REDIS.lpop("hogelist")
>["item-Y", "item-X", "item-a", "item-c", "item-d", "item-d"]

 

gRPCをdockerで動かしたぜんぜんわからんからメモ

gRPCを学ぼうとしているけどぜんぜんわからん

まずGo言語もわからん

プロトコルバッファーもわからん

まともに動かすのも難儀する

 

下記URLを参考にひとまず動くものを手にしようと思ってもうまくいかなかったり四苦八苦したので、Bidirectional streaming RPC を動かしたメモを残す

goでgRPCの4つの通信方式やってみた(Dockerのサンプルあり) - Qiita

 

詰まったポイントとしては、protoファイルに option go_package の記述がないことでエラーになったことの対処だったり、

Dockerにてホスト側の作業ディレクトリの親の親ディレクトリをボリュームマウントすることで不要なファイルまでマウントしてので、作業ディレクトリ配下で完結するようにしたり、

あとコンテナに入らないでコマンド実行する方法をまとめた。

 

環境

・win10 vbox Ubuntu

・docker、docker-compose インストール済

 

準備

ホスト側作業ディレクト

/var/docker/grpc-docker

 

ファイル・フォルダ構成

./docker-compose.yml

./grpc/Dockerfile

./grpc/src/proto/chat.proto

./grpc/src/server/main.go

./grpc/src/client/main.go

./grpc/src/pb/chat ※空フォルダ

 

./docker-compose.yml

version: "3.7"
services:
  go-grpc:
    build:
      context: ./grpc/
      dockerfile: Dockerfile
    container_name: "grpc"
    volumes:
      - ./grpc/src/:/go-grpc
    tty: true
    privileged: true

 

./grpc/Dockerfile

FROM golang:1.13-stretch

SHELL ["/bin/bash", "-c"]
RUN apt update && apt-get install -y vim unzip

# install protc
WORKDIR /protoc
RUN wget https://github.com/protocolbuffers/protobuf/releases/download/v3.11.2/protoc-3.11.2-linux-x86_64.zip
RUN unzip protoc-3.11.2-linux-x86_64.zip
RUN ln -s /protoc/bin/protoc /bin/protoc

# golang
WORKDIR /go-grpc
ENV GO111MODULE on
RUN go get -u github.com/golang/protobuf/protoc-gen-go

 

./grpc/src/proto/chat.proto

syntax = "proto3";

option java_multiple_files = true;
option java_package = "io.grpc.examples.chat";
option java_outer_classname = "ChatProto";

package chat;

option go_package = "./"; //上記参考サイトのものに追記

service Chat {
  rpc Chat (stream ChatRequest) returns (stream ChatReply) {}
}

message ChatRequest {
  string message = 1;
}

message ChatReply {
  string message = 1;
}

 

./grpc/src/server/main.go

package main

import (
    "fmt"
    "io"
    "log"
    "net"
    "time"

    pb "grpc-docker/pb/chat"

    "github.com/pkg/errors"
    "google.golang.org/grpc"
)

const port = ":50051"

// ServerBidirectional is server
type ServerBidirectional struct {
    pb.UnimplementedChatServer
}

func request(stream pb.Chat_ChatServer, message string) error {
    reply := fmt.Sprintf("%sを受け取ったよ!ありがとう^^", message)
    return stream.Send(&pb.ChatReply{
        Message: reply,
    })
}

// Chat クライアントから受け取った言葉に、言葉を返す
func (s *ServerBidirectional) Chat(stream pb.Chat_ChatServer) error {
    for {
        in, err := stream.Recv()
        if err == io.EOF {
            return nil
        }
        if err != nil {
            return err
        }
        message := in.GetMessage()
        fmt.Println("受取:", message)

        if err := request(stream, message); err != nil {
            return err
        }
        time.Sleep(time.Second * 1)
    }
}

func set() error {
    lis, err := net.Listen("tcp", port)
    if err != nil {
        return errors.Wrap(err, "ポート失敗")
    }
    s := grpc.NewServer()
    var server ServerBidirectional
    pb.RegisterChatServer(s, &server)
    if err := s.Serve(lis); err != nil {
        return errors.Wrap(err, "サーバ起動失敗")
    }
    return nil
}

func main() {
    fmt.Println("起動")
    if err := set(); err != nil {
        log.Fatalf("%v", err)
    }
}

 

./grpc/src/client/main.go

package main

import (
    "context"
    "io"
    "log"
    "time"

    "github.com/pkg/errors"

    pb "grpc-docker/pb/chat"

    "google.golang.org/grpc"
)

func receive(stream pb.Chat_ChatClient) error {
    waitc := make(chan struct{})
    go func() {
        for {
            in, err := stream.Recv()
            if err == io.EOF {
                close(waitc)
                return
            }
            if err != nil {
                log.Fatalf("エラー: %v", err)
            }
            log.Printf("サーバから:%s", in.Message)

            // お返し
            stream.Send(&pb.ChatRequest{
                Message: time.Now().Format("2006-01-02 15:04:05"),
            })
        }
    }()
    <-waitc
    return nil
}

func request(stream pb.Chat_ChatClient) error {
    return stream.Send(&pb.ChatRequest{
        Message: "こんにちは",
    })
}

func chat(client pb.ChatClient) error {
    stream, err := client.Chat(context.Background())
    if err != nil {
        return err
    }
    if err := request(stream); err != nil {
        return err
    }
    if err := receive(stream); err != nil {
        return err
    }
    stream.CloseSend()
    return nil
}

func exec() error {
    address := "localhost:50051"
    conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithBlock())
    if err != nil {
        return errors.Wrap(err, "コネクションエラー")
    }
    defer conn.Close()
    client := pb.NewChatClient(conn)
    return chat(client)
}

func main() {
    if err := exec(); err != nil {
        log.Println(err)
    }
}

 

ファイル生成

Dockerコンテナを立ち上げる

docker-compose up -d

 

下記コマンドを実行し、go moduleの初期化を行う

docker-compose exec go-grpc go mod init grpc-docker

すると ./grpc/src/go.mod が生成される

 

下記コマンドを実行し、gRPCファイルの生成

docker-compose exec  go-grpc protoc --proto_path ./proto --go_out=plugins=grpc:./pb/chat chat.proto

すると ./grpc/src/pb/chat/chat.pb.go が生成される

 

サーバとクライアントの実行

下記コマンドを実行し、gRPCサーバを起動する

docker-compose exec go-grpc go run server/main.go

すると ./grpc/src/go.sum が生成される

 

下記コマンドを実行し、gRPCクライアントを起動する

docker-compose exec go-grpc go run client/main.go

すると下記スクショのようにサーバ側とクライアント側で出力が表示される。

f:id:ikasekai:20211118214032p:plain

サーバ側とクライアント側の出力

ようやくgRPC学習のスタートラインに立てた感

次はgRPC-webを動かす方法を調べる

暗中模索

 

PS

A Tour of Go だけじゃ全然わからんけど、

とほほのGo言語入門 - とほほのWWW入門 を併せて参考にすると理解が捗った。

webRTCを使ってストリーミング鯖

twitchが埋め込みプレーヤー使用制限なんてことがあったので、自前配信サーバーを用立てれないかと思ってwebRTCでP2P動画配信サーバーを作ってみた。

んで、Twitchが埋め込みプレーヤーに対する制限を辞めたようなので、自前配信サーバーを維持することなくなったので、テキトーにコードをgithubに挙げてTURN鯖の設定のメモを残しておく。

 

動かしてみた感じ、思った以上にみんなTURNサーバを経由せずにブラウザ間でのP2P通信ができているようだった。

ただ、Firefoxだとiframe内でwebRTC p2pがうまくいってないようだった。別ウィンドウだとOK。

 

webRTCによる動画配信サーバー

socket.io でシグナリングと simple-peer.js でピア接続に使って実装した。

動画配信なので1対多でピアを接続する。

github.com

上記ソースの broadcast.js を実行してシグナリングサーバ兼HTTPサーバを立てる。

 

上記のサーバ用のsystemd を作る

sudo vi /etc/systemd/system/webrtc.service

[Unit]
Description=webrtc service
After=network.target

[Service]
Type=simple
ExecStart=/usr/bin/node /var/www/nodejs/broadcast.js
WorkingDirectory=/var/www/nodejs
KillMode=process
Restart=always

[Install]
WantedBy=multi-user.target

 sudo systemctl restart webrtc

 

TURN/STUN サーバのインストール

sudo apt install coturn

sudo vi /etc/default/coturn

coturnの設定は下記を参考

ja.tech.jar.jp

 

TURNサーバがちゃんと動いてるかは下記サイトを使ってテストできる

webrtc.github.io

 

とりあえず構築メモは以上。

 

今年頭にwebRTCはW3Cweb標準仕様となった。

www.publickey1.jp

動画配信は現状YouTubeかTwitchの二択みたいな状況だけれども、webRTC標準化によって何かとライブラリが充実したり、TURN/STUNサーバの構築がもっと簡単になるかもしれない。

 

なんにせよ当初の実験は成功し、今は必要なくなったのでwebRTC配信鯖は閉じけど、また必要になった構築できるようにしておきたい。

今度立てるときは、webRTCのストリームをバケツリレーする感じの仕様に挑戦してみたいね。

現状だと1対多で配信者に対して5人くらい視聴者がつくとカクつくみたいなので、1人あたりMAX3接続、みたいなオープン分散型動画配信システム。

 

まあ誰かがそのうち実装するだろう

 

 これはいい漫画

 

 

 

自宅動画ストリーミング鯖のススメ(Raspberr Pi + Nginx + libnginx-mod-rtmp)

概要

[ PC ]→rtmp→[ ラズパイ ]→HLS→[ プレーヤー ]

 

主な手順

  1. Raspberry Pi セットアップ
  2. 動画ストリーミング鯖の構築
  3. Rasppbery Piをインターネットに公開
  4. ドメインSSLを無料で取得、設定
  5. SSLを無料で取得、設定

 

1. Raspberry Pi セットアップ

1.1 本体セットアップ

まずは本体を調達


 

OSをダウンロード

https://www.raspberrypi.org/downloads/raspberry-pi-os/

ダウンロードしたらzipを解凍して2020-月-日-raspbian-ナンタラカンタラ.imgを取り出しておく

 

SDメモリカードフォーマッター をダウンロード

https://www.sdcard.org/jp/downloads/formatter/

microSDカードをフォーマットしておく

 

Win32 Disk Imager をダウンロード

https://sourceforge.net/projects/win32diskimager/

2020-月-日-raspbian-ナンタラカンタラ.img を microSDに焼く

 

1.2 Raspberry PiIPアドレスを固定する

Raspberry Pi でnmtuiでIPアドレスを固定してもいいし、ルーター側でMACアドレスからIPアドレスを固定してもいい。

 

2. 動画ストリーミング鯖の構築

参考にした記事:https://iot-plus.net/make/raspi/raspbian-buster-streaming-server-using-ffmpeg-rtmp-nginx/

 

2.1 必要なパッケージのインストール

sudo apt upgrade
sudo apt install nginx libnginx-mod-rtmp

 

2.2 諸々の設定

配信ディレクトリの用意

sudo mkdir -p /var/www/html/live/
cd /var/www/html/live/
sudo ln -s /dev/shm hls

 

Nginxに配信の設定

rtmpを受け取ってHLSを配信する設定を書く

sudo vi /etc/nginx/conf.d/rtmp

↑/etc/nginx/conf.d/rtmpの中身

rtmp {
    server {
        listen 1935;
        chunk_size 4096;
        allow play all;
        access_log /var/log/nginx/rtmp_access.log;

        application live {
            live on;
            hls on;
            record off;
            hls_path /var/www/html/live/hls;
            hls_fragment 1s;
            hls_type live;
        }
    }
}

 Nginxの設定に読みこませる

sudo vi /etc/nginx/nginx.conf

↑/etc/nginx/nginx.conf の末行に下記を書き足す

include /etc/nginx/conf.d/rtmp;

 

Nginxに設定を反映

sudo systemctl daemon-reload
sudo systemctl restart nginx

 

2.3 動作の確認

この時点で動画のストリーミングは可能になっている。

OBSの配信設定で、

サービス:カスタム...

サーバー:rtmp://(ラズパイIPアドレス)/live/

ストリームキー:stream

と入力して配信し、VLCプレーヤーを起動して「メディア>ネットワークストリームを開く」で下記URLを指定すると、OBSの配信映像が見れるハズ。

http://(ラズパイIPアドレス)/live/hls/stream.m3u8

 

3. Rasppbery Piをインターネットに公開

3.1 セキュリティ対策

公開する前に「最低限の」セキュリティ対策をしておく

まずRaspberry Piのデフォルトパスワードを変更する

passwd

そしてルーターファイアウォールが有効になっていることを確認。

ラズパイにアクセス制限を設定する

sudo vi /etc/hosts.allow

で下記を末尾に書き足す。下記例は自分のPCのIPアドレスが「192.168.1.2」の場合

ALL:192.168.1.

そして

sudo vi /etc/hosts.deny

で下記を末尾に書き足す

 ALL:ALL

 

3.2 ルーターの設定(ポートフォワーディング)

上記の設定ができたら、ルーター側でポートフォワーディングの設定する

固定したラズパイのアドレスに対してHTTP「80」とHTTPS「443」のフォワーディングを設定する

ポートフォワーディングの設定方法についてはルーターによって違うので割愛

 

設定が適切に出来ていたら、外の回線(例えばiPhoneとかで自宅Wi-Fiに繋いでない状態で)自宅のグローバルIPにアクセスしたらNginxの初期ページが表示されるハズ

 

グローバルIPは下記サイトとかで確認する

https://www.cman.jp/network/support/go_access.cgi

 

 

 

4. ドメインで取得、設定

この段階で外部に配信はできるけども、家庭用のネット回線のグローバルIPは変動するので、ドメイン(ホニャラリ.com)を取得して、常に同じURLでアクセスできるようにする

 

通常ドメインの取得には維持費がかかるが、無料のダイナミックDNSを利用する方法がある

ここではMyDNSでの設定を紹介する

http://www.mydns.jp

 

4.1 MyDNSでアカウント登録

MyDNSに新規登録はここ(わかりづらい)

http://www.mydns.jp/?MENU=010

ここに開通用のメールアドレス他、個人情報とか入力する

登録ができると、メールアドレス宛にマスターIDとパスワードが記載されたメールが届くハズ

そのログイン情報をつかってMyDNSにログインする。

 

4.2 ドメインの取得

MyDNSにログインできたら「DOMAIN INFO」からお好みのドメインを設定する。

この辺読むのがいいと思う

https://nw.myds.me/synology/setup-domain/

 

4.3 ドメインの自動更新

ログイン情報を元に、定期的にMyDNSに現在のグローバルIPを通知するスクリプトを作成する

vi /home/pi/noticeIp.sh

中身はこんな感じ

#!/bin/bash
wget -O - 'http://mydns000000:xxxxxxxxx@ipv4.mydns.jp/login.html'

mydns000000がマスターID

xxxxxxxxxがパスワードになる

スクリプトが作成できたら、それに実行権限を与える

sudo chmod 755 /home/pi/noticeIp.sh 

 試しに実行してみる

/home/pi/noticeIp.sh 

 なんか色々文字列が返ってきて「Login and IP address notify OK.」って文字がどこかにあればOK

ダメならなんかダメってメッセージが返ってくる

 

ちゃんと認証・グローバルIP通知ができたら、cronで定期実行してやるようにする

crontab -e

 エディタが立ち上がるので

30 * * * * /pi/home/noticeIp.sh

を末尾に加える。

 

おそらくこの時点で、先に設定したドメインにアクセスしたら自分のグローバルIPに転送されるようになってるはず。

ちなみに外部ネットワークから(スマホWi-Fiに繋がずに4G回線とかで)ドメインに接続したらRasppbery PiのNginx初期ページが表示されるだろうけども、自宅PCからドメインに接続してもルーターの設定画面を開くことになるかもしれない。

それはDNSの設定で色々ややこしいので、一番簡単な「自宅PCのhostsにMyDNSで取得したドメインに、Rapsberry PiのローカルIPを設定する」をオススメする。

 

5. SSLを無料で取得、設定

 通常SSLは維持費が掛かるが、MyDNSと同じ用に定期的に通知することで無料で取得できるサービスがある。

それが「Let’s Encrypthttps://letsencrypt.org/ja/

 

5.1 certbotのインストール

下記三つのコマンドを実行する

cd /usr/local/

sudo git clone https://github.com/certbot/certbot
sudo /usr/local/certbot/certbot-auto

  

5.2 SSL証明書の取得

ここではMyDNSで取得したドメインsample.mydns.jp とし、連絡先メールアドレスを sample@mail.com とする。適時買い替えて下記コマンドを実行する。

/usr/local/certbot/certbot-auto certonly --webroot -w /var/www/html/ -d sample.mydns.jp -m sample@mail.com

 ちゃんとポートフォワーディングとMyDNSの設定とNginxの設定が適切であれば

IMPORTANT NOTES:
- Congratulations! Your certificate and chain have been saved at:
/etc/letsencrypt/live/sample.mydns.jp/fullchain.pem
Your key file has been saved at:
/etc/letsencrypt/live/sample.mydns.jp/privkey.pem

といったメッセージが表示されてSSL証明書が保存されている。

 

あとSSL証明書も自動更新するように設定する

sudo crontab -u root -e

 して下記行を末尾に加える

 00 02 01 * * /usr/local/certbot/certbot-auto renew --force-renew --webroot-path /var/www/html/ --deploy-hook "systemctl reload nginx"

 

 

 

5.3 SSL証明書の反映

取得したSSLはNginxに反映する必要がある。

sudo vi /etc/nginx/nginx.conf

を開くと色々あるけど34行目あたりに

##
# SSL Settings
##

ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
ssl_prefer_server_ciphers on;

##
# Logging Settings
##

 といった記述があるはず。

このssl_prefer_server_ciphers on;の下あたりに設定を追記する。

上記がこんな感じになるはず

##
# SSL Settings
##

ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
ssl_prefer_server_ciphers on;
server {

    root /var/www/html;
    listen 443 ssl;
    server_name sample.mydns.jp;
    ssl_certificate /etc/letsencrypt/live/sample.mydns.jp/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/sample.mydns.jp/privkey.pem;

 

    add_header Access-Control-Allow-Origin '*';
    add_header Access-Control-Allow-Methods 'GET';
    add_header Access-Control-Allow-Headers 'Origin, Authorization, Accept,     Content-Type';
    add_header Access-Control-Max-Age 3600;

}

##
# Logging Settings
##

 

 上記を入力したらNginxを再起動して反映させる

sudo systemctl restart nginx 

 

これでhttps://sample.mydns.jp/live/hls/stream.m3u8 を外部ネットワークからiPhoneVLCプレーヤーで開けたら完了。

 

owari

 

さいごにオススメ漫画

 

 

幼少期編は正直顔のバランスに違和感感じる濃ゆいだけの漫画だけど、読んでたらマジで面白いからぜひポチってほしい

 

 

RubyでTwitchAPIをOAuth認証して叩くスクリプト

最近TwitchAPIがOAth認証してないと401を返すようになった

どうやら2020/5/12頃にアプデがあったようだ

https://discuss.dev.twitch.tv/t/requiring-oauth-for-helix-twitch-api-endpoints/23916

https://dev.twitch.tv/docs/authentication/getting-tokens-oauth/#oauth-client-credentials-flow

色々調べてRubyでクライエントIDとクライエントシークレット(ダッシュボードではクライアントの秘密という)でOAuth認証する「OAuth client credentials flow」に従って回収した。

もしはじめてAPIを叩く実装するなら、あらかじめhttps://dev.twitch.tv/でアプリケーション登録してクライエントIDとクライエントシークレットを取得してね

 

イカスクリプトメモ

require 'net/http'
require 'json'
require 'uri'

 

// https://dev.twitch.tv/console/apps で取得する「クライアントID」と「クライアントの秘密」

client_id = "xxxxxxxxxxclient_idxxxxxxxxxxxxxxxx"

client_secret = "xxxxxxxxxclient_secretxxxxxxxxxxxxx"

 

// 配信を調べたいTwitchのユーザー名 

twitch_id = "ink_kasekai"

 

// アクセストークンの取得

uri = URI.parse("https://id.twitch.tv/oauth2/token?client_id=#{client_id}&client_secret=#{client_secret}&grant_type=client_credentials")

response = Net::HTTP.post_form(uri ,{})

p response.code # status code
res_json = JSON.parse(response.body)

// アクセストークンを取得できた
access_token = res_json['access_token']

 

// アクセストークンを使ってAPI叩いてTwitchユーザー名からTwitchユーザーIDを取得
url = "https://api.twitch.tv/helix/users?login=#{twitch_id}"
uri = URI.parse(url)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme === "https"
headers = { "Content-Type" => "application/json", "Client-ID" => client_id, "Authorization" => "Bearer #{access_token}"}
response = http.get(uri, headers)
res_json = JSON.parse(response.body)

//ユーザーID取得できた

user_id = res_json["data"][0]["id"]

 

// アクセストークンを使ってAPI叩いてTwitchユーザーIDから配信情報を取得

url = "https://api.twitch.tv/helix/streams?user_id=#{user_id}"
uri = URI.parse(url)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme === "https"
headers = { "Content-Type" => "application/json", "Client-ID" => client_id, "Authorization" => "Bearer #{access_token}" }
response = http.get(uri, headers)
res_json = JSON.parse(response.body)

// 配信所法取得できた
p res_json['data']

 配信してたらデータに中身ある。中身が空なら配信してない

 

だいたいこんなかんじ

 

エミリコはかわいいわね

RubyからストリーミングサービスAPIで指定ユーザーが配信しているか確認(Twitch Mixer YouTubeLive)

配信サービスのユーザー名から現在配信しているか否かを判定するためのRubyコード

require 'net/http'
require 'json'
require 'uri'

が前提

Twitch

ここのGetting a client IDの(register your application on the Twitch developer portal.)からClientID("twichclientidxxxxxxxxxxxxxxxxx")を取得しておく。

dev.twitch.tv


streamer_idはTwitterのユーザー名

def get_streaming_status( streamer_id )
    response = twitch_client( "https://api.twitch.tv/helix/users?login=#{streamer_id}" )
    res_json = JSON.parse(response.body)
    if res_json["data"].present? then
        id = res_json["data"][0]["id"]
        response = twitch_client( "https://api.twitch.tv/helix/streams?user_id=#{id}" )
        res_json = JSON.parse(response.body)
        return res_json
    else
        return nil
    end
end


def twitch_client( url )
    uri = URI.parse(url)
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = uri.scheme === "https"
    headers = { "Content-Type" => "application/json", "Client-ID" => "twichclientidxxxxxxxxxxxxxxxxx" }
    response = http.get(uri, headers)
    return response
end

 

Mixer 

ここからClientID("mixerclientidxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")を取得する。

mixer.com

def get_streaming_status_mixer( streamer_id )
    response = client_mixer( "https://mixer.com/api/v1/channels/#{streamer_id}?fields=online" )
    if response.code.to_i != 200 then
        return true
    end
    res_json = JSON.parse(response.body)
    if res_json["online"].present? && res_json["online"] == true then
        return true
    else
        return nil
    end
end

def client_mixer(url)

    uri = URI.parse(url)
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = uri.scheme === "https"
    headers = { "Content-Type" => "application/json", "Client-ID" => "mixerclientidxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }
    response = http.get(uri, headers)
    return response
end

 

YouTube Live

ここからYouTubeDataAPIキー(YouTubeDataAPIkeyxxxxxxxxxxxxxxxxxxx)を取得しておく。

developers.google.com

YouTubeAPIキーの1日あたりの制限はしぶっちいので5分に1回とかだと簡単に上限に達するので注意

YouTube LiveはビデオIDかチャンネルIDかで方法が違う

def get_streaming_status_youtube_videoID( video_id )
    url = "https://www.googleapis.com/youtube/v3/videos?id=#{video_id}&key=YouTubeDataAPIkeyxxxxxxxxxxxxxxxxxxx&part=snippet"
    return get_streaming_status_youtube( url );
end


def get_streaming_status_youtube_channelID( channel_id )
    url = "https://www.googleapis.com/youtube/v3/search?part=snippet&type=video&eventType=live&channelId=#{channel_id }&key=YouTubeDataAPIkeyxxxxxxxxxxxxxxxxxxx"
    return get_streaming_status_youtube( url );
end

def get_streaming_status_youtube( url )
    uri = URI.parse(url)
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = uri.scheme === "https"
    headers = { "Content-Type" => "application/json" }
    response = http.get(uri, headers)
    if response.code.to_i != 200 then
        return true
    end
    res_json = JSON.parse(response.body)
    if res_json.blank? || res_json["items"].blank? || res_json["items"][0]["snippet"]["liveBroadcastContent"] == "none" then
        return false
    else
        return true
    end
end

 

以上 

 

全然関係ないけどオススメの漫画はる

とてもオススメしたい 

 

上記スクリプトは配信チャットで実装するのに必要でした

https://ikasekai.com/highchat/

 

Laravel Echo (Websocket ブロードキャスト)を試す

最近PHPフレームワークを触る機会が増えて、RailsのActionCableみたいなことがLaravelのEcho(ブロードキャスト 6.x Laravel)で実現できるらしいと興味を持ったので試してみた。

だいたい下記記事の通りだけど、一部記載の通りでは動作しなかったところもあるので捕捉的にで実現できるらしいと興味を持ったので試してみた。

 

だいたい下記記事の通りだけど、一部記載の通りでは動作しなかったり躓いたところもあるので捕捉として、Laravel初心者なので環境の構築からメモを残す。お試しのVbox上構築なので、セキュリティ対策とかそんな吟味してないので気を付けて!

qiita.com

 

長いのでまとめると

  • チャンネル購読するにはクライアント側でチャンネル名にRedisのprefix(laravel_database_)を足す必要があった。
  • ポート開放(firewall-cmd)忘れずに。
  • laravel-echo-server start でエラーでたら下記コマンドで直った。

sudo npm install n -g
sudo n latest

 

環境

  • CentOS 7
  • VirtualBox ネットワークはブリッジアダプタ(ipは 192.168.0.100 とする)
  • PHP 7.3
  • Laravel Framework 6.6.0
  • Apache/2.4.6
  • mariadb-server
  • Redis

 「larapro」が今回のテスト用のlaravelプロジェクト名です

ユーザー名は「centuser」ということにします。

構築

sudo yum install epel-release
sudo rpm -ivh http://rpms.famillecollet.com/enterprise/remi-release-7.rpm

sudo yum -y install --enablerepo=remi,remi-php73 php php-devel php-mbstring php-pdo php-gd php-xml php-mcrypt
sudo yum -y install --enablerepo=remi,remi-php73 php-opcache php-mysqlnd
sudo yum -y install --enablerepo=remi,remi-php73 composer
sudo yum -y install --enablerepo=remi,remi-php73 php-pecl-redis

 

 

DB周り

sudo yum install mariadb mariadb-server mariadb-client
sudo vi /etc/my.cnf

[client]
default-character-set=utf8mb4

[mysql]
default-character-set=utf8mb4

[mysqldump]
default-character-set=utf8mb4

[mysqld]
character-set-server=utf8mb4

sudo systemctl start mariadb
sudo systemctl enable mariadb
sudo mysql_secure_installation

mysql -u root -p

 文字コードの確認  UTF8mb4になってればOK

MariaDB [(none)]> show variables like '%char%';
+--------------------------+----------------------------+
| Variable_name | Value |
+--------------------------+----------------------------+
| character_set_client | utf8mb4 |
| character_set_connection | utf8mb4 |
| character_set_database | utf8mb4 |
| character_set_filesystem | binary |
| character_set_results | utf8mb4 |
| character_set_server | utf8mb4 |
| character_set_system | utf8 |

  • | character_sets_dir | /usr/share/mysql/charsets/ |

+--------------------------+----------------------------+

適当にデータベースを用意する

MariaDB [(none)]> CREATE DATABASE test_laravel;

 

Apache httpd周り

sudo mkdir /var/www/project

sudo chmod 2775 /var/www/project/

sudo chown apache:apache /var/www/project/

sudo usermod -aG apache centuser

sudo vi /etc/httpd/conf/httpd.conf

 Vbox上でIPアクセスするのでバーチャルホストではなくDocuRoot直接設定しちゃう

DocumentRoot "/var/www/project/larapro/public"

<Directory "/var/www">
AllowOverride None
Require all granted
</Directory>
<Directory "/var/www/project">
Options Indexes FollowSymLinks
AllowOverride All
Require all granted
</Directory>

 sudo systemctl restart httpd

sudo systemctl enable httpd

 

Redis

sudo yum install redis

sudo systemctl enable redis
sudo systemctl start redis

redis-cli

 

Firewall ポート開放

sudo firewall-cmd --add-service=http --permanent
sudo firewall-cmd --add-port=6001/tcp --permanent
sudo firewall-cmd --reload

Laravel構築

cd /var/www/project/

composer create-project --prefer-dist laravel/laravel larapro

cd larapro

npm install --save laravel-echo socket.io-client

composer require predis/predis

npm install -g laravel-echo-server

laravel-echo-server init

 こんな感じで対話形式でLaravel Echoの設定する

 192.168.0.100 のところは各々の環境のアドレスを設定してください

? Do you want to run this server in development mode? Yes
? Which port would you like to serve from? 6001
? Which database would you like to use to store presence channel members? redis
? Enter the host of your Laravel authentication server. http://192.168.0.100
? Will you be serving on http or https? http
? Do you want to generate a client ID/Key for HTTP API? Yes
? Do you want to setup cross domain access to the API? Yes
? Specify the URI that may access the API: http://192.168.0.100:80
? Enter the HTTP methods that are allowed for CORS: GET, POST
? Enter the HTTP headers that are allowed for CORS: Origin, Content-Type, X-Auth-Token, X-Requested-With, Accept, Authorizat
ion, X-CSRF-TOKEN, X-Socket-Id
? What do you want this config to be saved as? laravel-echo-server.json
appId: xxxxxxxxxxxxxxxx
key: 01010101010101010101010101010101
Configuration file saved. Run laravel-echo-server start to run server.

sudo laravel-echo-server start

 ※もし

Starting server in DEV mode...

/usr/lib/node_modules/laravel-echo-server/node_modules/ws/lib/websocket.js:347
...options
^^^

  というエラーがでたら

sudo npm install n -g
sudo n latest

 を実行しよう

 

ここから先はだいたい参考記事(https://qiita.com/zaburo/items/34289d4573f39113b25a)と同じ

ディレクトリは引き続きプロジェクトルート(/var/www/project/larapro)にて

vi config/app.php

vi .env

php artisan make:event PublicEvent

vi ./app/Events/PublicEvent.php

<?php

namespace App\Events;

use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class PublicEvent implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    /**
    * Create a new event instance.
    *
    * @return void
    */
    public function __construct()
    {
    //
    }

    /**
    * Get the channels the event should broadcast on.
    *
    * @return \Illuminate\Broadcasting\Channel|array
    */
    public function broadcastOn()
    {
        return new Channel('hoge-channel');
        // return new PrivateChannel('channel-name');
    }
    public function broadcastWith()
    {
        return [
            'message' => 'PUBLIC',
            ];
    }
}

vi ./routes/web.php 

 ここでは「/fire」にアクセスしたらEvent発火させることにした

Route::get('/', function () {
    return view('welcome');
});

Route::get('/fire', function(){
    broadcast(new \App\Events\PublicEvent);
    return 'public';
});

vi ./resources/js/bootstrap.js

 ファイル末尾に下記を挿入

import Echo from 'laravel-echo';
window.io = require('socket.io-client');

//接続情報
window.Echo = new Echo({
    broadcaster: 'socket.io',
    host: window.location.hostname + ':6001'
});

//購読するチャネルの設定
window.Echo.channel('laravel_database_hoge-channel')
    .listen('PublicEvent', (e) => {
    console.log(e);
});

  ※余談ハマった点

window.Echo.channel('laravel_database_hoge-channel')

  購読チャンネル名は「hoge-channel」だが、それに加えて「laravel_database」をprefixとして頭に付け加えなければならなかった。

 prefixがどこで設定されているかというと「./config/database.php」のこのあたり

'redis' => [

'client' => env('REDIS_CLIENT', 'phpredis'),

'options' => [
'cluster' => env('REDIS_CLUSTER', 'redis'),
'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
],

 

npm run dev

vi ./resources/views/welcome.blade.php

 <head>内に挿入

<meta name="csrf-token" content="{{ csrf_token() }}">

 <body>タグ最後に挿入

 <script src="{{ asset('js/app.js')}}"></script>

 

あとは参考記事(https://qiita.com/zaburo/items/34289d4573f39113b25a)と同じように、「http://192.168.0.100/」でコンソールを開いておいて「http://192.168.0.100/fire」を開いたらコンソールにPUBLICと表示されたら成功

 

おしまい

 

プライベートチャンネルは必要になったらまた試すかも

でもあとは参考記事と同じで大丈夫だと思う

 

重ねて感謝

qiita.com