常に最新ツイートがトップに来るようなChrome ExtensionをTypescriptで作る(執筆中)
イラっとしますね(唐突)
Twitterは無駄に流れてくるタイムラインを眺めるのが楽しいのになんでデフォルトが「ホーム」なんでしょうか。
Twitter立ち上げるたびに最新ツイートに変更しています。
DBじゃなくてCookieで持っているんですかね。
むっちゃイライラするので、自動的に最新ツイートが投稿順に流れるようにするChrome Extensionを作りましょう。
これでひな型を作ります。
Would you like more UI Features?
と聞かれるので、ここだけ注意してください。まあ全部とりあえずぶち込んでおけばいいって考えてもいいですが(私は遊びだったので全部ぶち込みました)
Content Scripts
にチェックを入れてください。
windows 10
なので yarn
を入れる必要があったりするのですが、そこはよしなに入れていきましょう。
エラーが出たら入れて、を繰り返したら特に問題なく完了しました。
とりあえずぶち込んだら、
$ yarn install
します。場合によっては npm install
でもいいんじゃないでしょうか(試していないですが…)。
そこまで時間がかからず入れ終わるはずなので
$ yarn run dev:chrome
実行!!
あとは dist/chrome
をChrome拡張としてChromeにぶち込みます。
chrome://extensions/
にアクセスして
上記画像の「パッケージ化されていない拡張機能を読み込む」から dist
以下の chrome
を読み込みます。
開発ツールからコンソールを見ると成功していればなんか出ていると思います。
app\scripts\contentscript.ts
の console.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
解説というよりはまりどころ
- 画面ロード時と全体が表示されるタイミングが違う
=> よくあることなんですかね…。とりあえず 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);
実際に発火させます。
- タイムラインを切り変えるためのボタンはどう押す?
これも地味にはまりました。 id
も class
もあてにならないので。
document.querySelector('[role="menu"] [role="menuitem"]');
こんな実装に…。
initEvent
二回する意味ある?
今検討中です。
リポジトリも置いておきますが上記ファイル以外は何の意味もないファイルです。
Apple Script (JavaScript for Automation)でiTermを複数タブで開くのを自動化
Ruby on Railsで開発しているのだが、
docker
:docker-compose up
server
:rails s
console
:rails c
- 外部のやつ
タスク
orgit
:rake hogehoge:fugafuga
orgit
みたいに最低でも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のプログラムが動かなくて嘆いたけどどうにかなった(短い)
Python
で Slack
に現在の気圧を通知するプログラムを書いていました。
Slack Api、botをワークスペースに参加させてなくて、2時間くらい溶かした
— キャリーラーメン (@hungrykirby) January 25, 2020
むっちゃ情けないんだけど上記ツイートの通り必要なチャンネルにbotを追加していなくて時間を溶かしました。 2時間って書いたけど、4時間くらい溶かした気がする。
で、 Slack
だけじゃなくて、 Google Spread Sheet
にも記録を残したいなと考えました。
tanuhack.com 内心「どんなエラーが待ち構えているのか」とびくびくしていたのですが、なんのエラーもなく上記のままで行けました。(2020-01-25現在)
で、 Rapberry PI
の cron
で動かそうとしたけど Slack
通知は来るのに、 Spread Sheet
は更新されないわけです。
普通に叩くと動くのに…。
APIのせいかなとも疑ったのですがパスの設定でした。
- Pythonのパス
app.py
のパス- プログラム中のパス
- MTA
30 * * * * python app.py
って書くわけじゃないですか。
記法は上記が分かりやすかったです。
qiita.com ノリでログまで設定する。 ログで出てくる情報限られているので、そこはまた今度設定する。
ログを設定して見るとどうやらエラーが。。。
No MTA installed, discarding output
みたいな。
MTA
がないといわれる。普通のLinuxにはデフォルトで入っているけどRasbianには入っていないらしい。
上記のようにエラーと対処
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)
WindowsでPythonを使うとき、仮想環境としては Anaconda
が使いやすいかなと思っています。
ただ、パスを通して使っても Powershell
では activate
コマンドが正しく機能しません。
つまり仮想環境を作ってもそこに入る方法がありません。
conda create -n py38 python=3.8 anaconda
やっても
activate py38
をして
conda info -e
すると base
のまんまです。
で、いろいろ試して activate.ps1
みたいなの自分で作って権限変えて…というような超絶めんどくさい手順を踏みました。パソコンを初期化したり新しいパソコンをもらったり、会社用のパソコンにやったり、4回くらいやったと思います。
現在のパソコンでもその設定をしていたはずなのですが、どうにもうまく動かないのでもう一度調べなおしました。
そしたら
~~完~~
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 使い方
pythonでsalesforceの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は消えちゃう(´・ω・`)
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みたいなのを作って(これも自動化できるけどそこまでコード書くのはだるかったからやらなかった).