あそびたい

こころにうつりゆくよしなしごとを

twitter4jとScalaでネトストしたい!!

好きな人のツイートは全部見たいって思うことありますよね。
ツイ消しも画像も含めて全部見たいって思いますよね。
ということで、ガチネトストプログラムを作りました。

仕様

  • ツイートは全てtxtファイルに保存
  • 画像もローカルに保存
  • 複数のアカウントに対応

以上の仕様に対応するように作成します。
今回はScala 2.11.8を使用しようと思います。

ディレクトリ構成

sbtを使っていくので、ディレクトリ構成は以下のようになります

/
|- src/
   |- main/
      |- scala/
         |- main.scala
         |- connect.scala
         |- stream.scala
|- files/
|- build.sbt
|- twitter4j.properties

build.sbt

build.sbtに必要なライブラリの依存関係を書いていきます。
とはいえ、twitter4j以外使わないので以下のようになります。

lazy val root = (project in file(".")).
  settings(
    inThisBuild(List(
      scalaVersion := "2.11.8",
      version      := "0.1.0-SNAPSHOT"
    )),
    name := "stalker",
    libraryDependencies ++= Seq(
      "org.twitter4j" % "twitter4j-core" % "4.0.4"
        , "org.twitter4j" % "twitter4j-stream" % "4.0.4"
    )
  )

twitter4j.properties

次に、twitter4jの設定を書いていきます。

debug=true
oauth.consumerKey=YOUR_CONSUMERKEY
oauth.consumerSecret=YOUR_CONSUMERSECRET
oauth.accessToken=YOUR_ACCESSTOKEN
oauth.accessTokenSecret=YOUR_ACCESSTOKENSECRET
twitter4j.loggerFactory=twitter4j.NullLoggerFactory

自分のconsumerKeyその他は頑張って探してください。
普通にググれば取り方が出て来るはずなので。

本体

コードそのものを書いていきます。

connect.scala

まずは、screen name(@以下の部分)からID(アカウントに固有の数字)を求めましょう。
そうすることで、@以下を変えられたとしても継続してストーキングすることができるようになります。

package connect

import twitter4j._

object TwitterConnector{
  val factory = new TwitterFactory()
  val twitter = factory.getInstance()
  def getId(names : List[String]) :List[Long] ={
    return names.map(x => twitter.showUser(x).getId())
  }
}

これで終わりです。ライブラリって便利ですね。
StringのListの形で引数を与えると、Id(Long)のListの形で返って来るようになっています。
本当はError処理とかするべきなんですが、面倒なので割愛します。

streaming.scala

次はstreamingでツイートを取得しましょう。
好きな人が呟いた瞬間にツイートを取って来ることができるようになります。

package stream

import java.io._
import java.net.URL
import twitter4j._
import scala.sys.process._
import scala.language.postfixOps


class StalkerListener extends StatusListener{
  override def onDeletionNotice(statusDeletionNotice :StatusDeletionNotice) ={
    // ツイートが削除された時に発動します
    // 今回は無視
  }

  override def onScrubGeo(userId :Long, upToStatusId :Long) ={
    // 今回は無視
  }

  override def onStatus(status :Status) ={
    // ツイートされた時に発動します
    val user = status.getUser()
    val file = new File(new File(".").getCanonicalPath, s"files/${user.getId}.txt")
    s"echo ${status.getText()}" #>> file !

    val medias = status.getMediaEntities().map(x => x.getMediaURL()).toList
    medias.zipWithIndex.foreach{case(x:String, i:Int) =>
      val stream = new URL(x).openStream
      val buf = Stream.continually(stream.read).takeWhile( -1 != ).map(_.byteValue).toArray
      val nameOnly = x.drop(x.lastIndexOf('/'))
      val fileName = nameOnly.split('.').mkString(i.toString ++ ".")
      val dir = (new File(".").getCanonicalPath).toString ++ s"/files/${user.getId}/"
      s"mkdir -p ${dir}"!
      val imageFile = new File(dir, s"${fileName}")
      val bw = new BufferedOutputStream(new FileOutputStream(imageFile))
      stream.close()
      bw.write(buf)
      bw.close
    }
  }

  override def onTrackLimitationNotice(numberOfLimitedStatuses :Int) ={
    // 今回は無視します
  }

  override def onException(e :Exception) ={
    // 例外が起こった場合に通知されます
    // 今回はスタックトレースでも出しておきます
    e.printStackTrace();
  }

  override def onStallWarning(e: StallWarning) = {
    // 変わったらしい
  }
}

