情報工学 – koneta https://koneta.click DIYとデジモノとプログラミングとライフハックをコネた...小ネタ Sun, 13 Mar 2022 11:52:36 +0000 ja hourly 1 https://wordpress.org/?v=6.1 https://koneta.click/wp-content/uploads/2020/02/cropped-icon-32x32.png 情報工学 – koneta https://koneta.click 32 32 HTMLとCSSでサムネイルジェネレータを作ってみました https://koneta.click/p/1028 https://koneta.click/p/1028#respond Sun, 13 Mar 2022 11:52:35 +0000 https://koneta.click/?p=1028 どうも、繁忙期をなんとか乗り越え本ブログを更新できる時間が戻ってきました。しかし、またいつ時間が取れなくなるかはわかりません。そこで、せめてブログの更新時間が減るようにサムネイルを簡単に作れる仕組みを作ってみました。色々作り方はあると思いますが、今回はHTMLとCSSでサムネイルのフォーマットを作り、それを画像化するという方針です。

どんなのができる?

本ブログでは主に2種類のフォーマットでサムネイルを作っているのですが、その中でも一番使っているものを生成できるようにしてみました。

やっていることは、背景画像を半透明の黒で暗くして、そこに半透明の文字抜きの白をのせています。

できたもの

というわけで、このような物ができました。

動作画面イメージ

背景画像の項目で端末内の画像を選択すると背景画像として表示され、その下のタイトルの欄に文字列を入力するとタイトルとして表示されます。基本的に誰にも公開する気はなかったので見た目としては全く手を入れてません。

ちょっとだけコード詳細

大したことはしていませんが、少しだけどんなコードかを書いておこうと思います。

コード全体
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>サムネイルジェネレータ</title>
  <link rel="stylesheet" href="./resource/ress.min.css">
  <script src="https://cdnjs.cloudflare.com/ajax/libs/html-to-image/1.9.0/html-to-image.min.js"></script>
</head>
<body>
  <div class="container">
    <h1>koneta.click サムネイルジェネレータ</h1>

    <div id="thumbnail-background" class="thumbnail">
      <div class="thumbnail__background-black__wrapper"></div>
      <!-- 半透明のテキストエリア テキストはエリアに高さを持たせるためのダミー -->
      <div class="thumbanail__title-dummy__area">
        <p id="p-title-dummy" class="thumbanail__title-dummy__text">タイトルサンプル</p>
      </div>
      <!-- タイトル文字に背景が透過しているように見せるための要素 -->
      <div id="thumbnail-background-dummy" class="thumbanail__title__area">
        <p id="p-title" class="thumbanail__title__text">タイトルサンプル</p>
      </div>
    </div>

    <div class="contoroler">
      <h2>操作</h2>
      <h3>背景画像</h3>
      <input type="file" id="form-thumbnail-background">
      <h3>タイトル(HTML記法でOK)</h3>
      <textarea id="form-text-title">タイトルサンプル</textarea>
      <h3>保存</h3>
      <button id="button-save">画像にする</button>
      <div class="controler__result-img__wrapper">
        <img id="result-image" />
      </div>
    </div>
  </div>

  <script>
    // 背景画像用処理
    const fileInput = document.getElementById('form-thumbnail-background')
    fileInput.addEventListener('change', () => {
      const reader = new FileReader()
      // ファイルが読み込まれたときに実行する
      reader.onload = function (e) {
        const imageUrl = e.target.result
        const thumbnailBackground = document.getElementById("thumbnail-background")
        thumbnailBackground.style.backgroundImage = `url(${imageUrl})`

        const thumbnailBackgroundDummy = document.getElementById("thumbnail-background-dummy")
        thumbnailBackgroundDummy.style.backgroundImage = `url(${imageUrl})`
      }
      reader.readAsDataURL(fileInput.files[0])
    })

    // タイトル用処理
    const textInput = document.getElementById('form-text-title')
    textInput.addEventListener('change', () => {
      // 高さ産出用と背景を見せる用の要素にテキストを設定する
      document.getElementById('p-title-dummy').innerHTML = textInput.value
      document.getElementById('p-title').innerHTML = textInput.value
    })

    // 画像化関係処理
    const saveButton = document.getElementById('button-save')
    saveButton.addEventListener('click', () => {
      const thumbnail = document.getElementById('thumbnail-background')
      htmlToImage.toJpeg(thumbnail)
        .then(function (dataUrl) {
          document.getElementById('result-image').src = dataUrl
        })
        .catch(function (error) {
          console.error('oops, something went wrong!', error);
        });
    })
  </script>
  <style>
    @font-face {
      font-family: 'NikumaruFont';
      src: url(./resource/nikumaru.otf);
    }
    .container {
      width: 100%;
      max-width: 1000px;
      margin: 0 auto;
    }
    .thumbnail {
      width: 100%;
      aspect-ratio: 16 / 9;
      position: relative;
      background-color: #000000;
      background-size: cover;
      background-position: center;
    }
    .thumbnail__background-black__wrapper {
      width: 100%;
      height: 100%;
      position: absolute;
      top: 0;
      left: 0;
      z-index: 10;
      background-color: rgba(0, 0, 0, 0.3);
    }
    .thumbanail__title-dummy__area {
      width: 80%;
      padding: 3% 0;
      position: absolute;
      top: 50%;
      left: 50%;
      z-index: 20;
      transform: translate(-50%, -50%);
      background: rgba(255, 255, 255, 0.9);
      outline: 6px solid rgba(255, 255, 255, 0.9);
      outline-offset: 4px;
      text-align: center;
    }
    .thumbanail__title__area {
      width: 100%;
      height: 100%;
      display: flex;
      justify-content: center;
      align-items: center;
      text-align: center;
      position: absolute;
      top: 0;
      left: 0;
      z-index: 30;
      background-size: cover;
      background-position: center;
      background-clip: text;
      -webkit-background-clip: text;
    }
    .thumbanail__title-dummy__text {
      font-size: 3rem;
      font-family: NikumaruFont;
      color: transparent;
    }
    .thumbanail__title__text {
      font-size: 3rem;
      font-family: NikumaruFont;
      color: rgba(0, 0, 0, 0.3);
    }
    .controler__result-img__wrapper {
      width: 100%;
    }
    .controler__result-img__wrapper img {
      width: 100%;
    }
  </style>
</body>
</html>

今回のコードではサムネイルで使っているフォントやcssのリセットを読み込んでいますが、それらはライセンスの関係もありここでは載せていません。フォントはこちらのにくまるフォント、リセットにはress.cssを使っています。もし、そのまま当サイトと同じフォーマットのサムネイルを作りたい場合は上記の2つをresourseディレクトリに設置してからアクセスしてみてください。

ちなみにフォントやサムネイルの画像素材など、本サイトで使わせてもらっている素材たちはこちらのページにまとめていますので、気になった方はこちらをご覧ください。

要所としては入力された画像やタイトルをサムネイルに反映する部分だと思います。値が入力されたときそれを画面に反映しようと思ったとき、最近ではVueなどのフレームワークがうかぶかもしれませんが、今回のツールくらいであれば直書きで十分です。

まずは画像が選択されたときの処理です。

const fileInput = document.getElementById('form-thumbnail-background')
fileInput.addEventListener('change', () => {
  const reader = new FileReader()
  // ファイルが読み込まれたときに実行する
  reader.onload = function (e) {
    const imageUrl = e.target.result
    // 選択された画像を要素の背景に設定する
    const thumbnailBackground = document.getElementById("thumbnail-background")
    thumbnailBackground.style.backgroundImage = `url(${imageUrl})`
  }
  reader.readAsDataURL(fileInput.files[0])
})

これで画像読み込みはオッケーです。次にテキスト部分は以下のようになります。

const textInput = document.getElementById('form-text-title')
textInput.addEventListener('change', () => {
  document.getElementById('p-title').innerHTML = textInput.value
})

テキストに関してはこれだけです。これだけの記述量ですみ、環境構築も不要な点でもフレームワークは不要だと思います。これでサムネイル画像完成です。

もしこのコードから改造を行う場合は、サムネイルのフォーマットを作っているcssと上記のjsで変更する対象を調整すればいいと思います。

終わりに

もともとサムネイルを作る際はGIMPというフリーソフトを使っており、作業時間は30分ほどかかっていました。それがこのジェネレータを使うことで5分程度でサムネイルを作れるようになりました。また、webページとして作成したため、作業できる端末がGIMPをインストールしているデスクトップやMacBookに限られていたところを、主にブログ更新に使っているChromebookでもできるようになったのが大きいところです。

実はボタンを押すだけで画像を生成するところまで実装しようと思っていたのですが、どうやらいま出ているライブラリでは対応していないcssを使ってしまっているためうまく生成してくれませんでした。そのため今はフォーマット完成後スクリーンショットを撮るという微妙な形になっています。そのうち時間ができたらここらもどうにかしていきたいと思います。

あとは他のサムネイルもサクッと作れるように更新していこうと思います。

]]>
https://koneta.click/p/1028/feed 0
無料でSupabaseのバックアップを取得してみました https://koneta.click/p/1018 https://koneta.click/p/1018#respond Fri, 11 Mar 2022 12:47:40 +0000 https://koneta.click/?p=1018 こんにちは、Supabaseって便利ですよね。Firebaseの代替を狙っているだけあり、無料でDBや会員管理、ストレージなどFirebaseにも負けず便利です。ほとんどの場合は無料で十分だと思います。

しかし、無料で1つだけ物足りない点があります。それがDBのバックアップです。残念ながらバックアップ機能は有料で月$25以上を払わないといけません。そこで今回はPythonでSupabaseのバックアップ/リストアを行うスクリプトを書いたので記事にまとめておこうと思います。

私の用途ではそのうち容量も足りなくなる見込みなので有料プランに変更する見込みなので、みなさんも一時的なものとして見てみてください。

環境準備

本記事ではバックアップの処理をPythonで書くので、その環境を用意します。今回はDocker上のPythonで雑に用意しようと思います。ちなみにここで書くDocker設定は他のスクリプトを書いたときのものを流用しているため、不要な設定などがあるかもしれないので参考までにご覧ください。

というわけでDockerに必要なファイルを用意します。ディレクトリの連携も雑に対応していくためdocker-compose.ymlDockerfileそしてPythonでインストールするライブラリを記述するrequirements.txtを用意します。正直なところ必要なライブラリはSupabaseだけなのでテキストファイルを用意するっ必要もないですが、おまけです。

version: '3'
services:
  python:
    container_name:  supabase-python-backup
    build: .
    volumes:
      - .:/workspace
    tty: true
FROM python:3.9.10

RUN apt-get update

WORKDIR /workspace
COPY ./requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
supabase==0.3.0

ちなみにSupabaseのPython向けライブラリは、現在まだベータ版とのことなので、新しいバージョンが出た際は指定バージョンを変更するようにしてください。

