ひとメモ

主にプログラミングのメモ

呼吸が浅くなりがちなのでMi Band3に深呼吸を促すリマインダが来るようにした

先日XiaomiのMi Band3を買いました。

www.hitowaft.work
スマホとBluetoothで接続して、LINEやTwitter、その他様々なアプリからの通知を確認することができます。

1時間座りっぱなしでいると「動きましょう」と通知してくれる機能は元々あるのですが、せっかくなのでもっと自分向けの通知を作って受け取ってみたいと思います。

使ったもの

手順

  1. LINE Notifyに登録してアクセストークンを取得する
  2. GASに後ほど記載のコードを貼り付けて、文言を好きに編集する
  3. GASでスクリプトのプロパティにACCESS_TOKENを登録する(省略可・後述)
  4. GASのトリガーをセットして、お好みの間隔で通知を受け取る

LINE Notifyのアクセストークン取得時の注意事項

LINE Notifyのアクセストークンを取得する際は、トークン名を一文字にしておきます。名前が長くなると、その分文言が入るスペースがなくなってしまうので…。

また、LINEのグループを作成してそこにLINE Notifyを招待し通知を受け取るやり方もあるのですが、そうすると[グループ名][LINE Notify]文言という形で通知が来て、ほぼ画面が埋まってしまうため1:1でLINE Notifyから通知を受け取るを選択します。

コード

function myFunction() {
  var messages = ["深呼吸しよう", "背筋を伸ばそう", "hoge", "fuga", "foo", "bar"];
  var random_num = Math.floor( Math.random() * messages.length );
  sendHttpPost(messages[random_num])
}


function sendHttpPost(message){
  token = PropertiesService.getScriptProperties().getProperty("ACCESS_TOKEN")

  var options =
   {
     "method"  : "post",
     "payload" : "message=" + message,
     "headers" : {"Authorization" : "Bearer "+ token}

   };

   UrlFetchApp.fetch("https://notify-api.line.me/api/notify",options);
}

コード解説

特に難しいことはしてませんが、JSやGASに不慣れな人に向けて少し解説します。
messages変数に通知したい文言を配列として入れています。
この配列の中から、実行ごとにランダムで一つ選ばれた文言がトリガーで決めた間隔で通知されます。

各文言を、全角なら13文字・半角なら34文字までに抑えると画面からはみ出しません。

token = PropertiesService.getScriptProperties().getProperty("ACCESS_TOKEN")の部分では、GASでスクリプトのプロパティに登録した値を取りにいってます。
どこにも公開しないならtoken = "LINE Notifyで取得したトークン"みたいにベタ書きでもいいかな。

動かないなと思ったら上部にある関数を選択からmyFunctionを選んで実行してください。


こんな感じで通知が来ます。
[良]となってるのが、トークン取得時につけた名前です。何かいい意味の漢字が良いなと思ったけど、特に思いつかなったのでそのまま良にしました。

参考リンク

Google Apps ScriptからLINE NotifyでLINEにメッセージを送る - Qiita

【超便利&お手軽】Python + LINE NotifyでLINEメッセージ送信 – Blue-black.ink

Xiaomi Mi Band3を買ったのでレビュー

安価なスマートウォッチ Mi Band3を買いました

Amazonで3700円で買いましたが、今見たら3100円になっていた…。

写真はちょっと着ける位置が間違っていて、本当は手首の出っ張った骨より腕側に着けます。

良かったところ

通知を逃す心配がない

スマホとBluetoothで接続していて、通知が来ると振動します。
スマホをポケットに入れてても歩いてると振動がわからなかったりしますが、腕だととてもわかりやすいです。
何かの連絡を待つような時でも、スマホ本体はカバンにしまって安心していられます。

電池の持ち

睡眠時に心拍数を測る設定(睡眠ログの精度が良くなるらしい)にすると電池のもちが悪くなるということでしたが、約2週間もちました。
あんまり充電のこと気にしなくて良いのは素敵。
なおスマホの方がBluetoothをつけておく分電池の減りが少し速くなりました…。

Slackの通知が見れて嬉しい

