ツタンラーメンの忘備録

プログラミングや精神疾患、ラーメンについて書いていきます。たぶん。

常に最新ツイートがトップに来るようなChrome ExtensionをTypescriptで作る(執筆中)

「ホーム」ではトップツイートが優先的に表示されます
「ホーム」ではトップツイートが優先的に表示されます
イラっとしますね(唐突)

Twitterは無駄に流れてくるタイムラインを眺めるのが楽しいのになんでデフォルトが「ホーム」なんでしょうか。

最新ツイートが投稿順に表示されます
最新ツイートが投稿順に表示されます

Twitter立ち上げるたびに最新ツイートに変更しています。

DBじゃなくてCookieで持っているんですかね。

むっちゃイライラするので、自動的に最新ツイートが投稿順に流れるようにするChrome Extensionを作りましょう。

qiita.com

これでひな型を作ります。

Would you like more UI Features?

と聞かれるので、ここだけ注意してください。まあ全部とりあえずぶち込んでおけばいいって考えてもいいですが(私は遊びだったので全部ぶち込みました)

Content Scripts にチェックを入れてください。

windows 10 なので yarn を入れる必要があったりするのですが、そこはよしなに入れていきましょう。 エラーが出たら入れて、を繰り返したら特に問題なく完了しました。


とりあえずぶち込んだら、

$ yarn install

します。場合によっては npm install でもいいんじゃないでしょうか(試していないですが…)。

そこまで時間がかからず入れ終わるはずなので

$ yarn run dev:chrome

実行!!

あとは dist/chromeChrome拡張としてChromeにぶち込みます。

chrome://extensions/ にアクセスして

エクステンション読み込み
エクステンション読み込み
上記画像の「パッケージ化されていない拡張機能を読み込む」から dist 以下の chrome を読み込みます。

開発ツールからコンソールを見ると成功していればなんか出ていると思います。 app\scripts\contentscript.tsconsole.log に対応しています。

更新したらこのくるっと矢印を押してください
更新したらこのくるっと矢印を押してください
私はけっこうここではまったんですけど、 app\scripts\contentscript.ts を編集しても chrome://extensions/ の該当Extensionのくるっと回っている矢印で更新しないと反映されません。 なんというトラップ

さてここから app\scripts\contentscript.ts を編集していきましょう。 潜影蛇手

window.addEventListener("load", Main);