これらと次に紹介するスクリプトを同じディレクトリに設置して下準備完了です。今回はDocker環境で動かすためにこのようにしましたが、supabaseのライブラリが動けば何でも大丈夫です(直接叩けばライブラリすら不要です)。

APIキーを準備

次にSupabaseの操作を行うためのAPIキーを取得します。 Supabaesの管理画面にログインしてメニューのSettings→APIの項目からProject API keysのservice_roleConfigurationのURLをメモしておいてください。

APIキーの位置

スクリプトの用意

さて、次は本題のスクリプトです。今回は雑にクラスにまとめているのでそれだけ掲載しておこうと思います。git?知らない子ですね。

import os
import shutil
import pickle
from supabase import create_client, Client

os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1'

class supabaseBackup():
  def __init__(self, supabase_api_url:str, supabase_api_key:str):
    self._supabase: Client = create_client(supabase_api_url, supabase_api_key)

    # エクスポート出力先ディレクトリ
    self._FILE_DIR = './backup'
    
    # Supabaseで設定している1回のクエリの取得件数上限
    self._MAX_ROWS = 1000


  def _save(self, table_name:str, data:any):
    """ データをファイル出力する
    Args:
      table_name (str): 出力するテーブル名
      data (any): 出力するデータ
    """
    with open(f'{self._FILE_DIR}/backup-{table_name}.pkl', 'wb') as f:
      pickle.dump(data, f)

  def _load(self, table_name:str):
    """ ファイルからテーブルのデータをインポートする
    Args:
      table_name (str): インポートするテーブル名
    Return:
      any: ファイルから読み込んだデータ
    """
    with open(f'{self._FILE_DIR}/backup-{table_name}.pkl', 'rb') as f:
      return pickle.load(f)


  def copy_to_date_directory(self):
    """ ディレクトリに保存されているpklファイルを日付ディレクトリに移動する
    """
    import datetime
    date = datetime.datetime.today().strftime("%Y%m%d%H%M%S")
    dir = f'{self._FILE_DIR}/{date}'

    os.makedirs(dir, exist_ok=True)

    # 既存の pkl ファイルを日付ディレクトリにコピーする
    import glob
    for p in glob.glob(f'{self._FILE_DIR}/*.pkl'):
        shutil.copy(p, dir)

  def supabase_backup_table(self, table_name:str):
    """ Supabase の指定テーブルのデータをすべてファイルに出力する
    Args:
      table_name (str): 出力するテーブル名
    """
    i = 0
    rows = []
    while True:
      offset = self._MAX_ROWS * i
      data = self._supabase\
                 .table(table_name)\
                 .select('*')\
                 .limit(1000, start=offset)\
                 .execute()\
                 .data
      if len(data) == 0:
        break
      else:
        i += 1
        rows += data

    self._save(table_name, rows)

  def supabase_restore_table(self, table_name:str, import_to_table_name:str = ''):
    """ Supabase の指定テーブルにファイルからデータをインポートする
    Args:
      table_name (str): インポートするテーブル名
      import_to_table_name (str): インポート先のテーブル名(ステージング環境テーブルにインポートする場合に使用する)
    """
    if import_to_table_name == '':
      import_to_table_name = table_name

    import_data = self._load(table_name)

    for i in range(0, len(import_data), self._MAX_ROWS):
      self._supabase.table(import_to_table_name).insert(import_data[i:i+self._MAX_ROWS]).execute()

  def supabase_clean(self, table_name:str):
    """ Supabase の指定テーブルのデータを空にする
    Args:
      table_name (str): データを空にするテーブル名
    """
    self._supabase.table(table_name).delete().execute()

さて、コードを書いてしまったのでこれ以上書くことはないのですが、実装時に少しハマったところは取得件数上限が1000件なので、それ以上にレコードがある場合はそれだけクエリを実行しなくてはいけないという点です。しかし、クエリ結果のDLは制限なしというのは嬉しいですね。

使ってみる

最後にこのスクリプトを使ってみようと思います。上記でクラスの定義はできているので、使うだけです。

if __name__ == '__main__':
  # Supabase
  SUPABASE_API_URL: str = 'TODO Supabase_URL https://xxxxxxxxxxxxxxxxxxxxxxxxx.supabase.co'
  SUPABASE_API_KEY: str = 'TODO SUpabase_KEY xxxxxxxxxxxxxxxxxxx......xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'

  backup = supabaseBackup(SUPABASE_API_URL, SUPABASE_API_KEY)


  # エクスポート
  backup.supabase_backup_table('TODO エクスポートするテーブル名')

  # 保存されているバックアップファイルを日付付きのディレクトリにコピー
  backup.copy_to_date_directory()

  # ステージング環境のテーブルをクリア
  backup.supabase_clean('TODO インポートするテーブル名')

  # ステージング環境にリストア
  backup.supabase_restore_table('TODO インポートするテーブル名', 'TODO インポート先のテーブル名')
$ docker exec -it supabase-python-backup python main.py

このスクリプトで、SupabaseのDBからデータを全件エクスポートして、インポートするという一連の流れが実行されます。設定値をベタ書きするなとか、バックアップファイルの扱い方とかツッコミ所はありますが、私の要望は満たしてくれているので一旦おいておきます。

終わりに

というわけで、無料プランのままバックアップを取る方法を書いてみました。もうDBの容量が8割くらいまで行っているのですぐ有料バックアップを使えるようになりますが、無料で使えるうちは使い倒してやりますよ!

]]>
https://koneta.click/p/1018/feed 0
スプレッドシート+GAS+GCPで簡易的なSSL証明書期限監視システムを構築してみました https://koneta.click/p/990 https://koneta.click/p/990#respond Sat, 15 Jan 2022 12:33:19 +0000 https://koneta.click/?p=990 昨今はLet’sさんやらホスティングサービスで勝手に更新してくれることの多いSSL証明書ですが、意外と手動で更新しなくてはいけないこともあります。

そして、更新ができないとサイトにアクセス時にエラーが表示されてしまい良くないです。

そこで、ちゃんと証明書の更新ができているかを定期的に確認する必要があるのですが、手動ではどうしても忘れやすく、とてもめんどくさいです。

というわけで、 今回は簡易的に証明書の有効期限を確認する仕組みを構築してみようと思います。今回はGCPのCloud RunでfastAPIの処理を動かし、監視するサイトリストをスプレッドシートで管理できるように作ってみようと思います。

指定サイト証明書の有効期限を返すAPIを作成する

まずは指定されたサイトURLの証明書有効期限を返すAPIを作成します。

この工程ではGCPのCloud Run上に確認用の処理をデプロイします。基本的には無料で何とかなる範囲ですがクレジットカードなど課金情報の登録が必要になります。もし課金情報の入力が嫌な場合は、有志が作成しているAPIを使ってみたり、各サイトの証明書期限を手動で入力するようにすれば、この工程は不要になります。

さて、では早速やっていきたいと思います。今回は必要最低限のところだけ書いていこうと思います。

処理の準備

まずはDockerfileの準備です。基本的に動作に必要なものがインストールできれば問題ないのですが、CloudRunの仕様上8080ポートでアクセスできるようにする必要があります。また今回はpipでインストールものをまとめておけるようにrequirements.txtに記述して一括でインストールするようにしておきました。

FROM python:3.7.12

COPY ./apps /apps
WORKDIR /apps

COPY ./requirements.txt ./
RUN pip install -r requirements.txt

CMD ["uvicorn", "main:app", "--reload", "--host", "0.0.0.0", "--port", "8080"]
fastapi==0.63.0
uvicorn==0.13.3
pyOpenSSL==21.0.0
pytz==2018.9

次にfastAPIの処理を実装していきます。今回はfastAPIについては全く触れず処理のみ掲載します。とりあえずコピペしていただければ動くと思います…。

#coding: utf-8
import uvicorn
from fastapi import FastAPI
from routers import CertificateExpiration

app = FastAPI()

@app.get("/")
def get_root():
    return {"message": "Hello World"}

app.include_router(CertificateExpiration.router)
#coding: utf-8
from urllib.parse import urlparse
from fastapi import APIRouter
from pydantic import BaseModel
import datetime
import pytz
import OpenSSL
import ssl

router = APIRouter()
    

@router.get("/certificate-expiration")
async def get_certificate_expiration(url: str):
    try:
        # URLからドメインを抜き出す
        parsed_url = urlparse(url)
        domain = parsed_url.netloc

        cert = ssl.get_server_certificate((domain, 443))
        x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert)
        expiration = x509.get_notAfter()

        # 表記を調整
        expiration = expiration.decode('utf-8')
        expiration = f'{expiration[0:4]}-{expiration[4:6]}-{expiration[6:8]}'

        return {"expiration": expiration}
    except:
        # エラーが発生したので今日の日付を返す
        return {"expiration": datetime.datetime.now(pytz.timezone('Asia/Tokyo')).strftime("%Y-%m-%d")}

ファイル構造はこのようになります。

/
│  Dockerfile
│  requirements.txt
│
└─ /apps
   │  main.py
   └─ /routers
            CertificateExpiration.py

今回やることだけであれば、main.pyCertificateExpiration.pyを分ける必要もないのですが、将来的にこのシステムにほかのAPIも追加していきたいと考えているため、今後処理を追加しやすいように分割して実装しています。

Cloud Runのプロジェクトを作成

処理が書けたので次はデプロイ先であるCloudRunの準備を進めます。まずはCloudSDKのインストールです。OSによって方法がちょっと違うので、公式のマニュアルを参考にしてください。

インストールができたらGCPの管理画面へアクセスし、プロジェクトを作成します。

手順としては管理画面にログインすると画面上部のプルダウン(下記画像内では「プロジェクトの選択」)があり、その中の「新しいプロジェクト」から作成できます。作成時にプロジェクIDが表示されているのでこれをメモしておいてください。

画面イメージ

初めてCloudRunを使う場合は決済情報を登録する必要がります。これはハンバーガーメニューの「お支払い」から登録することができます。これでGCP管理画面の操作はおしまいです。

次に端末から下記のコマンドを実行しプロジェクトを選択しておきます。

$ gcloud init

あとは画面に表示される項目に合わせて登録していけば準備完了です。

デプロイ

プロジェクトの準備ができたのでいよいよデプロイです。デプロイは下記のコマンドで実行できます。プロジェクトIDの部分は、上記手順で確認したそれぞれのIDに書き換えてください。

$ gcloud builds submit \
     --tag gcr.io/[プロジェクトID]/backend-app
$ gcloud run deploy [プロジェクトID] \
     --image gcr.io/[プロジェクトID]/backend-app \
     --region asia-northeast1 \
     --platform managed \
     --allow-unauthenticated

これらのコマンドはシェルなどにまとめておくと今後デプロイするときの役に立つのでオススメです。ステージング環境にアップロードしたり自動でトラフィックの切り替えたくない場合は適宜書き換えちゃってください。