仕事中非常に単純な作業ばかりしているので、脳のリソースが余っています。
そこにSlack(チャットツール)の通知が来ると、みんななんか話してるなーとちょっと楽しい気持ちになります。(画面が小さいので話してる内容はほぼわからない)

常に腕に時計が着いてると便利

腕時計は良い。

期待したほどでもなかったところ

まとまったデータの取得が面倒

そもそも睡眠のトラッキングをしたくて買ったのですが、そのデータをまとめて取得するのが面倒でした。
ログ取得のリクエストを送信→メールで用意できた旨の通知が来る→ダウンロードページにログインしてデータを取得という形。
毎日のデータはアプリ内で見られるので、自分でデータを弄りたい人以外には十分だと思います。

睡眠ログはまあまあ

私は早朝覚醒が多いのですが、目が覚めてもそんなに動いたりするわけではないのでその時間は中途覚醒として拾えていないことが多いです。
それ以外はスマホの睡眠トラッキングアプリよりは正確かなぁという気もしますが、正直よくわかりません。

座りすぎ通知の精度が低い?

1時間座りっぱなしでいると振動でお知らせしてくれる機能がありますが、役立つこともあればしばらく座っていた後立ち上がって動いてるのに「動きましょう」と知らせてくれることもあります。
「もう動いとるでw」と思うだけですが。

まとめ

この値段だし、時計も欲しかったので「買って良かったなー」という感じ。

hwhw.hatenablog.com

複数の環境変数をまとめて取得したい(Python)

やりたかったこと

qiita.com

上記の記事を参考にスクレイピングの練習をしていたところ、次のような部分がありました。

URL      = "https://qiita.com/login" 
ID       = ""                        
ID_sel   = "#identity"               
PASS     = ""                        
PASS_sel = "#password"               
Selector = ".st-Header_loginUser img"

私は完成したものを公開したい等諸々の理由で、上記全ての変数を環境変数としてos.getenv()で取得する形にしようと思いました。
同じ形が続くのでforループにします。

複数の変数に環境変数の値を代入

初めは次のような形でやろうとしましたが、これだと変数が未定義だというエラーが出ます。

Pythonはvar hoge = ~のように変数を宣言しないので、先にリストにしておくということができませんでした。

ダメコード

conf = [URL, ID, ID_sel, PASS, PASS_sel, Selector]

for i in conf:
    i = os.getenv(str(i).upper())

NameError: name 'URL' is not defined こんなエラーが出ます。
なので、以下のように書き換えたら動くようになりました。

動くコード

conf = ["URL", "ID", "ID_sel", "PASS", "PASS_sel", "Selector"]

for i in conf:
    locals()[i] = os.getenv("{}".format(i.upper()))

まず変数名をstrとしてリストを作っておきます。

次にlocals()を使います。
これは辞書形式でローカル変数を返してくれるそうで(よくわからない)、[i]がキーで変数名、os.getenv("{}".format(i.upper()))が値で各環境変数になります。
upper()としているのは環境変数を大文字で保存しているため。)

余談

書いといてなんですが、普通はこういう時わざわざループさせないのかもしれないなぁと思いました。
でも思った通りに動いて嬉しかったのでメモ。

DockerアプリのHerokuへのデプロイをGitLab CIを使って自動化する

GitHubにpushするだけでHerokuにデプロイできるようになった

Dockerでwebアプリを作成し、それをHerokuにデプロイする際

$heroku container:push web
$heroku container:release web

