2022/05/04(水)2022年にPerlで画像付きtweetするコード
#!/usr/bin/env perl
use strict;
use warnings;
use utf8;
use Twitter::API;
my $twitter = Twitter::API->new_with_traits(
traits => [qw/Migration ApiMethods RetryOnError/],
consumer_key => 'xxx',
consumer_secret => 'xxx',
access_token => 'xxx',
access_token_secret => 'xxx',
);
my $media = ['/nanka/tekitohna/filepath.png'];
my $ret = $twitter->upload_media($media);
$twitter->update('ツイートするテキスト', { media_ids => $ret->{media_id} });
ちなみに2022年現在、Net::Twitter::Lite からは画像をアップロードできませんでした。もう古いので、モダンな Twitter::API を使うのが良さそうです。
2019/06/05(水)【雨量観測】XRAINのスクリーンショットを会社Slackに投稿するPerlスクリプト
天気情報が流れるチャンネルが会社のSlackにあるのですが、XRAINのスクリーンショットも流れてきてほしくなったので作りました。
ちなみに大学生の頃は大学院の研究室訪問や大学院試験の費用を稼ぐために気象会社でアルバイトしていました。
SlackのtokenとチャンネルIDの取得情報はググってください。
$logo
は会社のロゴと矢印が描かれた画像で、矢印の先にオフィスがあるという具合になっています。適当な画像を用意して置き換えてください。
CPANモジュール以外は何か特別にインストール必要なものはありませんでした。
#!/usr/bin/evn perl
use strict;
use warnings;
use utf8;
use Log::Log4perl qw(:easy);
use WWW::Mechanize::Chrome;
use File::Spec;
use File::Basename 'dirname';
use HTTP::Request::Common qw//;
use Encode;
use Furl qw//;
use JSON;
use Imager;
use DDP;
# TODO:
# * 会社付近の色を取得して雨が降っているか判定
my $dirname = File::Spec->rel2abs(dirname($0));
my $rain_url = 'http://www.river.go.jp/x/krd0207010.php?lon=139.50406730175018&lat=35.674031853779084&opa=0.4&zoom=8&leg=0&intvl=5&ext=0';
my $fn= $dirname . "/xrain.png";
my $logo = $dirname . '/logo_and_arrow.png';
Log::Log4perl->easy_init($ERROR);
my $mech = WWW::Mechanize::Chrome->new(headless => 1);
$mech->viewport_size({ width => 760, height => 780 });
$mech->get($rain_url);
my $png = $mech->content_as_png;
open(my $fh, '>', $fn) or die $!;
binmode($fh);
print {$fh} $png;
close $fh;
my $img = Imager->new;
$img->read(file => $fn) or die $img->errstr;
$img->rubthrough(
src => do {
my $tmp = Imager->new;
$tmp->read(file => $logo) or die $tmp->errstr;
$tmp;
},
tx => 460,
ty => 527,
);
$img->rubthrough(
src => do {
my $tmp = Imager->new;
$tmp->read(file => $logo) or die $tmp->errstr;
$tmp;
},
tx => 1203,
ty => 527,
);
$img->rubthrough(
src => do {
my $tmp = Imager->new;
$tmp->read(file => $logo) or die $tmp->errstr;
$tmp;
},
tx => 460,
ty => 1170,
);
$img->rubthrough(
src => do {
my $tmp = Imager->new;
$tmp->read(file => $logo) or die $tmp->errstr;
$tmp;
},
tx => 1203,
ty => 1170,
);
$img->write(file => $fn) or die $img->errstr;
my $req = HTTP::Request::Common::POST('https://slack.com/api/files.upload',
Content_Type => 'multipart/form-data',
Content => [
token => 'xxxx',
channels => 'xxxx',
file => [$fn],
title => Encode::encode_utf8('XRAIN自動投稿'),
initial_comment => $rain_url,
]
);
my $res = Furl->new->request($req);
my $json = Encode::decode_json($res->content);
#p $json;
うまくいけば↓のように投稿されます。cronで定期実行させています。
2016/02/18(木)Google Calendar のデータをXMLに加工して monthly.js に 流し込む
所属している草野球チームのHP制作を担当していて、「スケジュールを載せたい」という要望が来ました。数週間ぐらい前に「そういえば何かカレンダーをキレイに表示させるJSライブラリがあったよなぁ」と「monthly.js」思い出して、Googleカレンダーとこれを使って表示させることにしました。
練習場所まで全世界に公開するのも気持ちが悪いので、Googleのカレンダーは「非公開」の設定のままで、どうにか情報を取得するすべはないものかと調べると、OAuth2なGoogle Calendar API というAPIを使えばいいと載っていました。( OAuth2じゃないAPIもあるっぽいですが、実装が複雑になるのでOAuth2の方式がいいよみたいな情報が公式ドキュメントに書かれていた。うろ覚え。)
しかし、肝心のPerlの解説がない。(仕事ではRubyがメインですが、草野球のホームページぐらいの規模ならPerlで書きたい。)ライブラリ自体は「Google::API::Client」という名前でCPANに登録されているのでこれを使うことにしました。
client_secret.json みたいなファイルをGoogle APIのページから取得すると(詳細は「celient_secret.json google」でググッて)あとは、以下のコードで、初回時のみ手動で認証してリフレッシュトークンを取得すると、それ以降はよしなにしてくれます。(ずっとアクセスを待ち受ける必要があるWebアプリにも組み込めます。)
#!/usr/bin/env perl
use strict;
use warnings;
use utf8;
use feature qw/say/;
use open qw/:encoding(utf-8) :std/;
use JSON qw//;
use Time::Moment qw//;
use Google::API::Client;
use Google::API::OAuth2::Client;
use XML::Simple ();
use Cache::FileCache;
use DDP; # cpanm Data::Printer してね
my $myemail = 'xxx@gmail.com';
my $client_secrets_file = 'client_secret.json';
my $client = Google::API::Client->new;
my $gcal = $client->build('calendar', 'v3');
my $auth_driver = Google::API::OAuth2::Client->new_from_client_secrets($client_secrets_file, $gcal->{auth_doc});
my $dat_file = 'token.dat';
get_or_restore_token($dat_file, $auth_driver);
my $my_calender_id = 'primary';
my $holiday_calendar_id = 'ja.japanese#holiday@group.v.calendar.google.com';
my $cache = Cache::FileCache->new({
cache_root => '/tmp',
namespace => 'calendar',
});
my %cache_duration_of = (
$my_calender_id => '5s',
$holiday_calendar_id => '7d',
);
my $now = Time::Moment->now;
my $first_day_of_month = Time::Moment->new(
year => $now->year,
month => $now->month,
day => 1,
offset => 540, # +09:00(540分)
);
my $req_body = {
timeMax => $first_day_of_month->plus_months(6)->to_string,
timeMin => $first_day_of_month->minus_months(6)->to_string,
};
my $schedule = get_or_cache_event_items($my_calender_id);
my $holidays = get_or_cache_event_items($holiday_calendar_id);
my @events = (@{$schedule->{items}}, @{$holidays->{items}});
print make_xml(\@events);
exit;
sub get_or_restore_token
{
my ($file, $auth_driver) = @_;
if (-f $file)
{
open (my $fh, '<', $file) or die $!;
local $/;
my $access_token = JSON::from_json(<$fh>);
close($fh);
$auth_driver->token_obj($access_token);
}
else
{
my $auth_url = $auth_driver->authorize_uri;
say 'Go to the following link in your browser:';
say $auth_url;
say 'Enter verification code:';
chomp(my $code = <>);
$auth_driver->exchange($code);
store_token($file, $auth_driver);
}
}
sub store_token
{
my ($file, $auth_driver) = @_;
my $access_token = $auth_driver->token_obj;
open (my $fh, '>', $file) or die $!;
print {$fh} JSON::to_json($access_token);
close($fh);
}
sub get_or_cache_event_items
{
my $calendar_id = shift;
my $items = $cache->get($calendar_id);
if ( ! $items )
{
$items = $gcal->events->list(calendarId => $calendar_id, body => $req_body)->execute({ auth_driver => $auth_driver });
$cache->set($calendar_id => $items, $cache_duration_of{$calendar_id}) if $items;
}
return $items;
}
sub get_date_using_datetime { local $_ = $_[0]; s/(.+)T.+/$1/; $_; }
sub get_time_using_datetime { local $_ = $_[0]; s/.+T([0-9][0-9]:[0-9][0-9]):[0-9][0-9]\+.+/$1/; $_; }
sub make_xml
{
my $items = shift;
my $data = {};
$data->{event} = ();
my $i = 0;
for my $item (@{$items})
{
my $event = {};
my $startdate = $item->{start}{date} ? $item->{start}{date} : get_date_using_datetime($item->{start}{dateTime});
my $enddate = $item->{end}{date} ? $item->{end}{date} : get_date_using_datetime($item->{end}{dateTime});
$event->{id} = ++$i;
$event->{name} = $item->{summary};
$event->{startdate} = $startdate;
$event->{enddate} = $enddate;
$event->{color} = '#ffb128';
$event->{color} = '#ddeeff' if $item->{organizer}{email} ne $myemail;
$event->{starttime} = get_time_using_datetime($item->{start}{dateTime}) if $item->{start}{dateTime};
$event->{endtime} = get_time_using_datetime($item->{end}{dateTime}) if $item->{end}{dateTime};
push(@{$data->{event}}, $event);
}
return XML::Simple::XMLout($data, RootName => 'monthly', XMLDecl => '<?xml version="1.0"?>', NoAttr => 1);
}
以下のようにXMLが出力されます。
<?xml version="1.0"?>
<monthly>
<event>
<name>練習(雨天中止)</name>
<color>#ffb128</color>
<enddate>2016-02-14</enddate>
<endtime>15:30</endtime>
<id>1</id>
<startdate>2016-02-14</startdate>
<starttime>11:00</starttime>
</event>
<event>
<name>試合!</name>
<color>#ffb128</color>
<enddate>2016-03-26</enddate>
<endtime>16:00</endtime>
<id>2</id>
<startdate>2016-03-26</startdate>
<starttime>14:00</starttime>
</event>
<event>
<name>グランド練習</name>
<color>#ffb128</color>
<enddate>2016-02-20</enddate>
<endtime>16:00</endtime>
<id>3</id>
<startdate>2016-02-20</startdate>
<starttime>14:00</starttime>
</event>
<event>
<name>練習</name>
<color>#ffb128</color>
<enddate>2016-02-28</enddate>
<endtime>15:00</endtime>
<id>4</id>
<startdate>2016-02-28</startdate>
<starttime>10:00</starttime>
</event>
<event>
<name>練習</name>
<color>#ffb128</color>
<enddate>2016-03-06</enddate>
<endtime>15:00</endtime>
<id>5</id>
<startdate>2016-03-06</startdate>
<starttime>10:00</starttime>
</event>
<event>
<name>練習</name>
<color>#ffb128</color>
<enddate>2016-03-13</enddate>
<endtime>15:00</endtime>
<id>6</id>
<startdate>2016-03-13</startdate>
<starttime>10:00</starttime>
</event>
<event>
<name>練習</name>
<color>#ffb128</color>
<enddate>2016-04-03</enddate>
<endtime>15:00</endtime>
<id>7</id>
<startdate>2016-04-03</startdate>
<starttime>10:00</starttime>
</event>
<event>
<name>練習</name>
<color>#ffb128</color>
<enddate>2016-03-20</enddate>
<endtime>15:00</endtime>
<id>8</id>
<startdate>2016-03-20</startdate>
<starttime>10:00</starttime>
</event>
<event>
<name>敬老の日</name>
<color>#ddeeff</color>
<enddate>2015-09-22</enddate>
<id>9</id>
<startdate>2015-09-21</startdate>
</event>
<event>
<name>国民の休日</name>
<color>#ddeeff</color>
<enddate>2015-09-23</enddate>
<id>10</id>
<startdate>2015-09-22</startdate>
</event>
<event>
<name>秋分の日</name>
<color>#ddeeff</color>
<enddate>2015-09-24</enddate>
<id>11</id>
<startdate>2015-09-23</startdate>
</event>
<event>
<name>文化の日</name>
<color>#ddeeff</color>
<enddate>2015-11-04</enddate>
<id>12</id>
<startdate>2015-11-03</startdate>
</event>
<event>
<name>元日</name>
<color>#ddeeff</color>
<enddate>2016-01-02</enddate>
<id>13</id>
<startdate>2016-01-01</startdate>
</event>
<event>
<name>昭和の日</name>
<color>#ddeeff</color>
<enddate>2016-04-30</enddate>
<id>14</id>
<startdate>2016-04-29</startdate>
</event>
<event>
<name>憲法記念日</name>
<color>#ddeeff</color>
<enddate>2016-05-04</enddate>
<id>15</id>
<startdate>2016-05-03</startdate>
</event>
<event>
<name>体育の日</name>
<color>#ddeeff</color>
<enddate>2015-10-13</enddate>
<id>16</id>
<startdate>2015-10-12</startdate>
</event>
<event>
<name>みどりの日</name>
<color>#ddeeff</color>
<enddate>2016-05-05</enddate>
<id>17</id>
<startdate>2016-05-04</startdate>
</event>
<event>
<name>海の日</name>
<color>#ddeeff</color>
<enddate>2016-07-19</enddate>
<id>18</id>
<startdate>2016-07-18</startdate>
</event>
<event>
<name>勤労感謝の日</name>
<color>#ddeeff</color>
<enddate>2015-11-24</enddate>
<id>19</id>
<startdate>2015-11-23</startdate>
</event>
<event>
<name>天皇誕生日</name>
<color>#ddeeff</color>
<enddate>2015-12-24</enddate>
<id>20</id>
<startdate>2015-12-23</startdate>
</event>
<event>
<name>春分の日</name>
<color>#ddeeff</color>
<enddate>2016-03-21</enddate>
<id>21</id>
<startdate>2016-03-20</startdate>
</event>
<event>
<name>春分の日 振替休日</name>
<color>#ddeeff</color>
<enddate>2016-03-22</enddate>
<id>22</id>
<startdate>2016-03-21</startdate>
</event>
<event>
<name>こどもの日</name>
<color>#ddeeff</color>
<enddate>2016-05-06</enddate>
<id>23</id>
<startdate>2016-05-05</startdate>
</event>
<event>
<name>成人の日</name>
<color>#ddeeff</color>
<enddate>2016-01-12</enddate>
<id>24</id>
<startdate>2016-01-11</startdate>
</event>
<event>
<name>建国記念の日</name>
<color>#ddeeff</color>>
<enddate>2016-02-12</enddate>
<id>25</id>
<startdate>2016-02-11</startdate>
</event>
</monthly>
Googleカレンダーが終了時刻それ自体はイベントの期間に含めない(不等号に「=(イコール)」が付くかどうかの違いのようなもの) のに対して、monthly.js は終了時刻それ自体をイベントの期間に含めるという違いがあるため、endtime(enddateじゃなくて)がない場合は、enddate を一日早めるという面倒な処理が追加で必要になります。(上記のコードではそれが追加されていないので注意。)
上記の面倒な処理を終えて「monthly.js」のマニュアルを読んでWebアプリに組み込むと、以下のように美しく表示できるようになります。ちなみに、高速で「月」を切り替えるとイベントが移動したり増殖したりする「monthly.js」のバグを作り終えてから発見しました。(ぎりぎりクリティカルなバグでもないかというあんばいですが…)
お疲れ様です。( ´Д`)=3
2015/01/18(日)Python勉強メモ#5「homura と madoka で形態素の頻度を数えよ」
あらかじめ「pip」とかで「homura」と「madoka」を入れておきましょう。
まずはダウンローダーの「homura」で形態素解析済みの青空文庫のデータをダウンロード。
my-mbp% ipython
Python 3.4.2 (default, Jan 12 2015, 11:46:28)
Type "copyright", "credits" or "license" for more information.
IPython 2.3.1 -- An enhanced Interactive Python.
? -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help -> Python's own help system.
object? -> Details about 'object', use 'object??' for extra details.
In [1]: from homura import download
In [2]: download('http://aozora-word.hahasoha.net/utf8/newnew.csv.gz')
100% 615.8 MiB 11.1 MiB/s 0:00:00 ETA
適当に解凍したら「madoka」(Pythonの辞書より省メモリかもしれないデータ構造を提供するライブラリ)で形態素をカウントです。(Madokaの詳細: http://s-yata.github.io/madoka/index.ja.html)
#!/usr/bin/env python
import madoka
sketch = madoka.Sketch()
with open('newnew.csv', 'r') as f:
for line in f:
morpheme = line.split(',')[3]
sketch[morpheme] += 1
print(morpheme)
print(sketch['人'])
print(sketch['面倒'])
print(sketch['蕩尽'])
出力された頻度(推定値)は以下のようになりました。
1418808
57427
1910
実際の頻度は以下の通り。
#!/usr/bin/env python
from collections import Counter
cnt_of = Counter()
with open('newnew.csv', 'r') as f:
for line in f:
morpheme = line.split(',')[3]
cnt_of[morpheme] += 1
print(morpheme)
print(cnt_of['人'])
print(cnt_of['面倒'])
print(cnt_of['蕩尽'])
217837
1738
23
2015/01/16(金)Python勉強メモ#4「ベンチマークをとれ」
PyPIにある「Benchmarker」を利用。コードはひらがなリストを作る処理です。リスト内包表記が最速でした。
#!/usr/bin/env python
from benchmarker import Benchmarker
with Benchmarker(1000*100, width=20) as bench:
code_point = range(0x3040, 0x30A0)
@bench('1')
def _(bm):
for i in bm:
moji_list = []
for cp in code_point: moji_list.append( chr(cp) )
@bench('2')
def _bm(bm):
for i in bm:
moji_list = [ chr(cp) for cp in code_point ]
@bench('3')
def _bm(bm):
for i in bm:
moji_list = list(chr(cp) for cp in code_point)
## benchmarker: release 4.0.1 (for python)
## python version: 3.4.2
## python compiler: GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.56)
## python platform: Darwin-14.0.0-x86_64-i386-64bit
## python executable: /Users/username/.anyenv/envs/pyenv/versions/3.4.2/bin/python
## cpu model: Intel(R) Core(TM) i5-4258U CPU @ 2.40GHz
## parameters: loop=100000, cycle=1, extra=0
## real (total = user + sys)
1 3.1367 3.0500 3.0200 0.0300
2 2.4863 2.4300 2.4200 0.0100
3 2.8027 2.7300 2.7100 0.0200
## Ranking real
2 2.4863 (100.0) ********************
3 2.8027 ( 88.7) ******************
1 3.1367 ( 79.3) ****************
## Matrix real [01] [02] [03]
[01] 2 2.4863 100.0 112.7 126.2
[02] 3 2.8027 88.7 100.0 111.9
[03] 1 3.1367 79.3 89.3 100.0