監視するサイトリストを作成する

さて、下準備は完了したので次はスプレッドシート上での作業になります。いつも通りにシートを作成してください。シートの内容は以下の通りです。証明書期限の項目は処理で自動入力されるため、事前に必要なのはサイト名とURL、通知フラグ、そして式を残日数に入力します。ちなみに通知フラグのチェックボックスはメニューの挿入→チェックボックスから入れることができます。

シート入力イメージ

残日数に入力する式は以下のようにD列の日付から証明書の期限が切れる残日数を計算します。おまけで証明書期限が入力されていない場合は空白になるようにしています。

=IF(D3="","",(IFERROR(DATEDIF(TODAY(), D3,"D"),0)))

もしシートの構造を変えたい場合はこちらの式を次に紹介する監視処理の値を修正してください。

サイトリストの期限を確認する処理を実装する

サイト監視リストができたので最後に監視を行う処理を用意して作業完了です。監視にはGASを使います。画面上メニューの拡張機能からApps Scriptを選んでください。入力する処理は以下の通りです。2箇所通知の送信先メールアドレスと上記で作成したAPIのURLはそれぞれの環境に合わせて書き換えてください。


// 通知先のメールアドレス
const NOTIFICATION_TO_ADDRESS = "[送信先メールアドレス]"

// API URL
const API_URL = '[API URL]'

// 通知を送信する残日数リスト
// ※ 最小の日付からは毎日通知する
const NOTIFICATION_EXPIRATION_DATE_LIST = [7, 30, 45];
const MIN_EXPIRATION = Math.min.apply(null, NOTIFICATION_EXPIRATION_DATE_LIST);

const ROW_ID_TABLE_START = 2; // サイトリストが始まる行数

const COLUMN_ID_SITE_NAME  = 1; // サイト名を掲載している列番号
const COLUMN_ID_SITE_URL   = 2; // サイトURLを掲載している列番号
const COLUMN_ID_EXPIRATION = 4; // 有効期限残日を算出している列番号
const COLUMN_ID_SEND_FLG   = 5; // 送信フラグを指定している列番号

/* 証明書の有効期限を確認する
 *
 */
function checkCertificateExpiration(url) {
  const requestUrl = `${API_URL}?url=${url}`
  const response = UrlFetchApp.fetch(requestUrl)
  const responseCode = response.getResponseCode()
  const responseText = response.getContentText()

  if (responseCode === 200) {
    const responseTextObject = JSON.parse(responseText); 
    return responseTextObject['expiration'];
  } else {
    // 有効期限が取得できなかったので今日の日付を入れておく
    return Utilities.formatDate( date, 'Asia/Tokyo', 'yyyy-MM-dd'); 
  }
}


/* 通知する
 * @param array 行の内容
 */
function sendNotifiication(rows) {
  notificationText = "";
  for (let i in rows) {
    const row = rows[i];
    notificationText += `サイト名: ${row[COLUMN_ID_SITE_NAME]} 残日数: ${row[COLUMN_ID_EXPIRATION]}日\nURL: ${row[COLUMN_ID_SITE_URL]}\n`;
  }

  const subject = "【重要】 SSL証明書失効警告";
  const body = `SSL証明書失効期限が迫っています!\n\n${notificationText}`;

  MailApp.sendEmail(NOTIFICATION_TO_ADDRESS, subject, body, {
    noReply: true
  });
}

/* リストの残日数を確認して指定された日付だった場合は通知する
 *
 */
function checkExpiredDate() {
  const sheet = SpreadsheetApp.getActiveSheet();
  const rows = sheet.getDataRange();
  const numRows = rows.getNumRows();
  let values = rows.getValues();

  //  証明書の有効期限を更新する
  for (let i = ROW_ID_TABLE_START; i <= numRows - 1; i++) {
    const row = values[i];

    // 通知送信フラグがONのもののみ有効期限を更新する
    if (row[COLUMN_ID_SEND_FLG]) {
      const certificateExpiration = checkCertificateExpiration(row[COLUMN_ID_SITE_URL]);
      sheet.getRange(i+1, COLUMN_ID_EXPIRATION).setValue(certificateExpiration);
    } else {
      sheet.getRange(i+1, COLUMN_ID_EXPIRATION).setValue('');
    }
  }

  // 有効期限を更新したので残日数を再計算する
  SpreadsheetApp.flush();
  values = rows.getValues();


  // 送信するサイトの列情報リスト
  let notificationRows = [];

  for (let i = ROW_ID_TABLE_START; i <= numRows - 1; i++) {
    const row = values[i];

    // 通知送信フラグがONのもののみ判定する
    if (row[COLUMN_ID_SEND_FLG]) {
      if (NOTIFICATION_EXPIRATION_DATE_LIST.indexOf(row[COLUMN_ID_EXPIRATION]) !== -1) {
        // 指定日数の場合は通知リストに追加する
        notificationRows.push(row)
      } else if (row[COLUMN_ID_EXPIRATION] <= MIN_EXPIRATION) {
        // 指定日数の最小日からは毎日通知リストに追加する
        notificationRows.push(row)
      }
    }
  }

  // 通知対象のサイトがある場合は通知を行う
  if (notificationRows.length > 0) {
    sendNotifiication(notificationRows);
  }
}

実行時にはcheckExpiredDate関数を選択して実行ボタンを押します。初回はスプレッドシートにアクセスする権限やメールを送信する権限など許可が求められます。許可画面では一度警告画面が表示されますが、詳細からすすめることができます。

最後に通知確認のため、証明書の期限残にあわせてNOTIFICATION_EXPIRATION_DATE_LISTで設定している日数を調整して実行してみてください。「【重要】 SSL証明書失効警告」というメールが送信されていれば作業完了です。

終わりに

というわけで今回は、ほぼスプレッドシートで証明書監視機能を構築してみました。これまでGASはなんとなくダサい感じがしていて食わず嫌いをしていたのですが、今回触ってみてそのお手軽さにハマってしまいそうです。今後も色々小さいアプリを作って行きたいと思います。

]]>
https://koneta.click/p/990/feed 0
GoogleスプレッドシートをCMS&APIとして使ってみました https://koneta.click/p/894 https://koneta.click/p/894#respond Sat, 27 Nov 2021 12:32:38 +0000 https://koneta.click/?p=894 今、ちょっとしたWebサービスを構築しているのですが、その中でちょっとしたお知らせ機能を実装したくなり、NEWS機能を実装することになりました。しかし他の機能も実装している中、NEWSの登録機能まで作るのは大変なので、どうやったら簡単にデータを用意できるかを考えてみました。

このような場合、最近であればmicroCMSなどのヘッドレスCMSなどを使うのがあるあるになるかと思います。しかし今回はとっても小規模なので、それすらオーバースペックな気がしてしまいました。そこで今回はGoogleさんのスプレッドシートにNEWS内容を入力して、それをAPIから取得する方針でやっていきたいと思います。

スプレッドシートを用意する

まずはNEWSを入力するシートを用意します。シート自体はいつも通り作成して、下記の画像のように必要な情報を記入していきます。

記事サンプル

今回は、NEWSの日時とタイトル、本文を入力してみました。もし他にもNEWSのカテゴリだったり筆者だったり必要な情報がある場合はその分だけカラムを追加していけば大丈夫です。

データの準備ができたら、次の手順のためにシートの名前とドキュメントのIDを確認しておきます。シートの名前は画面下から確認できます。この名前は自分で好きなものに変更することができるため、わかりやすい名前にしておくといいと思います。また、ドキュメントのIDはURLから確認できます。https://docs.google.com/spreadsheets/d/[ここがドキュメントのIDです]/edit#gid=0 このIDをメモしておいてください。

スクリプトを用意

データの準備ができたら次にスプレッドシート側でスプレッドシートの内容をAPIとして呼び出せるようにスクリプトを用意します。

上記で用意したスプレッドシートのメニューから「拡張機能」→「Apps Script」と選択していきます。そして表示されたエディタに下記のコードを入力します。1行だけ上で確認したシート名とドキュメントIDに書き換えてください。

function getData(id, sheetName) {
  var sheet = SpreadsheetApp.openById(id).getSheetByName(sheetName);
  var rows = sheet.getDataRange().getValues();
  var keys = rows.splice(0, 1)[0];
  return rows.map(function(row) {
    var obj = {}
    row.map(function(item, index) {
      obj[keys[index]] = item;
    });
    return obj;
  });
}

function doGet(e) {
  var data = getData('[※※※ドキュメントID※※※]', '[※※※シート名※※※]');
  var output = ContentService.createTextOutput(JSON.stringify(data, null, 2));
  output.setMimeType(ContentService.MimeType.TEXT);
  return output;
}

入力出来たら一度手動で実行してみます。まずはプルダウンから実行売る関数を選択します。今回はdoGetを選択します。

関数選択中

最後に、プルダウンの左にある実行を押します。初回はGoogleアカウントの承認が必要になります。「このアプリはGoogleで確認されていません」と表示されますが、自分で作成しているアプリなので「詳細」から進んじゃってください。これでスクリプトが実行されているはずです。「お知らせ 実行完了」と表示されていれば大丈夫かと思います。

正常に実行されていない場合は、ドキュメントIDやシート名があっているか、実行している関数がdoGetになっているかを確認してみてください。

最後にこのスクリプトに外部からアクセスできるようデプロイします。デプロイは画面右上の「デプロイ」ボタンからできます。このボタンから歯車マークの「ウェブアプリ」を選択します。

デプロイ層あ

あとは必要な情報を入力します。今回はサイトのフロントエンドから呼ばれるため「アクセスできるユーザー」は「全員」を選択します。これでデプロイは完了です。最後にAPIにアクセスするためのURLが発行されているので、あとはここにアクセスするだけでデータを取得することができます。

APIとして呼び出してみる

最後に用意できたNEWS取得APIからデータを取得しています。まずは上記のスクリプトを用意したときに発行されたURLに直接アクセスしてみます。データが取得できていれば作業完了です。

今回、私はNuxtJSでサイトを構築していますが、おまけとしてコンポーネントからaxiosで呼び出しているところも書いておきます。

<template>
  <v-row>
    <v-col v-if="isLoadedNews" cols="12">
      <h3>NEWS</h3>
    </v-col>
    <v-expansion-panels accordion>
      <v-expansion-panel v-for="(news, index) in newsList" :key="index">
        <v-expansion-panel-header>
          {{ news.date }} - {{ news.title }}
        </v-expansion-panel-header>
        <v-expansion-panel-content>
          <p style="white-space: pre-wrap" v-text="news.body"></p>
        </v-expansion-panel-content>
      </v-expansion-panel>
    </v-expansion-panels>
  </v-row>