streaming APIを使うときにはListenerというものが必要になるので、それの定義を行なっています。
監視しているstreamにツイートがされたときに、そのツイート引数としてonStatus関数が呼ばれます。
そのツイート主のIDを取ったtxtファイルを作成し、その中にツイートの内容をリダイレクトすることで書き込んでいます。
JavaのFileとかを使って追記しても良かったんですがめんどくさかったのでscala.sys.processを使って、リダイレクトして書き込んでいます。

後半部は画像ファイルを落として来る処理ですね。
動画が上がったときに関してはテストしていないので、どうなるのかわかりませんが、画像はいい感じに保存されるようになっています。

main.scala

さて、あとはstreamを監視するだけですね。

import java.io._

import twitter4j._
import scala.io._
import scala.sys.process._
import scala.language.postfixOps

import stream._
import connect._


object Main{
  def main(args: Array[String]) :Unit = {
    // val newName = ["hoge","fuga"]
    // val firstIds = TwitterConnector.getId(newName)

    val idFile = new File(new File(".").getCanonicalPath,"files/ids.txt")
    println(idFile)

    // firstIds.foreach{firstId =>
    //   s"echo ${firstId}" #>> idFile !
    // }

    val idSource = Source.fromFile(idFile.getPath())
    val ids = idSource.getLines.map{x => x.toLong}.toArray
    idSource.close

    val twitterStream : TwitterStream = new TwitterStreamFactory().getInstance();

    val listener = new StalkerListener()
    twitterStream.addListener(listener)
    val fq = new FilterQuery(ids: _*)
    println(ids.mkString(","))
    println("start Streaming")
    twitterStream.filter(fq)

  }
}

コメントアウトを全て外せば、newNameに含まれているscreenNameからIDを取得して、検索対象に含むようになります。
twitterStreamにはuser,filterなど複数あるのですが、今回はIDを指定してツイートを取得したいので、filterを用いています。
詳しくは公式のドキュメントを。

使い方

これで完成です。
あとはrootディレクトリにて
sbt run
しておくだけで、files/(id).txtにツイートが収集できます。
画像はfiles/images/(id)/以下に収集されます。
ただし、仕様上filterだと非鍵垢のツイートしか取得できないので、鍵垢に対してはuser streamを用いた上でonStatusではじくようにしたら良いのではないでしょうか。

最後に

これを用いた際に生じる不都合については一切責任は追いませんので、ご容赦ください。

それでは、楽しいネトストライフを!

Emacs+sbt+ensimeでScalaの環境を整えた話

今期の実験でScalaを使おうと思い、まず手始めに環境を整えたので、はまったポイントをまとめようかと。
環境は、

です。

コンパイラを入れよう

Scalaのビルドツールとしてメジャーなものの1つであるsbtをインストールします。
幸いbrewに登録されているので、

brew install sbt
sbt

でインストールできます。が、めちゃめちゃ時間がかかります。
最初にライブラリとか落としてくるからっぽいです。

Emacsの環境を整えよう

Scalaのモードはデフォルトでは入っていない(?)ようなので、入れます。
M-x list-packages で scala-mode を入れましょう。

ググるscala-mode2とか出てきますが、つい最近scala-modeに変わったっぽいです。ややこしい。

IDEを入れよう

ScalaIDEとしてensimeなるものがあり、これがsbtとEmacsに対応しているとのことだったので導入しましょう。

まずはsbt側から

~/.sbt/0.13/plugins/plugins.sbtを作成し、そこに

if (sys.props("java.version").startsWith("1.6"))
  addSbtPlugin("org.ensime" % "sbt-ensime" % "1.0.0")
else
  addSbtPlugin("org.ensime" % "sbt-ensime" % "1.9.1")

を書き足します。以上です。

次にEmacs側を

毎度おなじみM-x list-packages でensimeを入れます。

これだけで一応連携自体はできるのですが、どうせやったらIDEらしく
.を入力したらauto-completeが起動したりとか
カーソルを合わせたところの型がみれたりとか
できたらいいなあと思ったので探してみたら、やってらっしゃる方
がいらっしゃったので、コードをコピペしたものの、バージョンの壁に阻まれ、若干妥協することになりました……

最終的なinit.elは以下の通りになりました。