function Main():void {
  let event = document.createEvent( 'MouseEvents' );
  setTimeout(() => {
    const top_tweet_label = document.querySelector('[aria-label="トップツイートがオフになります"]');
    const home_tweet_label = document.querySelector('[aria-label="トップツイートがオンになります"]');
    let timeline_change_star = home_tweet_label === null ? top_tweet_label : home_tweet_label;
    event.initEvent("click", true, true);
    if(timeline_change_star !== null){
      timeline_change_star.dispatchEvent(event);
      setTimeout(() => {
        let button_change_timeline_change = document.querySelector('[role="menu"] [role="menuitem"]');
        event.initEvent("click", true, true);
        if(button_change_timeline_change !== null){
          button_change_timeline_change.dispatchEvent(event);
        }
      }, 500);
    }
  }, 4000);

(この段階では画面をロードした瞬間に「ホーム」と「最新ツイート」を切り替えます。) 動いているGif

作成したエクステンションの動作
作成したExtensionの動作


解説というよりはまりどころ

  • 画面ロード時と全体が表示されるタイミングが違う

=> よくあることなんですかね…。とりあえず window.addEventListener("load", Main); ってしたらよしなに読み込んでくれると思ったのですが、実際はちょっとずらしているっぽいです。 React の動作なのかな。 とりあえず setTimeout で4秒後に実行にします。 だいたい3.5秒くらいで(私の環境では)ロードされました。 これはネットワークなどによって異なるため、ほんまは繰り返して成功したタイミングで!!とかやりたかったんですけど、無限ループでブラウザ落ちて(正確には閉じることすらできなくなった)だめだったので、暫定対応です。

  • TypeScriptの画面ロード完了後に読み込まれるメソッドは?

window.addEventListener("load", Main); いつも jQuery 使っていたので知らなかったです。

  • タイムラインを並び順を変更するボタンをどうやって取得する?

Class は難読化のためか、まったく規則性がなくて使えない。 id もけっこう上にしかついていなくて、取得できない。 諦めかけていたのですが、「トップツイートがオフになります」「トップツイートがオンになります」これだけは不変であるようでした。これを取得することに

  • 3を決めたがいいがどうやって取得する?

document.querySelector('[aria-label="トップツイートがオフになります"]'); いつも jQuery 使っていたので querySelector 初めて使いました(笑)

  • マウスイベントをどうやって発火させる?(そもそもマウスイベントでやるべきなのか、まだ検討中です)

ここも地味にはまりました。検索かけても取得する方法で発火させる方法は全然出てこない。 document.createEvent( 'MouseEvents' ); マウスイベントを作った?上で event.initEvent("click", true, true); クリックイベントを登録?して(すみません、残り二つの引数の意味わかっていないです。最初動かなくて適当に入れたら動きました…)、

timeline_change_star.dispatchEvent(event);

実際に発火させます。

  • タイムラインを切り変えるためのボタンはどう押す?

これも地味にはまりました。 idclass もあてにならないので。

 document.querySelector('[role="menu"] [role="menuitem"]');

こんな実装に…。

  1. initEvent 二回する意味ある?

今検討中です。


リポジトリも置いておきますが上記ファイル以外は何の意味もないファイルです。

github.com

Apple Script (JavaScript for Automation)でiTermを複数タブで開くのを自動化

Ruby on Railsで開発しているのだが、

  • docker : docker-compose up
  • server : rails s
  • console : rails c
  • 外部のやつ
  • タスク or git : rake hogehoge:fugafuga or git

みたいに最低でも5タブ(ウィンドウだとちらかったのでタブにしている)開く。 これを毎回手動で開くのがめんどくさかったので、 Apple Script くんに頑張ってもらうことにした。

しかし、記法が好きなれなかったので JXA で実装した。 gif貼りたいけど、諸事情で貼れない。

以下コード

var titles = ["docker", "server", "console", "git or rake", "others"];

function run(input, parameters) {
  var Terminal = Application('iTerm');
  Terminal.createWindowWithDefaultProfile();
  var terW1 = Terminal.windows[0];
  delay(1)
  try{
    for(var i = 0; i < titles.length; i++){
        session = terW1.tabs[i].sessions[0];
        session.write({ text: 'cd path/to/somewhere', newline: true });
        session.name = titles[i];
        sys = Application("System Events");
        if( i !== titles.length - 1){
            sys.keystroke("t", { using: "command down"});
        }else{
            sys.keystroke("1", { using: "command down"});
        }
        delay(1);
    }
  }catch(e){
    // なにか書こうと思ったが今はいいや
  }
  delay(1)
  
  return input;
}

参考にしたコードの変数が js だけど大文字で始まったり、スネークケースかキャメルケースか変なところだが…。


session.write({ text: 'cd path/to/somewhere', newline: true }); <= この部分は session.cmd みたいなのがあるかなと思ったけど、いい感じなのが動かず、こういう形にした。

sys.keystroke("t", { using: "command down"}); <= 新規にタブを作成してする

delay(1); <= これをいれないと、同じタブ内でやりきってしまう。

sys.keystroke("1", { using: "command down"}); <= ループの最後で最初のタブに戻る

return input; <= いらない気がするけど残している。

Raspberry PIでPythonのプログラムが動かなくて嘆いたけどどうにかなった(短い)

PythonSlack に現在の気圧を通知するプログラムを書いていました。

むっちゃ情けないんだけど上記ツイートの通り必要なチャンネルにbotを追加していなくて時間を溶かしました。 2時間って書いたけど、4時間くらい溶かした気がする。

で、 Slack だけじゃなくて、 Google Spread Sheet にも記録を残したいなと考えました。

tanuhack.com 内心「どんなエラーが待ち構えているのか」とびくびくしていたのですが、なんのエラーもなく上記のままで行けました。(2020-01-25現在)

で、 Rapberry PIcron で動かそうとしたけど Slack 通知は来るのに、 Spread Sheet は更新されないわけです。 普通に叩くと動くのに…。

APIのせいかなとも疑ったのですがパスの設定でした。

  1. Pythonのパス
  2. app.py のパス
  3. プログラム中のパス
  4. MTA
30 * * * * python app.py

って書くわけじゃないですか。

make.bcde.jp

記法は上記が分かりやすかったです。

qiita.com ノリでログまで設定する。 ログで出てくる情報限られているので、そこはまた今度設定する。

ログを設定して見るとどうやらエラーが。。。 No MTA installed, discarding output みたいな。 MTA がないといわれる。普通のLinuxにはデフォルトで入っているけどRasbianには入っていないらしい。

qiita.com

上記のようにエラーと対処

sudo apt-get install postfix

動かない

which python

これで

30 * * * * /path/to/python app.py

。。。だめ

30 * * * * /path/to/python /path2/to2/app.py

。。。だめ 正確に言うとここで Slack 通知まではちゃんと来る。

ただ、 Spread Sheet がだめ。更新されない。

わからなかったんだけど、ソースコード中のパスもフルパスである必要がある。

上記の例だと

credentials = ServiceAccountCredentials.from_json_keyfile_name('file_name.json', scope)

=>

from os.path import join, dirname
credentials = ServiceAccountCredentials.from_json_keyfile_name(join(dirname(__file__), 'file_name.json), scope)

みたいな。 わからん。

ちなみに

os.getcwd()

上記はだめ。

join(dirname(__file__), 'file_name.json)

の挙動はいまいちわかっていない

Anaconda Powershell activateについて(2020-01-24)

WindowsPythonを使うとき、仮想環境としては Anaconda が使いやすいかなと思っています。 ただ、パスを通して使っても Powershell では activate コマンドが正しく機能しません。

つまり仮想環境を作ってもそこに入る方法がありません。

conda create -n py38 python=3.8 anaconda

やっても

activate py38

をして

conda info -e

すると base のまんまです。

で、いろいろ試して activate.ps1 みたいなの自分で作って権限変えて…というような超絶めんどくさい手順を踏みました。パソコンを初期化したり新しいパソコンをもらったり、会社用のパソコンにやったり、4回くらいやったと思います。 現在のパソコンでもその設定をしていたはずなのですが、どうにもうまく動かないのでもう一度調べなおしました。

そしたら

qiita.com

~~完~~

conda init powershell

でよしなにやってくれます。 そのあとくだらないことでつまずいたので、備忘録としてこの記事を書いています。

やったー!!!!と思い

activate py38

としました。ところが

conda info -e

したところ

# conda environments:
#
base                   * C:\work\conda
multilogin            C:\work\conda\envs\multilogin
py38                    C:\work\conda\envs\slackPy38
twitter                 C:\work\conda\envs\twitter

あれ、環境が変わってない…?となりました。なんでーーーー!!!となったのですが、少しコマンドが変わっていました。 新しいのは

conda activate py38

conda が必要になったのですね~~。

python salesforce einstein 始める language 使い方

pythonsalesforceのeinstein apiを使おうとしたんだけど、絶妙に情報が少ない上に、絶妙にわかりづらい…。
Salesforce Einstein は、ビジネステクノロジーにおける AI です。 - セールスフォース・ドットコム

というわけでpythonでデータセットを持ち上げるサンプルファイルを公式に基づいて書いた。公式だと複数ファイルに分割していたが、その書き方がわかりづらいので…。

EINSTEIN_BASE_URL = 'https://api.einstein.ai/v2'
LANG_BASE_URL = EINSTEIN_BASE_URL + '/language'
LANG_DATASETS_URL = LANG_BASE_URL + '/datasets'
ACCESS_TOKEN = '***'

import requests
import json
from requests_toolbelt.multipart.encoder import MultipartEncoder

class DataSet():
    def __init__(self, access_token):
        self.access_token = access_token

    def create_intent_dataset(self, path):
        type = 'text-intent'
        return self._create_dataset(path, type)

    def _create_dataset(self, path, type):
        multipart_data = MultipartEncoder(
            fields={
                'path': path,
                'type': type
            }
        )
        headers = {'Authorization': 'Bearer ' + self.access_token,
                'Content-Type': multipart_data.content_type}
        res = requests.post(LANG_DATASETS_URL + '/upload',
                            headers=headers, data=multipart_data)
        json_response = json.loads(res.text)
        return json_response

def main():
    access_token = ACCESS_TOKEN
    dataset = DataSet(access_token=access_token)
    path = 'http://einstein.ai/text/weather.csv'
    response = dataset.create_intent_dataset(path)
    print(json.dumps(response, indent=4, sort_keys=True))

if __name__ == "__main__":
    main()

Processing いい感じに改行 文字枠

Processingでhtml的に幅を指定していい感じに改行した上で,その文字に背景色をつけたかった.
というわけでそういうことができる感じのclassを作った.なんか汚いけど….

setup(文字の上から枠の外までの余白,左,下,左)
update(テキストボックスの最大幅)
draw(文字開始の左, 文字開始の上) -> 枠の下のy座標を返す

ちなみにtabは消えちゃう(´・ω・`)

f:id:hungrykirby:20180208185434g:plain

RectText tb;

void setup(){
  size(1920, 1024);
  background(255);
  String sampleText = "abcdefghijklmnopqrstuvw\nxyz'012345\t67890@[]ああああああああいうえおかきくけこ劉備早々無限大";
  tb = new RectText(sampleText);
  tb.setup(200, 50, 10, 120); /*top, right, buttom, left*/
}

void draw(){
  background(255);
  tb.update(1000.0);
  float y = tb.draw(mouseX, mouseY);
  ellipse(mouseX, y, 20, 20);
}

class RectText {
  String t;
  String textModified;
  StringList sl;
  float xPos, yPos;
  
  float paddingLeft, paddingRight, paddingButtom, paddingTop;
  
  PFont pF;
  
  float th; //text height
  float le; //leading
  
  int numLines;
  float maxWidth; //max width of text
  
  RectText(String _t){
    pF = loadFont("RictyDiminished-Regular-48.vlw");
    t = _t;
  }
  
  void setup(float pT, float pR, float pB, float pL){
    textFont(pF, 48);
    textAlign( LEFT, TOP );    
    th = textAscent() + textDescent();
    le = 2.0;
    textLeading(th+le);
    noStroke();
    
    paddingTop = pT; paddingRight = pR; paddingButtom = pB; paddingLeft = pL;
  }
  
  void update(float w){
    maxWidth = calcMaxW(t, w);
    sl = wordWrap(t, maxWidth);
    numLines = sl.size();
    textModified = "";
    for(int i = 0; i < numLines; i++){
      textModified += (sl.get(i) + "\n");
    }
  }
  float draw(float x, float y){ /*xとyを開始点として文字を書く*/
    xPos = x; yPos = y;
    fill(0);
    rect(xPos - paddingRight, yPos - paddingTop, maxWidth + paddingRight + paddingLeft, (th+le)*(numLines+0) + paddingTop + paddingButtom);
    fill(255, 0, 0);
    text(textModified, xPos, yPos);
    
    return (th+le)*(numLines+0) + paddingButtom + yPos; //Buttom
  }
  
  StringList wordWrap(String s, float mW) {
    StringList a = new StringList();
    float w = 0;
    int i = 0;
    while (i < s.length()) {
      char c = s.charAt(i);
      String cc = "" + c;
      w += textWidth(cc);
      if(c == '\n'){
        String sub = s.substring(0, i);
        a.append(sub);
        s = s.substring(i + 1, s.length());
        i = 1;
        w = 0;
      }else{
        if (w > mW) {
          String sub = s.substring(0, i);
          a.append(sub);
          s = s.substring(i ,s.length());
          i = 0;
          w = 0;
        } else {
          i++;
        }
      }
    }
    a.append(s);
    return a;
  }
  
  float calcMaxW(String _t, float mW){
    float f = 0;
    if(textWidth(_t) > mW) f = mW;
    else f = textWidth(_t);
    return f;
  }
  
}

Python LINE bot imagemap 画像作成 自動化

以前の記事でLINE bot + flask + imagemapをやりました.画像準備するのがクソだるかったので,自動的に画像を作成するやつ作りました.

import glob

files = glob.glob('C:\work\python_data\\resizeImg\data\*')

import cv2
import numpy as np

import sys
import os

sizes = [240, 300, 460, 700, 1040] #imagemapで必要な横幅

for f in files:
    img = cv2.imread(f, cv2.IMREAD_COLOR)
    imgH, imgW = img.shape[:2]
    name,ext = os.path.splitext( os.path.basename(f) )
    index = int(name[1])
    for s in sizes:
        cv2.imwrite("C:\work\python_data\\resizeImg\\3" + str(index) + "\\" + str(s) + ".jpg", cv2.resize(img, (s, int(s*imgH/imgW)))) #拡張子がないとエラーした
        os.rename("C:\work\python_data\\resizeImg\\3" + str(index) + "\\" + str(s) + ".jpg", "C:\work\python_data\\resizeImg\\3" + str(index) + "\\" + str(s))

大きさを変えたい画像(縦長を想定)をX0.jpg,X1.jpgみたいに保存する.(本来はint(name[-1]がいい気がする))
フォルダは30,31みたいなのを作って(これも自動化できるけどそこまでコード書くのはだるかったからやらなかった).