</template>
<script>
export default {
  props: {
    isDisplayLoading: {
      type: Boolean,
    },
  },
  data: () => ({
    newsList: [],
  }),
  computed: {
    isLoadedNews() {
      return this.newsList.length > 0
    },
  },
  async mounted() {
    await this.$nextTick(async () => {
      if (this.isDisplayLoading) this.$nuxt.$loading.start()
      await this.$axios.get(this.$NEWS_API.url).then((response) => {
        this.newsList = response.data
      })
      if (this.isDisplayLoading) this.$nuxt.$loading.finish()
    })
  },
}
</script>

Vuetifyが入っていたりローディング用の変数があったりと関係ない部分がありますが、取得部分はawait this.$axios.get(this.$NEWS_API.url).then でデータを持ってくることができています。

サイトの様子

というわけで、APIからデータを簡単に取得することができました!

おわりに

さて、つれずれなるままに書いてみました。今回は超最低限のCMSとしてスプレッドシートを使ってみました。なんといっても簡単にNEWS記事を管理することができ、APIから簡単に取得までできお手軽でした。最低限機能としては優秀だったかと思います。需要を満たしていればこれだけでも十分という場面は結構ありそうです。

ただ、今回紹介した方法だけでは画像やリンクが張れなかったり、そもそもデータ取得のレスポンスが遅かったりと気になる点はけっこうあります。なので今回の方法は「あくまでこんな方法もあるらしいですぜ」という感じでお願いします!

]]>
https://koneta.click/p/894/feed 0
ChromeBookで作業したいのでVPSにDockerで「Eclipse Theia」環境を構築してみました https://koneta.click/p/848 https://koneta.click/p/848#respond Fri, 23 Jul 2021 18:28:25 +0000 https://koneta.click/?p=848 先日ChromeBookを購入しました。このちょろめ君は、そのお手軽さで意外と色々なタイミングで出動してくれています。しかしChromeBookでお手軽にできるのはおおよそブラウザのみでできる範囲になります。Androidアプリも使う事はできますが、やはり物足りないもので、ChromeBookでできることを増やそうと奮闘する毎日です。

私のPCの使いみちの中で大きいのが、主にWeb系のコーディングです。その昔であればメモ帳とブラウザだけあればWebページを作ることができる…という話でしたが、現在のWeb系はフロントエンドもツールやフレームワークが充実し、開発環境をしっかり構築しないといけなくなりました。

というわけで今回はブラウザからいい感じの開発環境にアクセスできるようWeb上のエディタとして「Eclipse Theia」を構築してみようと思います。今回はこれをVPS(ConoHa)上に構築することで、どの端末からも同じ環境にアクセスできるようにしてみます。

Docker環境準備

まずはDocker環境の準備です。Docker環境の構築は以前記事にしていますので、まだDocker環境を作っていない方はこちらを参考にしていただけると幸いです。

$ docker --version
Docker version 19.03.13, build 4484c46d9d
$ docker-compose --version
docker-compose version 1.25.5, build 8a1c60f6

今回はVPS(ConoHa)で作業しましたが、中身は普通のCentOSなので普通にDocker(とdocker-compose)環境が作成できれば大丈夫だと思います。

リバースプロキシの準備

次はProxyの用意です。こちらは必須の作業ではありませんが、同じVPS上で動作させているのであれば、簡単に複数サイト/ツールで使用できSSL設定も自動で行ってくれるようになるため使用するのをおすすめしておきます。こちらも以前記事にしていますので参考にしてみてください。

今回はVPS上に設置する事、加えてすでにほかのツールを設置していることやそれぞれのツールにドメインで簡単にアクセスできるようにしたかったのでプロキシの設定が必須でしたが、ローカルで動かす場合やテストで動かしたいだけであれば設定は不要です。

Eclipse Theia環境を作る

docker-compose.ymlを用意

さて、周辺環境の構築ができましたので、いよいよ本題、「Eclipse Theia」の構築をやっていきます。とはいえDocker上に構築していくので、動かすだけであれば操作自体はとっても単純です。

version: '3'