;; ;; Scala-mode *****************************************************************
(require 'scala-mode)

;;; Use auto-complete for ensime
(setq ensime-completion-style 'auto-complete)

(defun scala/enable-eldoc ()
  "Show error message or type name at point by Eldoc."
  (setq-local eldoc-documentation-function
              #'(lambda ()
                  (when (ensime-connected-p)
                    (let ((err (ensime-print-errors-at-point))) err))))
  (eldoc-mode +1))

(defun scala/completing-dot-company ()
  (cond (company-backend
         (company-complete-selection)
         (scala/completing-dot))
        (t
         (insert ".")
         (company-complete))))

(defun scala/completing-dot-ac ()
  (insert ".")
  (ac-trigger-key-command t))


;; look type at point by C-t
(bind-key "C-t" `ensime-type-at-point scala-mode-map)

;; Interactive commands

(defun scala/completing-dot ()
  "Insert a period and show company completions."
  (interactive "*")
  (eval-and-compile (require 'ensime))
  (eval-and-compile (require 's))
  (when (s-matches? (rx (+ (not space)))
                    (buffer-substring (line-beginning-position) (point)))
    (delete-horizontal-space t))
  (cond ((not (and (ensime-connected-p) ensime-completion-style))
         (insert "."))
        ((eq ensime-completion-style 'company)
         (scala/completing-dot-company))
        ((eq ensime-completion-style 'auto-complete)
         (scala/completing-dot-ac))))

;; Initialization
(setq ensime-startup-snapshot-notification nil)

(add-hook 'ensime-mode-hook #'scala/enable-eldoc)
(add-hook 'scala-mode-hook 'ensime-scala-mode-hook)
(add-hook 'scala-mode-hook 'flycheck-mode)

動かしてみよう!

test.scalaを作って意気揚々とM-x ensime としたところ……

byte-code: check that sbt is on your PATH and see the Troubleshooting Guide for further steps http://ensime.org/editors/emacs/troubleshooting/ [(wrong-type-argument stringp nil)]

なんかつらそうなエラーが出てきました……

とりあえず必死になって英語のドキュメントを読んだところ、.ensimeとかいうファイルがないとダメだそうで、それ自体はsbtを使って作れるそうなので作ってみます

sbt ensimeConfig

で、再度M-x ensimeとすると……

ENSIME ready. Let the hacking commence!

成功です!
なんかいい感じに下線引いてくれたり型を出してくれたりするようになりました。

まとめ

多分こんな流れだったとは思うのですが、なにぶん環境が完成してから書き始めたため、他に詰まったところがなかっただろうか……という感じです。
あと、デバッガはうまく連携できなかったので、できた方がいらっしゃったら教えて下さい。

ではではっ

EmacsでSmalltalkの環境を整えようとした話

この夏に純粋オブジェクト指向Smalltalkを勉強しようと思い、環境を整えていたのですが、様々にはまるポイントがあったのでまとめようと思った次第です。
まず

何をいれたらいいのかわかりにくい

処理系がいっぱいあるっぽくて、どれを入れたらいいのかわからなかったです。
とりあえずEmacsと連携できそうなやつ、ということでSmalltalk emacsでググって出てきたgnu-smalltalkとやらを入れてみました。
一応こいつ自体はbrewで入れられたのですが

付属のEmacs lispの使い方がわからない

付属でsmalltalk-mode.elとかgst-mode.elとかがついて来るんですが、まず使い方がいまいちわからんと。
で、ググってみて出てきたelispのコードをinit.elに書いてみたんですが、動かず
small-talk-mode.elの中身見てみたらコメントでこんなことが書いてありました

;;; Incorporates Frank Caggiano's changes for Emacs 19.
;;; Updates and changes for Emacs 20 and 21 by David Forster

いや、Emacs21とか旧石器時代かよ。そら動かんわ。

ということで、こいつを動かす術を探す旅に出たのですが

調べれど調べれど何も出てこない

Qiitaとかはてなブログとかにまとめがないのは覚悟していたのですが、stack over flowとかですらみつからず途方に暮れていた時にUbuntu向けのパッケージを圧縮したファイルを見つけ、最終更新が2016年だったのでとりあえず落としてみて中身を見ると、
smalltalk-mode-init.el.in
とかいう名前のファイルがあったので、これは!と思いinit.elにコピペしてみると

変数がなくて怒られる

Emacsあるあるですね。24で削除されたinhibit-first-line-modes-regexpsって変数を書き換えようとして怒られてました。その部分をinhibit-local-variables-regexpsとすると

ついに動きました!

C-c mでgstというインタープリタ(と思われるもの)が動きました!
長い道のりでした……ということで結論。

結論:マイナー言語はめんどくさい

この一言に尽きると思います。メジャーな言語ならこんなわたわたしなくていいですからね。
とはいえこのいじってる時間も楽しかったりするのでいいんですが。
これからごりごりsmall-talkを書いていきます!
最後にinit.elに書いたものだけ書いておきます。

(push (cons "\\.star\\'"
	    (catch 'archive-mode
	      (dolist (mode-assoc auto-mode-alist 'archive-mode)
		(and (string-match (car mode-assoc) "Starfile.zip")
		     (functionp (cdr mode-assoc))
		     (throw 'archive-mode (cdr mode-assoc))))))
      auto-mode-alist)

(autoload 'smalltalk-mode "smalltalk-mode.elc" "" t)
(autoload 'gst "gst-mode.elc" "" t)

(push '("\\.st\\'" . smalltalk-mode) auto-mode-alist)

(push "\\.star\\'" inhibit-local-variables-regexps)

ではではっ

org-modeのTableで縦線を入れる方法

org-modeのTableで縦線を入れる方法

こんにちは。
突然ですがみなさんorg-modeのtableで縦線を入れたくて困った経験はないでしょうか。
横線であれば、

|---+---+---+---+---|

こういうやつで入るのに、縦に線を入れようとしても、どうやっても入らない。
で、諦めてM-x org-table-convertしてtable-modeで無理やり入れていたのですが、編集するのも面倒になるのでできれば避けたい。
ということで頑張って探してみたところ、見つけました。

http://orgmode.org/manual/Column-groups.html#Column-groups

column-groupsって書かれても気付かんわ……

とりあえず、ものは試し。書いてみると……

|---+-------+-------+---------+---------+---------+-----------------|
|   | Name  | Hair  | Height  | Weight  | English | Result          |
|---+-------+-------+---------+---------+---------+-----------------|
| / | <>    | <     |         |         | >       | <>              |
|---+-------+-------+---------+---------+---------+-----------------|
|   | Emi   | brown | average | heavy   | yes     | foreign student |
|   | Mario | brown | short   | average | no      | Japanese        |
|   | Jun   | black | tall    | average | no      | Japanese        |
|   | Lisa  | brown | tall    | average | yes     | foreign student |
|   | Jo    | red   | average | light   | yes     | foreign student |
|   | Mie   | black | short   | light   | yes     | Japanese        |
|   | Ran   | black | average | light   | yes     | Japanese        |
|   | Mai   | brown | tall    | heavy   | no      | Japanese        |
|---+-------+-------+---------+---------+---------+-----------------|

f:id:yossiyossy:20160724095548p:plain

できました!<>の間が1つのグループとしてみなされるため、その両側で縦線を入れる、という感じでしょうか。
いろいろ探してみてやり方が見つからず、全然違うことを調べていた時に見つかる、という感じだったので、誰か同じように困っている方がいらっしゃったら、と思い記事にしました。
これで、より一層org-modeの使い勝手が上がりますね!

ではでは!

すでにネタがない

すでにネタがない

こんにちは。

タイトルの通りです。ブログを初めて二記事(しかも片方はただの自己紹介)で書くことがなくなりました。
よくよく考えれば、Emacsなんて人の設定をコピペして使ってるだけだし、いろんな言語の深い話かけるほど詳しくないし、プログラミングの話を毎日毎日できるわけがないんですよね。
だから、全然違う話をします。


三回生ですので進路に悩むお年頃です。
理系だから院に行くと思われがちなのですが、就職も少し考えています。
ので、夏期インターンシップを一社だけですが申し込み、明日その面接が控えております。
その対策として今まで自分がしてきたこととか振り返っているのですが、

とても辛い

自分が頑張ったと思ってたことを客観的に見て評価する必要が出てきて、あれ……?俺実は大したことしてない……?って気持ちになります。辛いです。
就活をしてらっしゃる方々は毎度毎度こんなことをしているのですね。そりゃ心の一つや二つ折れますね。
とはいえ面接もしていないのに心を折っている場合ではありません。持ち前の言語能力でそれらしいことをつらつらと語ってきます。
そう、それらしい日本語を並べるのには自身があります。半期でレポートを2万字分書いて10単位も錬成したことのある男です。
そうやって自分を鼓舞しているのですが、そのことすらも大したことじゃないんじゃないかという気になってきます。本当に辛いです。

明日の面接が終わればつらいものは実験と試験だけになるので少しは気が楽になります(実験も実験で憂鬱ですが)。
頑張ってきます。

ではでは。