という感じで、手元でビルドしたイメージをHerokuのContainer Registryにpushしていました。(公式ドキュメント

それだと時間がかかるのと、CI(Continuous Integration:継続的インテグレーション)というのを使ってみたかったので、GitLab CIを試しました。

その結果Git Hubにgit pushするだけで、修正した部分が自動的に反映されるようになりました。便利〜。

なお時間は同じかそれ以上にかかります。
でもPCつけたまま待たなくて大丈夫なのが良い。

関連記事 hwhw.hatenablog.com

手順

※Dockerfile等の説明は割愛します。

  • GitLabのアカウントを作成→GitHubと連携

  • プロジェクトのルート直下に.gitlab-ci.ymlというファイルを作成

  • HerokuのAccount Settings(アプリのSettingsじゃなくて画面右上の)でAPI Keyを取得

  • GitLabのSettings > CI/CD > Variablesで環境変数を設定

    • HEROKU_TOKEN = 上記のHeroku API Key
    • HEROKU_APP = Herokuのアプリ名
  • GitHubにpush → 自動でデプロイ!嬉しい!

.gitlab-ci.yml

そのままコピペで多分動きます。詳しく解説できるほど理解はしてない…。

push-heroku:
  only:
    - master
  image: docker:stable
  services:
    - docker:dind
  script:
    - echo $HEROKU_TOKEN | docker login --username=_ --password-stdin registry.heroku.com
    - docker build -t registry.heroku.com/$HEROKU_APP/web .
    - docker push registry.heroku.com/$HEROKU_APP/web

    - docker run --rm -e HEROKU_API_KEY=$HEROKU_TOKEN wingrunr21/alpine-heroku-cli container:release web --app $HEROKU_APP

参考リンク

ほぼQiita記事のymlファイルまんまですが、それだけだとheroku container:release webの部分が自動でできなかったのでもう一つのリンクを参考に全自動にしました。

HerokuのContainer RegistryベースのデプロイをGitLab-CI上で行う - Qiita

Release container to heroku with Gitlab.ci - Qrated - Medium

Dockerイメージのベースをubuntuからalpineに変えてビルドを高速化する

alpineにすると軽くなると聞いた

↓のwebアプリを作成する際、環境構築にDockerを使っていて、HerokuへのデプロイもDockerごと(?)上げています。
些細な変更をしただけでもビルドにいちいちとても時間がかかっていたので、なるべく速くビルドができないかと軽量化を図りました。

hwhw.hatenablog.com

結果

ubuntuベース 635MB

alpineベース  244MB

391MBの減量に成功

Herokuへのデプロイも少しだけ速くなったような気がします。(計ってないのでわからない)
なおDockerfileの書き方はよくわかっていないので、効率の悪いやり方になってるかもしれません。ご了承ください。

変更点

  • apt installが使えないので、apk addというコマンドを使用します。
  • パッケージの名前を変えないとエラーが起きたりします。python3-pippy-pip など。
  • psycopg2(postgresqlを使うためのパッケージ?)をrequirements.txtに入れていましたが、エラーが起きるので調べた結果apk addのところにpy-psycopg2を追加したら動きました。

Dockerfile変更前

RUN mkdir /app

COPY src/requirements.txt /app
COPY ./src /app/src
COPY . /app

WORKDIR /app/src



RUN apt update && apt install python3 python3-pip vim sqlite3 sudo -y
RUN sudo apt install python-dev libpq-dev -y
RUN pip3 install -r requirements.txt




CMD ["python3", "app.py"]

Dockerfile変更後

RUN mkdir /app

COPY src/requirements.txt /app
COPY ./src /app/src
COPY . /app

WORKDIR /app/src



RUN apk add --update python3 py-pip vim sqlite sudo
RUN sudo apk add python3-dev postgresql-dev py-psycopg2
RUN pip3 install --upgrade -r requirements.txt




CMD ["python3", "app.py"]

指定した単語を含むツイートを消去する簡易プログラム書いた(Python)

過去のネガティブなツイートを消したい

前提

Pythonの環境構築、必要なパッケージのインストール、ツイート履歴の取得、Twitterの各種トークンの取得が必要ですが、説明は省略しています。

github.com

検索をかけて出てくるツイート消去ツールがうまく使えなかったので、自分で書きました。以下コード。

コード

バグがないことを保証しません。これを利用して発生したいかなる損害についても責任を負いません。利用は自己責任でお願いします。

#!/usr/bin/env python
# coding: utf-8

# In[1]:


import os
import sys

import pandas as pd
import twitter
from requests_oauthlib import OAuth1Session


# In[2]:


consumer_key = os.environ["CONSUMER_KEY"]
consumer_secret = os.environ["CONSUMER_SECRET"]
access_token = os.environ["ACCESS_TOKEN_KEY"]
access_token_secret = os.environ["ACCESS_TOKEN_SECRET"]


# In[3]:


def return_twitter_api():

    api = twitter.Api(consumer_key=consumer_key,
                          consumer_secret=consumer_secret,
                          access_token_key=access_token,
                          access_token_secret=access_token_secret)

    return api


# In[4]:


def delete_tweets(tweet_ids):
    api = return_twitter_api()
    count = 0
    

    
    for id in tweet_ids:
        status = None
        
        try:
            status = api.DestroyStatus(id)
        except:
            if  'No status found with that ID.':
                continue
        finally:
            if status != None:
                count += 1
            else:
                pass

        
    return print("{}件のツイートを消去しました。".format(count))


# In[5]:


tweet_df = pd.read_csv("/hoge/tweets.csv")


# In[6]:


tw_id_and_text_df = tweet_df.reindex(columns=["timestamp", "tweet_id", "text"])


# In[7]:


print("消したいキーワードを入力してください。複数の単語を入力するにはスペースで区切る(cを入力でキャンセル)")
input_words = input().split()
if input_words == ["c"]:
    print("キャンセルしました")
    sys.exit()
elif input_words == []:
    print("単語を入力してください")
    sys.exit()
else:
    pass


# In[8]:


wanna_delete_words = "|".join(input_words)
wanna_delete_df = tw_id_and_text_df[tw_id_and_text_df["text"].str.contains(wanna_delete_words) == True]


# In[9]:


wd_tweet_ids = wanna_delete_df["tweet_id"]


# In[10]:


columns = ["timestamp", "text"]
new_df = wanna_delete_df.reindex(columns=columns)


# In[11]:


print(new_df)
print("対象ツイートは{}件あります。実行しますか? y/N".format(len(wd_tweet_ids)))
ans = input()

if ans == "y":
    delete_tweets(wd_tweet_ids)
else:
    print("キャンセルしました。")

補足

In[2]では環境変数に設定した各種トークンを変数に代入しています。
そのコードをどこにも上げないなら直接ここにconsumer_key = "hogehogefugafuga" みたいに入力してもOK。

In[4]のexcept内では既に別の単語に引っかかって消去済みのツイートに当たった場合No status found with that ID. と返ってくるのでそれを無視しています。
本当はそれが増えてくると実行時間の無駄なので、消去済みのものにはフラグを立てていった方がいいんですが、面倒なのでやってません。

やるならpandasのDataFrameに新しいcolumnを作って、消去済みかどうかの真偽値を入れる感じかな。

In[5]にはダウンロードした自分のtweets.csvファイルのパスを入力してください。

キーワードを複数含むツイートなど、既に消去済みの場合があるので対象ツイート数と消去したツイートの数は異なる場合があります。

Twitterの相互フォロワーをご飯に誘いたい?それ、簡単にできるよ

声をかけたくても色々考えちゃって声をかけられない人向けに、簡単にご飯(でも何でも)に誘えるwebアプリを作りました

f:id:hwhw:20190529074414p:plain

作ったきっかけ

Twitterしてると「〇〇日に東京行くので、誰かご飯食べませんかー?」みたいな募集を見ることがないですか?
あなたはそれに「行きたーい!」とすぐ返事できるタイプでしょうか。

私は「どうせ私が行きたいと言っても迷惑だろうし…」とウジウジしてしまうタイプです。

f:id:hwhw:20190629105745p:plain

なので、気軽に誘ったり誘いに乗ったりできるアプリを作ろうと思いました。

できました

コミュ障向けご飯誘いアプリ「あわよい飯」

Twitterの相互フォロワーをチェックボックスで選ぶだけで、簡単に誘いをかけることができます。
相互に会いたいと思っている場合だけ、お互いにそれが伝わるようになっています。

誘う人は正直あまり会いたくない人から誘われて断る面倒を避けられ、誘いに乗る側は「え、この人も私に会いたいの…?」とか思われる心配をせずに済むというメリットがあります。

良かったら使ってみてください。
「ここが変」みたいなのも(優しく)指摘してもらえると嬉しいです。

GitHub - hitowaft/NGAC: あわよい飯

使ったもの

  • Docker
  • Python + Flask
  • Heroku
  • Bootstrap

役立った本。一冊あるととても捗った。カバーの触り心地も良くてオススメ。

Bootstrap 4 フロントエンド開発の教科書

Bootstrap 4 フロントエンド開発の教科書