services:
  eclipse-theia:
    restart: always
    image: theiaide/theia-full # 全部入りイメージ
    ports:
      - 50000:3000
    volumes:
      - /path/to/workspace:/home/project/:cached
    environment:
      - VIRTUAL_HOST=[ドメイン (プロキシ用)]
      - LETSENCRYPT_HOST=[ドメイン (Let's用)]
      - LETSENCRYPT_EMAIL=[メアド (Let's用)]
                                
networks:
  default:
    external:
      name: common_link

はい。今回はdocker-composeを使って環境を構築していくので、上記の内容でおなじみのdocker-compose.ymlを作成してください。修正するのはvolumesのワークスペースディレクトリ部分とenviromentのドメイン設定部分だけです。

ちなみに、ディレクトリ設定の方は設定したディレクトリ以下であればその部分だけ表示することができるため、ワークスペースのルートディレクトリを設定してあげるといいと思います。また、プロキシを利用しない場合はenviromentnetworksの設定は削除してもらって大丈夫です。

BASIC認証の用意

今回は外部からアクセスできる場所に設置するため一応アクセス制限用の設定をします。今回は簡単にBASIN認証でお茶を濁そうと思います。やり方は上記のリバースプロキシ設置時の記事に書いてありますので参考にしてみてください。

$ htpasswd -c /path/to/[ドメイン] [BASIC認証 ID]
New password: [BASIC認証 PASSWORD]
Re-type new password: [もう一度]

基本的に対応したパスワードファイルを読み込んであげるだけですが、ファイル名をドメインと同じにするのを忘れないようにしてください。

エディタのインストール

最後にオマケとしてCUIにエディタ(micro)を入れておきたいと思います。基本的には完全に無駄な作業です。Eclipse Theiaのコンテナは基本的にUbuntuで動いているらしく、エディタ内にターミナルの機能もあります。このターミナル機能を使うことでSSHで別のサーバにアクセスすることもできます。

しかしSSH先でvimなんぞを使ってしまうとESCキーがエディタ側に吸われてしまうため、vimで入力モードから脱出することができません。そこで(SSH先のサーバに)別のエディタを入れておきます。

curl https://getmic.ro | bash

というわけでmicroというエディタを入れておきました。こちらはCUIのソフトながら使い勝手はGUIにも匹敵するエディタで、少し前におすすめされたいたので入れてみました。まあ、こちらは完全にオマケなのでご自身の使い道に合わせて必要があれば入れてみてください。

実行してみる

準備ができたらいつも通りdocker-composeコマンドで実行して作業完了です。

$ cd /path/to/[docker-compose.ymlディレクトリ]
$ docker-compose up --build -d

実行できたらブラウザからアクセスしてみます。

アクセスした様子

(DNS or hostsの設定が完了していれば) 先ほど設定したドメインでアクセスするとワークスペースのディレクトリでVSCodeを起動したような画面が出てきます。これにて作業完了です。

終わりに

さて簡単に書いてきました。これのおかげで私のChromeBook君の使い道が爆発的に増えました。コーディング環境ができたのはもちろんですが、一緒に構築できたターミナル環境が思っているよりも便利でした。今後もChromeBookの使い道を増やしていけるよう探索を続けていきたいと思います。

]]>
https://koneta.click/p/848/feed 0
Nature Remo APIでn8nから家電を遠隔操作してみました https://koneta.click/p/791 https://koneta.click/p/791#respond Wed, 09 Jun 2021 16:24:36 +0000 https://koneta.click/?p=791 以前の記事でNature Remoというスマートリモコンで生活の未来度合いを向上することができたという記事を書きました。今回はそのRemoの活躍の場を広げるためプログラムから操作する…つまりはAPIを使って操作してみようと思います。加えてn8nでフローから操作もやってみようと思います。

背景

Nature Remoはスマホやスマートスピーカーから家電を簡単に操作することができるスマートリモコンです。私はその中でもNature Remo miniという端末を使用しています。Remo自体は紹介記事を書いていますのでよかったら読んでみてください。

また、IFTTTというサービスを活用することでワークフローを作成して、タイマーや居場所、SNSなどで設定した条件を満たしたときにRemoから家電を操作することができます。しかしIFTTTは最近無料で使用できる範囲が狭くなり使い勝手が悪くなってしまいました。 (私は元々使ってなかったので関係はなかったですが…)

そんな中、IFTTTの代わりとしてよく名前が挙がっているのがn8nです。n8nは無料で色々なサービスの連携/自動実行フローを作成できるのOSSのツールです。

n8nにRemoが公式に対応しているわけではないですが、RemoにはAPIが用意されているためHTTPSリクエストで家電を操作することができます。今回はこのAPIとn8nを使ってちょっと違った遠隔操作をしてみようと思います。

アクセストークンを取得する

APIを使用するためのアクセストークンを取得していきます。というわけでまずは home.nature.global にアクセスしてアクセストークンを作成します。

チープっぽいですが公式です。

ページにアクセスしたらGoogleアカウントで認証するかNatureに登録しているメールアドレスを入力してログイン用のメールを送信してもらってください。ログインができると「home.nature.globalがRemoへのアクセスをリクエストしています。」というページが表示されるので「許可する」を選択して完了です。

トークンは発行直後しか出ないのでモザイクの意味は…

権限の確認が終わると「Generate Access Token」のボタンが表示されます。このボタンを押すとアクセストークンが取得できます。一度ページを離れてしまうとアクセストークンは表示されないのでちゃんとメモしておくようにしてください。また、このトークンがほかの人に知られてしまうと、家電を好きなようにされてしまうので、絶対に外部に漏れないようにしてください。

これでアクセストークン取得完了です。

APIを使ってみる

トークンが取得できたので、n8nでワークフローを設定する前にAPIを使ってみます。APIはHTTPSリクエストで叩くことができるので、リクエストを飛ばせればなんでも大丈夫ですが、今回はcurlコマンドでやってみます。というわけで下記のコマンドを実行してみます。[アクセストークン]のところには1つ前の手順で取得したトークンで書き換えて実行してください。

$ curl -H "Authorization:[アクセストークン]" https://api.nature.global/1/appliances

これによりRemoに登録されている家電とそれぞれに実行できる操作の一覧を取得することができます。コマンド実行時に大切なのがヘッダーとして付与しているAuthorizationです。基本的にAPIを叩くときには必須の項目になります。そして、このコマンドで取得できる家電や操作にはIDが振られています。APIで家電を操作する際にはこのIDをパラメータとして指定するため操作したいIDはメモしておいてください。

今回は照明を操作してみようと思います。照明の操作には先ほどの取得した一覧からIDを持ってきます。

[
    {
        "他の家電情報"
    },
    {
        "id": "※※※※※※ ここのIDが大事 ※※※※※※",
        "device": {
            "...Remoの情報..."
        },
        "model": {
            "...照明自体の製品情報..."
        },
        "type": "LIGHT",
        "nickname": "照明",
        "image": "ico_light",
        "settings": null,
        "aircon": null,
        "signals": [],
        "light": {
            "buttons": [
                "...ボタン類..."
            ],
            "state": {
                "...明るさやON/OFFなど..."
            }
        }
    },
    {
        "他の家電情報"
    }
]

上記が一覧取得の結果を抜粋したものになりますが、この中の「※※※※※※ ここのIDが大事 ※※※※※※」部分に書かれているID
を使って照明を操作します。操作にはまたcurlで今度はPOSTします。

$ curl -H "Authorization:[アクセストークン]"  -X POST -d "button=on" https://api.nature.global/1/appliances/[照明のID]/light

これを実行すると照明が点灯するはずです。逆に消すときはbutton=onの部分をbutton=offにすれば大丈夫です。ちなみにこのボタンの設定値は先ほどの一覧取得した中のbuttonsnameの項目で確認することができます。ここまでできればあとはプログラムから叩いてもいいし煮ても焼いてもです。

n8nから実行する

さて本記事のメインでありながら、正直蛇足っぽいn8nからAPIを叩くフロー作成をやっていきます。とはいえここまでで動くことは確認できているのでn8nのHTTPリクエストノードに設定してあげるだけで動作します。

完成イメージ

ワークフローの完成イメージは上記の通りです。ただ動かすだけであればSTART含め2つのノードだけで作ることもできますが、一応将来性を考えトークンや照明IDの設定はSETノードに切り分けています。

SETには以下のように設定したい値を登録してあげるだけで大丈夫です。設定後一度ノードを実行してあげると次のノードでこの値を使用することができます。

設定値

次がメインのHTTPリクエストノードです。このノードでは基本的にcurlで叩いた値とSETノードで登録した値を登録してあげれば大丈夫です。SETノードで登録した値を使うには各入力欄の右側にある歯車マークから「Add Expression」を選択して「Nodes」からSetValuesString から選択することができます。あとはcurlで試した通りにHeaderとParametersに登録してあげるだけです。

前半
後半

これでフローは完成です。最後にフロー実行ボタンを押して正常に照明が操作できるかを確認してみてください。

エクスポートファイル (クリックで展開)
{
  "name": "Remo-Light",
  "nodes": [
    {
      "parameters": {},
      "name": "Start",
      "type": "n8n-nodes-base.start",
      "typeVersion": 1,
      "position": [
        200,
        0
      ]
    },
    {
      "parameters": {
        "keepOnlySet": true,
        "values": {
          "string": [
            {
              "name": "Authorization",
              "value": "[アクセストークン]"
            },
            {
              "name": "Light-ID",
              "value": "[照明ID]"
            },
            {
              "name": "button",
              "value": "on"
            }
          ]
        },
        "options": {}
      },
      "name": "Set request data1",
      "type": "n8n-nodes-base.set",
      "typeVersion": 1,
      "position": [
        350,
        0
      ]
    },
    {
      "parameters": {
        "requestMethod": "POST",
        "url": "=https://api.nature.global/1/appliances/{{$json[\"Light-ID\"]}}/light",
        "options": {},
        "bodyParametersUi": {
          "parameter": [
            {}
          ]
        },
        "headerParametersUi": {
          "parameter": [
            {
              "name": "Authorization",
              "value": "={{$json[\"Authorization\"]}}"
            }
          ]
        },
        "queryParametersUi": {
          "parameter": [
            {
              "name": "button",
              "value": "={{$json[\"button\"]}}"
            }
          ]
        }
      },
      "name": "Turn Light1",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 1,
      "position": [
        500,
        0
      ]
    }
  ],
  "connections": {
    "Start": {
      "main": [
        [
          {
            "node": "Set request data1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set request data1": {
      "main": [
        [
          {
            "node": "Turn Light1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {},
  "id": "7"
}

オマケとして、ここで作成したフローをエクスポートしたjsonファイルを載せておきます。使用する際はSETノード内のトークンと照明IDを修正してください。

終わりに

さて、そんなわけで今回はRemoとn8nの連携をやってみました。APIを使うことで自分のプログラムからでも操作できるようになるのでRemoの適用範囲をだいぶ広げることができると思います。

今回紹介した内容だけではちょっと物足りない気もしますが、私は今回紹介したフローの起点をWebHookにすることでリクエストされたら自動的にフローが実行されるようにし、使わなくなったKindleをリモコン兼データモニターとして活用しています。

意外といい感じ

別途アプリを入れる必要もなく、Kindle自体の脱獄も不要、Webブラウザが動けばリモコンとして使えるため、意外と気に入っています。そのうちこちらの詳細も記事にしたいと思います。

参考

Remo公式リファレンス

アクセストークン管理

]]>
https://koneta.click/p/791/feed 0
ワークフローツールn8nで超簡易的死活監視を構築してみました https://koneta.click/p/765 https://koneta.click/p/765#respond Sat, 29 May 2021 15:43:33 +0000 https://koneta.click/?p=765 前にオープンソースのワークフローツールであるn8nをDockerで簡単に構築するという記事を書きましたが、今回はその活用法の一つとして、簡易的なWebサイトの死活監視&Slack通知のツールとして使ってみたいと思います。

通常であれば、専用のツール環境を構築したり、小難しいスクリプトを書かなくてはいけませんが、今回の方法ではGUIでポチポチするだけでおおよそ動いてくれます。

下準備

まずは下準備です。大前提としてn8nは構築済みということで…。もしやってみたいけどまだ構築していないという場合は私の記事でも参考にしてもらえると幸いです。

さて、n8nは準備できたので、次にSlackに投稿するためのアクセストークンを取得します。今回は監視結果をSlackに送りたいのでSlackのトークンを取得しますが、メールで送信する場合や別のサービスを利用する場合はそれぞれの方法に合わせたトークンなりを用意しておいてください。

トークン取得は本題ではないのでサラッと行きます。

  1. SlackにログインしつつSlackAPIの管理画面にアクセスする。
  2. 右上の「Your Apps (作成されたアプリ)」→「Create an App (新しいアプリを作成する)」をクリックする。
  3. アプリの名称、ワークスペースを選択して「Create App」をクリックする。
  4. 作成したアプリの設定画面左側の「Basic Information (基本情報)」から「Add features and functionality (特徴と機能の追加)」内の「Permissions (権限)」を選択する。
  5. 「Add an OAuth Scope」のボタンをクリックして chat:writechat:write.customizeの権限を追加する。
  6. 画面左メニューの「App Home」で遷移した後、「Your App’s Presence in Slack (Slack へのログイン状態)」のEditボタンをクリックする。
  7. ボットの名前とユーザ名を入力してAddボタンをクリックする。
  8. 左側メニューの「OAuth & Permissions (OAuth & 権限)」をクリックする。
  9. 「Install App to Workspace」をクリックして、権限内容を確認したのち「許可する」をクリックして有効化する。
  10. アクセストークンが発行される。

上記の手順でアクセストークンを取得することができます。発効後再度確認したくなった場合は、左側メニューの「OAuth & Permissions (OAuth & 権限)」から確認することができます。子のトークンが外部に漏れると自由に投稿されてしまうので注意です。

これで下準備はできたのであとはn8nで設定していくだけです。

ワークフロー作成

準備ができたので、さっそくワークフローを作成していきます。今回の完成図は以下のような形です。

完成図

今回はn8nからHTTPリクエストを飛ばして想定通りのテキストが返ってくるかを確認して、想定通りなら何もしない。想定のデータが返ってこない場合はSlackに通知を飛ばす。というワークフローを作ってみます。

本記事では「右上のプラスボタンからノードを追加して矢印で繋げることでフローを作成する」のような基本的なn8nの操作は省略していきます。前回記事ではちょっと詳しめに書いているので、基本操作を把握していない方はご覧になってください。

1. 定期実行

まずはワークフローの開始地点を設定します。ワークフロー作成時にはデフォルトで「Start」というノードが登録されていますが、こちらは自分で動かしたときに「ここからはじまるよ」というノードです。今回は手動ではなく一定の時間で自動的に動いてほしいので、「Cron」のトリガーノードを追加します。

cron設定画面

今回は1時間に1度実行してみようと思いますので、ノードの設定から「Mode」は「Every Hour」で「Minute」は0で設定します。「Mode」は毎時以外にも毎分, 毎日, 毎週, カスタムもありますのでご自身の環境/要望に合わせて調整しててみてください。

2. HTTPリクエスト

次は監視のメイン部分となるHTTPリクエストです。リクエストするにはその名の通り「HTTP Request」ノードを選択します。設定画面が出てきたら、URL欄に監視したいURLを入力します。あとはレスポンスの形式に合わせて「Response Format」の項目を「JSON」や「String」などに設定します。

リクエスト設定画面

3. リクエスト結果で条件分岐

さてリクエストはできたので、取得した結果をもとに必要があれば通知を送ってみたいと思います。初見だと、ちょっと設定しにくいと思いますが、一回やってみるとかなりシンプルなので初回は頑張ってみてください。

条件分岐には「IF」ノードを使用します (もしTrue/Falseより多くの分岐がしたければ「Switch」ノードが使えます)。この条件分岐ノードで想定内のデータが取れていればOK。取れていなければNGでSlack通知に進むというようにフローを組めば大丈夫です。

実際に条件分岐ノードを設定してみようと思います。今回は2パターンを考えてみます。下記の設定をする前に一度フローを実行しておいてください。実行しておくことで下記の条件設定時に選択肢として各レスポンスデータを選択できるようになります。

Webページ向けレスポンスがHTML形式(String)

まずは単純にWebページに対してリクエストした場合です。この場合ではレスポンスで想定されるテキストが含まれているかで判定するのがいいと思います。今回はレスポンスで返ってくるHTML内に「Hello World」が含まれていれば正常という想定で設定してみます。

IF設定画面 HTML ver

これを設定するには、まず「IF」ノードの設定画面上の「Value1」で、前のノードから取得したHTMLデータを選択します。HTMLデータを選択するには、入力欄右にある歯車マークから「Add Expression」を選択し、そこから「Nodes」→「HTTP Request」→「Output Data」→「JSON」→「Data」を選択します。もし選択肢として出てこない場合は、フローを実行するかフロー自体がつながっているかを確認してみてください。

HTTPRequestの結果対象に指定している様子

「Value1」が選択できたら、「Operation」は「Contains」、「Value2」は調べたい文字列「Hello World」と入力します。

これでレスポンスデータに「Hello World」が含まれているか?の条件分岐ができるようになりました。

API的レスポンスがjson形式(JSON)

もう一つのパターンとしてはAPIへリクエストした場合でjson形式のレスポンスが返ってくる場合の設定方法です。こちらも基本的に想定されるデータと一致しているかで判定を行います。今回はシンプルにレスポンスとして下記のようなjsonが返ってくると想定します。

[
    {
        "Hello": "World"
    }
]

つまり、レスポンスデータ内の「Hello」項目が「World」で返ってくれば正常という判断になります。これをn8nで設定するには、まず「Value1」の項目に「Hello」。「Operation」の項目は「Equal」。そして「Value2」の項目は1つ前のHTTPリクエストの結果、つまり歯車マークから「Add Expression」を選んで「Nodes」→「HTTP Request」→「Output Data」→「JSON」→「Hello」を選択します。

IF設定画面 JSON ver

これで「Hello」項目が「World」で返ってくるか?の条件分岐ができるようになりました。

4. Slack通知

最後にSlack通知部分を作成します。通知を送るにはこれまた名前通りの「Slack」ノードを追加します。追加したノードは上記で作成した条件分岐ノードの「false」側につなげます。もし監視してOKの場合にも通知を贈りたい場合は別途「true」に設置することでOKパターンで通知を送ることもできます。今回は通知しないため「NoOp」ノードをつなげて明示的に何もしないようにしています。

「Slack」ノードを使用する前にAPI設定をしておく必要があります。これはノード設定画面の一番上の「Slack API」の鉛筆マークから設定することができます。

鉛筆マークを押した後の画面

APIの設定画面が表示されたらあとは適当な名前を付けて「Access Token」の項目に本記事初めに取得したSlackのアクセストークンを入力して完了です。ちなみにこの設定はほかのフローでも使いまわすことができ、使用したAPI設定は画面左のメニュー内「Credentials」から見ることができます。

Slack設定画面

APIの設定が終わったら「Slack」ノードの設定をしていきます。「Authentication」では「Access Token」を選択します。あとは投稿先のチャンネルを入力 (例: #notice-alert など)し、「Text」で通知したい内容を入力します。「Text」入力時欄の右側にある歯車マークから、他のノードの情報を持ってくることもできます。今回の例では「HTTP Request」のノードからリクエストを飛ばした「url」の情報をテキスト内に加えて通知しています。

終わりに

今回作成したワークフローをエクスポートしたモノを下に載せておきます。こちらをインポートして、監視対象のURLとSlackトークン指定を修正していただければ動作するはずです。

エクスポートファイル (クリックで展開)
{
  "name": "watch-example.com",
  "nodes": [
    {
      "parameters": {},
      "name": "Start",
      "type": "n8n-nodes-base.start",
      "typeVersion": 1,
      "position": [
        10,
        240
      ],
      "disabled": true
    },
    {
      "parameters": {
        "triggerTimes": {
          "item": [
            {
              "mode": "everyHour"
            }
          ]
        }
      },
      "name": "Cron",
      "type": "n8n-nodes-base.cron",
      "typeVersion": 1,
      "position": [
        150,
        240
      ]
    },
    {
      "parameters": {
        "url": "https://example.com",
        "jsonParameters": true,
        "options": {}
      },
      "name": "HTTP Request",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 1,
      "position": [
        300,
        240
      ]
    },
    {
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "World",
              "value2": "={{$node[\"HTTP Request\"].json[\"Hello\"]}}"
            }
          ]
        }
      },
      "name": "IF",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        450,
        240
      ]
    },
    {
      "parameters": {},
      "name": "NoOp",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        610,
        130
      ]
    },
    {
      "parameters": {
        "channel": "#notice-alert",
        "text": "=NOT WORK!!!\n{{$node[\"HTTP Request\"].parameter[\"url\"]}}\n<!channel>",
        "attachments": [],
        "otherOptions": {},
        "blocksUi": {
          "blocksValues": []
        }
      },
      "name": "Slack",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 1,
      "position": [
        610,
        330
      ],
      "credentials": {
        "slackApi": "HOGEHOGE"
      }
    }
  ],
  "connections": {
    "Cron": {
      "main": [
        [
          {
            "node": "HTTP Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Request": {
      "main": [
        [
          {
            "node": "IF",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF": {
      "main": [
        [
          {
            "node": "NoOp",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Slack",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": true,
  "settings": {},
  "id": "1"
}

今回は簡易的な監視システムをn8nで構築してみましたが、今後もn8nの使い道を見つけたら共有していきたいと思います。

]]>
https://koneta.click/p/765/feed 0
Chrome操作を超便利にする拡張機能「Vimium」の紹介 https://koneta.click/p/759 https://koneta.click/p/759#respond Sat, 29 May 2021 13:29:49 +0000 https://koneta.click/?p=759 今回はおすすめのChrome拡張機能「Vimium」を紹介してみたいと思います。

最近iPadじゃ物足りずChromeBookを購入したのですが、購入した理由の3割位はこの拡張機能を使いたかったです。…というのは嘘ですが、この拡張機能を入れるだけでChrome上の操作効率をとっても上げることができるので、キーボードが使える端末を使用する際は必ず入れています。

今回はそんなVimiumを簡単に紹介していこうと思います。

概要

Vimiumはブラウザ上での動作をVimライクなキーボード操作だけでやっちゃおうぜ! …という拡張機能です。この拡張機能を導入することで、Chromeのデフォルトで使えるショートカットだけでは実現できない操作までキーボードだけで実行することができるようになります。

機能

Vimiumにはいろいろな機能がありますが、今回は私がよく使っている機能を抜粋して紹介していこうと思います。

ページ内検索 [ / ]

まずはページ内検索です。基本的に「Ctrl+f」と同じ機能です。検索するにはまず「/」でウインドウを呼び出します。その後は検索したい文字列を入力して検索します。あとは「n」「N」で候補を移動します。

デフォルトのショートカットを使わない理由ですが、Vimiumでページ内検索をするとリンクが検索対象になったときにエンターキーで遷移することができます。一応デフォルトのページ内検索でも遷移することができますが、遷移までにかかるキータッチ数からVimiumの機能を使用しています。

リンク遷移 [ f ]

私がVimuimを使う一番の理由がこの機能です。こちらの機能は言葉で説明するよりも動作を実際に見てもらったほうがわかりやすいと思います。

動作イメージ

このようにキーボード操作だけでリンク遷移を行うことができます。操作方法も簡単で「f」でページ内の各要素にIDが振られます。あとは遷移したい要素のIDをタイプするだけで要素にフォーカス(リンクなら遷移)することができます。

これをマウス操作でやろうとすると、カーソルを動かしてリンク要素の上に持っていってクリックと手数がかかります。しかしViumium機能であればキーボードから手を話さずリンクから目を離さずに遷移できるので作業効率が微妙に改善してくれます。ただし、タッチタイプができないと微妙な機能ではあります。

スクロール関係 [ j, k, gg, G ]

お次はスクロール関係です。デフォルトでも矢印キーでスクロールすることッハできますが、手をホームポジションから動かす必要がないのでちょっとだけいい感じです。また、Vimと同じように「gg」「G」でページの最上部/最下部に移動する事もできます。正直数クロール自体は何でもいいのですがページ一番上に移動する機能がとても便利です。

履歴遷移 [ H, L ]

最後に紹介するのが「戻る」と「進む」です。デフォルトでも「Alt+矢印キー」で同じことができますが、矢印キーだとホームポジションから押しにくいためこちらを活用しています。

注意点と対策

無理に使わない

ここまでVimiumの一部機能を紹介しましたが、多機能とはいえ対応していない機能も結構あります。またVimniumに頼らなくてもデフォルトで使い慣れているChrome元々のショートカットもあります。そいういう場合は無理にVimniumに頼らないのも手だと思います。

私の場合はタブを閉じる(Ctrl+w)や閉じたタブを開く(Ctrl+Shift+w)、タブの移動(Ctrl+1~9) などはChromeデフォルトのものを使用しています。特にこれといった理由はないのですが他のソフトでも使えるアプリはそのままのショートカットを使用している感じです。使わないキーは動作しないようにしておくと誤動作を防げるのでおすすめです。

ブラウザゲームができない

Googleの検索欄だったり、ログインフォームだったり、明確に入力エリアにフォーカスしている場合は普通に文字を入力することができます。

しかし、タイピングゲームなどをブラウザでプレイする際など、キー入力先が入力フォームではない場合、キー入力がゲーム内に反映されない場合があります。その時はVimと同じように「入力モード」にする必要があります。入力モードに入るための対応キーはVim同様「i」です。

下記に対応キーの変更/削除方法を書きますが、入力モードに入るためのキーは残しておくべきと思います。

キー設定を削除/変更する

Vimiumは意外といろいろな機能がありすべてを使いこなすときっと素晴らしい操作感を得ることができると思います。しかし、使用頻度が低かったり、使わない機能は無効にしておきたい、対応するキーを変えておきたいという事があるかもしれません。その場合も拡張機能の設定から変更可能です。

# 設定を全部解除
unmapAll
# 必要なキーのみ設定
map j  scrollDown
map k  scrollUp
map gg scrollToTop
map G  scrollToBottom
map h  scrollLeft
map l  scrollRight
map r  reload
map yy copyCurrentUrl
map p  openCopiedUrlInCurrentTab
map P  openCopiedUrlInNewTab
map i  enterInsertMode
map f  LinkHints.activateMode
map F  LinkHints.activateModeToOpenInNewForegroundTab
map /  enterFindMode
map n  performFind
map N  performBackwardsFind
map H  goBack
map L  goForward
map J  previousTab
map K  nextTab
map ^  visitPreviousTab
map ?  showHelp

実際、私が普段使っていない機能が何かの拍子に誤動作するということが度々ありました。そこで不要なキー設定は削除しておくのをおすすめしておきます。

終わりに

さて、今回は私が使っているChrome拡張機能の紹介でした。使いこなせると日々の作業効率をちょっとだけ上げることができるのでぜひとも使ってみてください。

]]>
https://koneta.click/p/759/feed 0
無料でビットコインがもらえるfreebitco.inを今一度試してみます!各機能説明編![2021/10/20更新] https://koneta.click/p/694 https://koneta.click/p/694#respond Sun, 21 Mar 2021 11:03:57 +0000 https://koneta.click/?p=694 とある自動車メーカーが購入したり、時価総額が6万ドルを超えたり、最近ビットコインが何かと話題になっていますね。私はかれこれ4年くらいビットコインの周辺をウロウロしているのですが、そんなに上がらなくていいのになぁとは思います…。

さて、4年前というとまだまだ黎明期真っ只中です。そんな時期に流行っていたのがいわゆるFaucetと呼ばれる、無料でビットコインをもらうことができるサービスです。私もMoonやAutoやBonusやLuckyやら色々なFaucetに手を出していました。今挙げたサービスはすべて消滅してしまっていますが、当時は簡単に「仮想通貨」が手に入るとめちゃくちゃたくさんのFaucetが作られていました。

とはいえ量産されるFaucet達は詐欺前提だったり、もらえるコイン数が少なかったりと、よっぽど暇じゃないと利はほとんど無いものがほとんどでした。そんな中、当時でもちゃんと稼げたのが今回再挑戦するfreebitco.inさんです。最近になり色々と機能追加があるようなので再開してみようと思います。

FreeBitco.in ?

というわけでfreebitco.inさんの簡単な紹介をしていきます。

こちらは「1時間毎にフリーロールで出目に対応するコイン数をもらえる」といういわゆる「よくある」Faucetなのですが、それ以外の機能が色々あり、それらを活用することでより多くのコインが獲得できるサービスになっています。機能の紹介は後ほど書いていこうと思います。

Faucet群雄割拠時代には頑張ってコインをためても出金ができないだったり、そもそもうまく動作してないなど問題のあるサイトが量産されていましたが、Freebitco.inさんは2013年から運営されているようで信頼できるサイトといっても問題ないと思います。実際、私も何回か入金/出金したことがありますが、今のところ問題には遭遇していません。

機能

フリーロール (FREE BTC)

1時間毎にボタンを押すことで出目に対応するコインを獲得することができます。こちらが一番Faucetの基本となる機能です。1時間に一度出目により6段階の賞金を得ることができます。

ここまではいわゆるよくあるFaucetなのですが、freebitco.inさんはこれに加えてサイト内のコンテンツを利用することで賞金をUPさせる機能が備わっています。またフリーロールの賞金とは別に、特典がもらえるREWARDSポイントや賞金がもらえるLOTTERYチケット(それぞれの紹介は後ほど)を獲得することができます。

フリーロール!REWARDSボーナス付き!

もう一つ他のFaucetと異なる点があります。他のFaucetではフリーロールを回すために「私はロボットじゃありません」の確認をする必要があるのですが、freebitco.inさんでは特定の条件を満たすことでこの確認を省略することができます。条件はTOPページの「REQUIREMENTS TO UNLOCK BONUS」の「PLAY FREE BTC WITHOUT CAPTHCA」から確認することができます。またREWARDSポイントを消費することで確認を省略することもできます。

フリーロールで獲得できるコイン数はBTCの価格に同期し変動しています。そのため古い紹介記事を読むと「以前はコイン数がもらえていたのにケチになったなぁ」と思うかもしれませんが金額でいえばもらえる金額は変わっていません。現在BTCの最小単位である1satoshiまで来てしまったので、今後どうなるかは分かりませんが…。

ハイローゲーム (MULTIPLY BTC)

フリーロールと並び稼ぎ頭となるのがMULTIPLY BTCです。この機能の中身は出目に対して高いか低いかを選択するハイローゲームになっています。つまるところ賭けを行ってコインを増やそうというのが基本機能になります。

ハイロー画面!

黎明期のFacetでもハイローゲームを備えているところはありましたが、freebitco.inさんはハイローゲームでの稼ぎ以外にREWARDS/LOTTERYのボーナスが貰えたり、フリーロールの賞金が増加したり「賭け金ごとのボーナス」もあります。ただ、この「賭け金ごとのボーナス」というのがややこしいのです。

詳細は省きますが、ハイローゲームを続けていると「ボーナスを取得する」のボンタンが表示され、押すとボーナスが獲得できます。しかし、このボーナスはまだ自分のウォレットに入っていません。完全に自分のものにするには獲得したボーナスの25倍の数値分ハイローゲームを回さなくてはいけません。

BONUS獲得画面(25倍数値回し中)

というわけでリスクを犯しても稼ぎたい方にはおすすめしておきますが、他のコンテンツと比べるとリスクが伴いますので自己責任でお願いいたします。かくゆう私は検証のため回しまくっていました (当時のレートでですが0.3BTC以上失っているのは秘密です)。

ルーレット (WHEEL OF FORTUNE)

こちらは最近始まったルーレットです。こちらはfreebitco.inさんにしては珍しく、通常ではサイト内にリンクが無く登録メールアドレスに毎日送られてくるメール内のリンク(基本は最下部)からページにアクセスすることで1日1回プレイすることができます。ちなみに1リンクにつき48時間有効とのことです。

こちらがルーレットへのリンク!

賞品はアクセスしたページのルーレットに掲載されています。基本はBTCとREWARDSポイントそしてLOTTERYチケットになります。他にはAmazonギフトやゴールデンチケット, iPhoneなどもあります。が基本の3種以外はまだ当選したことがないので、本当に出るかは未確認です。

ルーレット画面

REWARDS

上記の説明で何回か出てきましたが、このサイトにはサイト内操作により貯まり、いろいろなボーナスを獲得できるREWARDSポイントという機能があります。こちらをうまく使うことでフリーロールでの稼ぎを文字通り10倍にすることができます。

REWARDSポイントは直接BTCと交換することができます。現在は1ポイント=1satoshiで交換できますがこちらはBTCの価格により変動してきたので、今後BTCの価格が上昇していくと 0.5satoshi とかになっていくと予想しています。

またREWARDSポイントが溜まりやすくなる「BONUS DAY」が不定期で開催されていて(以前は週末固定でしたが…)こちらの期間内であればフリーロールやハイローゲームで獲得できるREWARDSポイントが数倍になります。何倍になるかは期間により異なるので公式Twitterやメールをご確認ください。

利息 (EARN)

freebitco.inさんにはサイト内のウォレットに30,000satoshi以上入っていると利息を得ることができます。レートは年4.08%(執筆時点)です。これだけでもFaucetとして異質ではありますが、コインの引き出し制限などもなく、この利息(1日換算だと0.0109589%)を毎日もらうことができます。

私がよく使っているGMOコインVC TRADEにもステーキングのサービスはありますが、こちらは期間を設定してその間は出金できなかったり、そもそも特定の曜日からしか初められなかったりとなにかと制約が多いです。それを考えてもらえるとfreebitco.inさんが長生きする理由が見えてくるかもです。

PREMIUM

最近プレミアムアカウントという制度が始まりました。こちらはfreebitco.inさん内のウォレットでFunFairというトークンを一定数保有しているとプレミアムアカウントになれるそうです。

プレミアムアカウントの特典は保有しているトークン数にもよりますが、ハイローゲームのキャッシュバックが増える、配当が得られる、ルーレットの回数が増えるという点があります。

先程ルーレットの紹介をした際に「通常ではサイト内にリンクが無い」と書きましたが、こちらのボーナスでもらえるルーレット回し権はTOPページ内にリンク(下記画像の「**PREMIUM MEMBER ALERT**」)が貼られますのでご注意ください。ルーレットの特典は毎日のルーレットと同じですが、「ロボットじゃありません」の確認が不要になっていたり、まとめてルーレットを回せたりと微妙に手間が省かれています。

この辺りにリンクが出てきます

私は検証のためにトークンを購入し1ヶ月保有してみましたがFunFairトークンの将来により損得が分かれそうです。FunFair君に将来性を感じるのであれば特典が得られるfreebitco.inさん内で保有するのがいいと思います。

BETING

こちらも比較的新しめのコンテンツです。その名の通り賭けを行う機能なのですが、上記のハイローとは異なり、こちらはスポーツの勝敗やコインの価格など現実世界が対象になっています。

ただ私はスポーツ、特に海外にものは疎く、この機能は活用していませんので、深く触れることができません。開催されている内容を見て自信のあるものが対象になっていたら試してみるのがいいと思います。

LOTTERY / WIN A LAMBO

最後になりましたが、freebitco.inには宝くじの機能もあります。こちらはその名の通りサイト内で手に入れたくじに当選すればコインが貰えるという機能になります。くじをもらうには、上記で紹介したフリーロールやハイローゲーム, ルーレットをプレイするかくじ自体をBTCで購入するという方法があります。

しかし、わたしはこの機能も活用していませんので正直検証不足です。本当に当たるのか攻略の方法があるのか…。

ブラウザマイニング (終了)

終了済みなので説明省略

一時はブラウザ上でマイニングをする機能がありました。基本機能は画面上でマイニングに使用するスペックを選択し、あとは放置しておくだけという感じでした。低スペックではありますが私の環境では10時間で28satoshi程度でしたので、そこまで魅力もありませんでした…。

基本戦略

さて基本機能の紹介が終わりましたので、どうやればより稼げるのかを書いていこうと思います。

フリーロールメイン

freebitco.in の仕様変更に伴いREWARDS特典で得られる項目が変わってしまいました。これにより、以前より大きく稼ぐのが難しくなってしまいました。現在の基本戦略はとにかくメールで送られてくるルーレットを回すことになっています。1回ルーレットリンクは48時間制限なので2日に1度作業すれば大丈夫です。

過去の戦略 (旧仕様のため省略

上の方でも書いたとおり、効率よく稼ぐためにはREWARDSポイントをうまく活用する必要があります。そこで基本的にはREWARDSの特典で獲得できるポイントを増やす「REWARD POINTS BONUS」の項目を常に設定しておく方針になります。

まずはフリーロールやルーレットでREWARDS特典を獲得できるポイント(最低12 RP)を獲得します。十分にポイントが獲得できたらその時点で一番いい「REWARD POINTS BONUS」の特典を獲得します。獲得すると24時間獲得ポイント数が増えますので、またフリーロールを回します。これを繰り返し「100 REWARD POINTS / ROLL」を常に獲得できるだけのポイント(1,200 RP)を稼いぎます。

4400RPが溜まったら、最後にフリーロールの獲得コイン数を増やすREWARDS特典である「FREE BTC BONUS」の「1000% BONUS」を獲得します。これによりフリーロールでの獲得コイン数を増やすことができました。あとはこれを繰り替えることで効率よくフリーロールでコインを獲得することができます。

「REWARD POINTS BONUS」の特典に必要なポイントは12RPの倍数になっています。これは24時間のうちにフリーロールを回した回数が12回未満だと赤字になるように設定されています。そこで毎日最低でも12回以上フリーロールを回す必要がありますのでご注意ください。

入金も辞さない場合

上記で説明した通り、freebitco.inにはBTCを入金することでフリーロールの手間を省くことができます。コイン数はBTCの金額(加えて円ドルのレート)にもよりますがおおよそ1万円に設定されています。毎回ボットの確認をしなくてはいけないのはかなり手間なので、この特典は獲得しておくことをおすすめしておきます。またコインを入れておくことで利息も獲得できるようになるのでよりお得になっています。

余談ですが…私はボット確認省略の特典を獲得し、フリーロールとREWARDS特典の獲得を自動で回してくれるようにシステムを作って動かしています。自動化できるのもfreebitco.inさんならではです。システムはDockerコンテナ内のSeleniumで動かしていてなかなかいい感じにできました…という自己満足で閑話休題です。

また、サイト内でFunFairトークンを一定数所有することで利息が余計に付与されたり追加でルーレットが回せるようになるので、そこまで入金を辞さない場合はFunトークンを購入してみるのも手です。どうやら freebitco.inさんはFunトークンを流行らせていきたいようなので、今後もFunトークンを持っていることで特典が得らる可能性は高いと思います。

リスクもいとわない場合

さて、リスクを取ってでもこのサイトで稼ぎたいという方はハイローゲームやBETINGに挑戦してみるしかないです。これについては長期的にみると損をするようにできているのでオススメはできませんが短期的には大きく稼げる可能性はあります。

私は、獲得できるボーナスやREWARDSポイントそして増加するフリーロールの額で、賭け以外の部分で利益を出せないかを検証しましたがいい結果は出ませんでした。さすがにボーナスなど含めちゃんと考えて設定されていますね。というわけでお勧めはしないですが、刺激が欲しい方はちょっとやってみてもいいかもしれないです。

ちなみに…

上記では「FREE BTC BONUS」を獲得すると書きましたが、BTC価格が上昇して1回のフリーロールでの獲得コイン数が少なくなっている現在では、「FREE BTC BONUS」を獲得するよりも「100 REWARD POINTS / ROLL」だけを獲得していたほうがお得になります。ちなみに損得のボーダーは1フリーロールあたり14satoshiになります。

もちろん今後のBTCの価格やBTC-REWARDSポイントの交換レートによって変わってきますので、一概には言えませんが厳密に最大効率でコインを獲得したい方は気にしてみてください。

最後に

さて、今回はfreebitco.inさんの紹介記事を書いてきました。正直、参入するには怪しさ満点のサイトと思います。ページデザインも前時代的ですし…。私自身はこれまで色々検証してきたので結構信頼もしているのですが、何事もよく分からないものには手を出さないのがいいですよ。

また最近はFunトークンを推したいようで、サイト内の仕様もだいぶ変わりました。今後も結構な頻度で仕様変更が発生すると思います。本サイトでは仕様変更があるたびに記事を更新していこうと思いますが、振り回されるのが嫌な方も手を出さないのがいいと思います。

もし、これを読んでも初めてみようという方は実は紹介リンクになっているこのページ内から登録していただけると私が喜びます。

]]>
https://koneta.click/p/694/feed 0
OSSのワークフロー作成ツール n8n をVPS(ConoHa)に構築します https://koneta.click/p/680 https://koneta.click/p/680#respond Sun, 14 Mar 2021 15:53:00 +0000 https://koneta.click/?p=680 この世にはWebサービスやイベントを繋げ自動的にワークフローを実行してくれる便利なツールがあります。この手の有名なツールとしてはIFTTTやZapierが挙がってきます。しかし、これらは一部機能が有料だったり登録できる件数が少なかったりします。そこで出てくるのが今回紹介するn8nです。

n8nは、オープンソースで使用できる自動化ツールで、IFTTTなどと同じように、何かしらの動作やイベントをトリガーとしてアクションが始まり、定義したフロー通りに自動で処理を行ってくれます。

そこで今回はn8nの環境をVPS(ConoHa)上のDockerで構築し簡単な操作方法を実際に試してみたいと思います。

環境構築

基本的な環境構築は公式サイトの「How to get started?」の項目を見ると全て書かれています。Node環境があればコマンドちょちょいで、Docker環境があればコマンド1つでツールを動かし始めることができます。しかしあえて今回はここに掲載されていないdocker-composeで環境を作っていこうと思います。

タイトルではVPSに構築しますと書いていますが、正直Docker環境があれば、結構どこでも大丈夫です。というわけでDocker環境を用意しておいていただきたいのですが、ConoHaでの環境構築は以前記事にまとめていますので、そちらを参考にしていただけるとと思います。

では早速作っていきたいと思います。とはいえDocker(docker-compose)上で構築するため、やることはほぼほぼコマンドを実行するだけです。というわけでdocker-composeを実行するため設定ファイルを用意していきます。内容は以下の通りです。

version: "3"

services:
  n8n:
    image: n8nio/n8n
    restart: always
    ports:
      - "5678:5678"
    environment:
      - N8N_BASIC_AUTH_ACTIVE=true
      - N8N_BASIC_AUTH_USER
      - N8N_BASIC_AUTH_PASSWORD
      - N8N_HOST=${SUBDOMAIN}.${DOMAIN_NAME}
      - N8N_PORT=5678
      - N8N_PROTOCOL=https
      - NODE_ENV=production
      - WEBHOOK_TUNNEL_URL=https://${SUBDOMAIN}.${DOMAIN_NAME}/
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ${DATA_FOLDER}/.n8n:/home/node/.n8n
    network_mode: bridge

docker-conpose.ymlだけで完結させることもできますが、取り回しがよくなるよう今回は設定ファイルを別に作成してみます。設定ファイルは.envという名前でymlファイルと同じところに置いておきます。内容は下記の通りです (ほぼ公式サイトのコピペです)。

# どこのフォルダにデータを保存する
DATA_FOLDER=/root/n8n/

# どんなトップドメインにn8nを設置する
DOMAIN_NAME=example.com

# どんなサブドメインでn8nを動かす
SUBDOMAIN=n8n

# BASIC認証のID - ※※※※※ 変更必須です ※※※※※
N8N_BASIC_AUTH_USER=user

# BASIC認証のPASSWORD - ※※※※※ 変更必須です ※※※※※
N8N_BASIC_AUTH_PASSWORD=password

# 定期実行で使用するタイムゾーン
GENERIC_TIMEZONE=Europe/Berlin

この設定で、n8n.example.comで受け付けるための設定ができます。ご自身の環境に合わせて修正してください。ちなみに、タイムゾーンはツール内の設定で変更できるのでそのままでも大丈夫です。

また外部からアクセスできる環境にn8nを設置するのであれば、上記の設定だけではSSLの設定ができたいないのでオープンな環境に設置するには不足しています。今回はn8nコンテナの前段としてリバースプロキシを設置するためSSLはそちらで補います。加えて公式にも書かれていますが、オープン環境に設置するのであればセキュリティ対策として最低限BASIC認証くらいは入れてください。

ここまでくればあとはDockerを起動するだけでn8n自体の設置作業は完了です。

$ docker-compose up --build -d

最後に.envで設定したドメインに合わせてDNSレコードやhostsを設定すれば作業完了です。

追加作業リバースプロキシ

さて、上記設定だけではSSLが未対応なので今回はリバースプロキシを設置して対応していきたいと思います。今回使用するリバースプロキシはDockerで動かすNginxで構築されていて基本的にコンテナを実行するだけで動作してくれるイメージを使います。

本記事でも最低限の項目は書いていこうと思いますが、以前に記事にまとめていますので、詳しくはこちらをご覧ください。

というわけで、リバースプロキシのDockerを用意します。

version: '3'

services:
  nginx-proxy:
    build: jwilder/nginx-proxy
    restart: on-failure
    labels:
      - com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy=jwilder/nginx-proxy
    ports:
      - 80:80
      - 443:443
    volumes:
      - proxy:/etc/nginx/vhost.d
      - proxy:/usr/share/nginx/html
      - /var/run/docker.sock:/tmp/docker.sock:ro
      - ./certs:/etc/nginx/certs:ro
    network_mode: bridge

  letsencrypt:
    image: jrcs/letsencrypt-nginx-proxy-companion
    restart: on-failure
    depends_on:
      - nginx-proxy
    volumes:
      - proxy:/etc/nginx/vhost.d
      - proxy:/usr/share/nginx/html
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./certs:/etc/nginx/certs:rw
    network_mode: bridge

volumes:
  proxy:

これだけでリバースプロキシ自体の用意とSSLの準備が完了しました。毎度のお手軽さには驚かされます。このdocker-composeは先ほどのdocker-composeとは別に作成したほうがいいです。

次に、Nginx側で「良しなに」設定してもらうための記述を先ほどのn8n用docker-composeに追記していきます。追記項目はドメイン設定とSSL証明書取得用のメールアドレス、そしてコンテナを繋げるネットワーク設定です。

version: "3"

services:
  n8n:
    image: n8nio/n8n
    restart: always
    ports:
      - "5678:5678"
    environment:
      ...(省略)...
      - VIRTUAL_HOST=n8n.knta.cc
      - LETSENCRYPT_HOST=n8n.knta.cc
      - LETSENCRYPT_EMAIL=tunezune.history@gmail.com
    volumes:
      - ...(省略)...
    network_mode: bridge

上記のようにn8nで使用するドメインと証明書取得用のメールアドレス、ブリッジの設定を追記します。最後にNGINXのコンテナを起動、n8nのコンテナを再度起動。これだけで作業完了です。

$ cd /path/to/nginx_docker-compose/
$ docker-compose up -d
$ cd /path/to/n8n_docker-compose/
$ docker-compose up -d

使ってみる

では環境構築ができたので、最後に軽くn8nを使ってみようと思います。題材は「slack投稿を定時実行」でやっていきます。

初期画面

n8nを起動して設定したドメインにアクセスすると上記の画像のようなページが表示されます。こちらがn8nのメイン画面になっています。画像真ん中らへんに表示されているStartのノードが初期フローの開始地点となっています。

基本の作業はこのノードに次のノードを繋げていくことでワークフローを作っていきます。ノードを追加するには右上にある赤色の+ボタンを押すか各ノードのドット部分からドラッグです。

定期実行する開始ノードを追加

上記の操作を行うとノードを選択するウインドウが出てきます。ここから追加したいノードを探します。ノードには2種類あり「Regular」が処理を行うノード、「Trigger」がワークフローの起点になるノードになります。今回は定期実行をするための起点ノードCronを使いたいため、Triger欄からCronを選択しています。

起点を追加した様子

Cronを追加した様子は上記の通りです。ノードを選択した際にノードに対応した設定画面が表示されたと思います。追加後でもノードをダブルクリックすると再度表示されます。今回は定期実行のノードのためいつ実行するかの設定ができます。

次にSlack投稿をしてもらうためSlackのノードを追加します。先ほどと同じようにノード追加画面を開き、今度は「Regular」の項目からSlackのノードを追加します。今回はSlackのAPIキーを取得するところは省略しますが、APIキーの設定、チャンネルと送信するテキストの設定で追加完了です。

また、こちらも本記事では使いませんが、送信するテキストには前のノードのデータ (各サービスのAPIノードであれば取得したデータ、RSS取得ノードならRSSの中身など) を指定することもできます。

フロー完成~!

以上の作業で上記のようなフローが完成です!最後にフローを保存して、右上の「Active」を有効にすることで定期的にSlackに送信してくれるフローが動き始めます。なんともまぁ簡単でした。

終わりに

さて、そんな感じで書いてきました。私の中では自動化というのはいろいろロマンのある作業だったりします。しかし、自動化をするにはいろいろ作業する必要があり、正直めんどくさいという面もあります…..。そんなところでn8nに出会ったわけですが思っていたよりも数倍簡単に環境構築もフロー作成もでき、今回は触れませんでしたがサーバ上のコマンドも実行できるので使い道はどこまでも広げることができます。ありがとういいツールです。何か便利な使い道を思いついたらまた記事にしようと思います。

]]>
https://koneta.click/p/680/feed 0