空談録

世界で5人くらいに役立ちたい

PowerBIでお小遣い管理をしよう

月一更新…なんのことかな…?

Office 365を契約しているとPowerBIもなんだかそこそこ使える雰囲気があるのですが、まったく使わないのももったいないと思うわけです。
というわけでBIっぽく自分のお金の状態をPowerBIに載せて分析してみましょう。

なおPowerBIの使い方はよくわかってないので、すごいざっくりとした使い方のみです。

とりあえずデータを作ろう

まずはPowerBIに載せるデータを用意しましょう。
今回はExcelを使います。というかExcelじゃないとつらい。

生のデータを直接出すとなんかよろしくない感じがあるので、ダミーデータをサクサクと生成します。
データの要素としては、
・日付
・消費金額
・累積値
・(カテゴリ)
・(項目名)
とします。()のものは必須ではないです。

おおむねこんな感じのテーブルになります。
PowerBIに載せるためにテーブルにしとく必要があるのでそこだけ注意です。

f:id:fantasticswallow:20170709110330p:plain

支出は+、収入は-で管理します。
Currently Costのところは '=E6 - D7'みたいな式で計算。
Categoryは好きにお使いください(入力が面倒なのでA,B,C...みたいな振り方している)

データについては次のような条件を基に生成しました
・30日に200000くらいお金を手に入れる
・大体毎日ごはんに =INT(RAND() * 1000 + 500)くらいの出費をする
・毎月1日にOffice 365に900円、携帯代として8000円はらう
・4/1に交通費として70000払い、以後は5000円ずつ交通費として使用
・大体3週間おきに =INT(RAND() * 2000 + 2500)くらいの本を買う
・10日おきに2900円払うと無料で10連を回すやつをやる
・月末金曜日に1,2本何か買う
・あとたまに適当な出費

みたいな感じです。私ではありません

生成し終わったらテーブルを日付順にソートします。これでひとまずテーブルは完成です。

日付ごとの残金を作成する

上のやつだけでも行ける気がしますが、日ごとの推移を追うにはちょっと不便なのでもう一つテーブルを作成します。
というのもすべての日にデータを入力してるわけではないのと、複数行に同じ日付がはいるケースがあるため、集計が面倒だからです
なので日付と残金だけ入れたテーブルを作成しましょう。
こっちはPower Queryで簡単に。

Advanced Editorで見るとこんな感じです。

let
    Source = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
    #"Changed Type" = Table.TransformColumnTypes(Source,{{"Category", type text}, {"Date", type datetime}, {"Name", type text}, {"Price", Int64.Type}, {"Currently Costs", Int64.Type}}),
    Custom1 = Table.FromColumns({List.DateTimes(#"Changed Type"{0}[Date],Duration.Days(DateTime.LocalNow() - #"Changed Type"{0}[Date]) + 1, #duration(1,0,0,0))}),
    Custom2 = Table.AddColumn (Custom1, "test", (x) => Table.Last(Table.SelectRows(#"Changed Type", each x[Column1] = [Date]))[Currently Costs]),
    Custom3 = Table.FillDown(Table.ReplaceErrorValues(Custom2, {"test", null}), {"test"}),
    #"Changed Type1" = Table.TransformColumnTypes(Custom3,{{"Column1", type date}})
in
    #"Changed Type1"

Changed Typeまでは自動生成なので(Excelのテーブルから作成するとそこまで入る)、下3行について説明します。
Custom1はテーブルの最初の日付から、現在の日付まで1日おきのリストを作成します。
DateTimesの第2引数にはリストの項目数を指定するのでDuration.Daysで差分の日数を入れている感じです。

Custom2は残金の列を作成しています。特定の日付の中でも最後のデータをその日の最終残金として取得して列を生成します。

f:id:fantasticswallow:20170709113501p:plain

上のような感じで、日付と対応する金額が入るのですが、支出が記録されていない日についてはErrorとなります。
ここは前の日のデータを引き継いでほしいので、次のCustom3で対応します。

Custom3では、Errorをいったんnullに置き換え、その後FillDownでnullを一つ上の行のデータで埋めるようにしています。
これで連続したデータの完成です。

最後にDateTimeになっているのをDateにしてExcelに吐き出して完了です。

PowerBIで読み込もう

というわけでExcelのデータが完成しました。
こだわる場合はもっといれるといいと思いますが、まあ今回はこれで。

作成したExcelファイルはOneDrive上に保存します。
(どこでもいいですが、OneDriveとかに載せると自動更新がかけやすいです)

ここまでやったらようやくPowerBIを開きましょう。
Power BI | 対話型のデータ視覚化 BI ツール あたりからサインインします。

PowerBIの左下の「データの取得」から「ファイル」っぽいところの「取得」を押します

f:id:fantasticswallow:20170709115038p:plain

そしたらどこのファイルを参照するか聞かれるので、先ほど保存したファイルの保存先を選択します。
私の場合はOneDrive - 個人用となります。(適宜選んでください)

接続し終わるとファイル一覧が表示されるので、先ほど作成したExcelファイルを選択します。

f:id:fantasticswallow:20170709115344p:plain

選択するとまっさらな……なんて呼ぶのかわかりませんがシートっぽいものが表示されると思います。
表示されたらデータを選択していきます。
ひとまず残金グラフを作りましょう。

f:id:fantasticswallow:20170709115932p:plain

フィールドからテーブルQuery1のColumn1とtestを選択し(名前は適宜合わせてください)、グラフの種類を折れ線にすると上のような感じでグラフが出ます。
これだけだと手元のExcelで出すのと一緒じゃん!ってなるので予測値を出してみましょう。

f:id:fantasticswallow:20170709144330p:plain

右の視覚化ペインの中のタブを「分析」に切り替え、「予測」のグラフを追加します。
「予測の長さ」には予測したいデータ数を入力します。今回は60日分予測するために60ポイントとしています。
「信頼区間」は指定した確率でこの辺通るだろみたいな補助線を算出するのに使用されます。確率が高いほど上限値、下限値は広く取られます(全部取れば再現率は100%みたいなあれ)。95%とかでいい気はします。
「季節性」は一定の周期でグラフが変動している場合に使用します。今回のケースであればおおよそ30日置きに給料が増えてるので30とか指定します。(自動で勝手に計算してくれるので任せてもいいです。)
(内部的な予測アルゴリズムはSeasonal ETSだと思います。Excelの関数と一緒っぽいのでExcelで予測しても同じようになるはず)

なるほどこれで予測値が見れる!のはいいんですけど、今のところExcelでよくね感を避けれません。
しかしPowerBIだと、Excelでグラフを管理するのと違い、自動で更新してくれるというメリットがあります。
特に予測については自動で範囲を変えるのは結構だるいのでありがたいです。
なので自動更新の設定をしましょう。

いったんレポートを保存してレポート編集画面を抜けます。
「マイ ワークスペース」の「データセット」を選んで「更新のスケジュール設定」を選びます。
f:id:fantasticswallow:20170709151138p:plain

すると設定画面に飛ぶので、「OneDriveの資格情報」を開いて、「資格情報を編集」を押します。
なんか意味深な「~~の構成」ってダイアログが出るので「サインイン」を押しましょう。

f:id:fantasticswallow:20170709151404p:plain

サインインすると一瞬でリダイレクトされますが、これでひとまず自動更新はできるようになります。
(この操作してないとデータセットの更新ができなくなる?雰囲気がある。)

これでちまちまと最初のExcelを編集し、気になったらPowerBIを見ることで最近やばい!とかが見られるようになりました。いえーい。
グラフ一個で寂しい場合はいろいろ追加するとよいと思います。

f:id:fantasticswallow:20170709152340p:plain

特に意味のないドーナツグラフ。


というわけでいろいろ自動化しながらデータを眺めるって話でした。
たまに眺めてはまだだ…まだいける…!とかいっています。

最近はおしごとやだぁ…みたいな状態です。いつまで続くんだろう…。
しかしおかねほしい。

この辺で。

workbench.colorCustomizationsを用いた痛Visual Studio Code化

コードを書きたい、気力がない…。
創造力の減少を感じる。

かなり昔にTextMate Themeを用いてVisual Studio Codeを痛くしたものがあったと思うのですが、Visual Studio Codeが1.12になったタイミングで大きく変わったのでその対応です。
artfulplace.hatenablog.com

1.12の変更についてはリリースノートでどうぞ。
code.visualstudio.com

workbench.colorCustomizationsで設定から色を設定可能になったのはいいんですが、その結果としてまた変える対象が変わったのでぐぬぬという感じです。

注意:いつも通り壊れてるとか出たりサポート対象外になりますがそれでもよい方向けです。
公式にサポートしてほしい…。
また基本的にはlightテーマで行っています。darkなテーマでそのまま使うとおかしくなる可能性は否定できないのでご了承ください。

目指す状態

f:id:fantasticswallow:20170520215450j:plain
大体こんな感じになるようにしましょう。

cssを書き換えよう

いつも通りCSSを書き換えます。
とはいえかなり減ってきた感じです

.monaco-workbench .part.editor {
	 background: url("vsc-bg.png") no-repeat;
	 background-size: cover;
}
.monaco-editor, .monaco-editor .inputarea {
        background: none;
}
.monaco-editor.vs-dark {
        background: none;
}

まずは画像を置くのは必須です。
あとはいわゆる基本テーマの背景色を削除する形になります。
他の部分については配色テーマの置き換えで消せるのですが、基本テーマ部分(vs, vs-darkなどのuiThemeが適用される部分) については配色テーマでは操作ができないのでこの部分だけCSSで消します。

vs-darkについてはあとからCSSが指定される形になるので(light < darkという順で、vs-dark適用時もvs-lightなテーマは存在する)、無理やり消します。
ハイコントラストも同様だと思いますが探すのが面倒だった…。

残りはworkbench.colorCustomizationsで対応します。

colorCustomizationsでいじろう

というわけで画像が後ろに出てくるようにしたところで、新機能であるworkbench.colorCustomizationsをいじっていきます。
これは現在のテーマの色よりも優先される設定となります。なのでAbyssにしてるので変わらないなどなどはないわけです。

またテーマの一部だけいじりたい、という場合テーマを直接いじるのは割と大変ですしプレビューも面倒です。
そんなときにworkbench.colorCustomizationsが役立ちます。ユーザー設定保存するだけで反映されるので見た目どうかを試すのにも便利です。
微妙だったら消せばいいわけですし。

どこが設定できるかはTheme Color Referenceを見ていただくとして本題にいきましょう。
code.visualstudio.com

エディタ部分の背景色を透過させればいいので次の二つを変更します。
・editor.background
・editorGroup.background

editor.backgroundは割と範囲が広いのですが(workbench.part.editor、one-editor-silo、monaco-editor-background)、さすがにこれいじらないとつらいのでいじってしまいます。
変になる可能性も高いですが手間も増えるので難しい。

editorGroup.backgroundはworkbench.part.editor直下のcontentに該当します。これだけなので影響自体はそこまで大きくないです。

それぞれを変更したときの挙動を確認しておきます。
次の画像は左が editor.background: #FF00DD44、右が editorGroup.background: #FF00DD66 です。(それぞれもう片方は#FFFFFF00で透過させています。)

f:id:fantasticswallow:20170521114143p:plain

こうしてみると左側のほうが何層にも重なっているため右よりも濃くなっています。
なのでそのあたりを意識しつつ透明度を設定します。

workbench.colorCustomizationsでいじるときにカラーコードを8文字入れるとrgbaで反映されるのは確認済み(少なくとも今回の二つは)なので、最初の画像のような見た目であれば
・editor.background: "#EFEFEF66"
・editorGroup.background: "#FFFFFF77"
とかやると画像みたいな感じにはなります。

darkなテーマとかの場合はそれに合わせて色を変更するとよいと思います(さすがにdarkテーマでの検証まではしてない)


というわけで便利な機能が増えてましたという話でした。
issueにたびたび背景画像設定できるようにみたいなのは流れてますがどうなったのかはよくわからないのでなんとも。
しかしどういう挙動にするかもわからないので難しい…。

コードを書こうとVisual Studioを開いても何もする気がおきないので何かこうやる気を取り戻したい…。
触らない期間があるとどうしてもやる気がどっかとんで行ってしまう。ぐぬぬ

この辺で

東方天空璋 体験版用のMS-IME向けテキスト辞書

いーつーもーのー
今回は動画がすぐに上がってきたからすぐに完成したぜ。

ということで東方天空璋の体験版のスペルカード名とか曲名とかキャラ名が今すぐ変換したい方向けのやつです。
最近はGoogle 日本語入力とか流行ってる気がするし、こういう辞書ファイルの需要があるのかよくわからない…。
といいつつ私はMS-IMEをずっと使っているわけですが。


ダウンロードは下のDropBoxのリンク開いて右上にあるダウンロードからどうぞ。
ネタバレ対策はないのでネタバレ怖いという方は開かないでください。
www.dropbox.com

利用方法はIMEのオプションの「ユーザー辞書ツール」から「ツール」メニュー →「テキストファイルから登録」 でどうぞ。
よくわからなければググってください。きっとわかりやすい説明がいくらでも見つかります。

ちなみに今回もgistでバージョンを管理しています。だいぶ雑に管理しています。
https://gist.github.com/fantasticswallow/e7f1c574b46a7c79ccc9f18180547a1c
ぼくのかんがえたさいきょうのじしょふぁいる とかミスを直したいとかいろいろ勝手にどうぞ。forkとかはご自由に。

利用する上での注意点は次の通りです。
・利用は自己責任でお願いします。変な変換が紙に出たとかは責任取れません。
・編集はご自由にどうぞ。ミス修正とかもご自由に
DropBoxで公開しているファイルと同じファイルの再配布は不許可。
・辞書への登録部分で何らかの変更点があるものを「配布者の名のもとに」配布するのは構いません。

以上です。
こんなん再配布するやつおらんやろ~って気もしますがまあ一応…。


そんなわけでいつものテンプレ記事でした。
製品版は買いに行きたい…でも暑そう……。

ゲームをあまりしていないので製品版に向けて東方も練習していかないとなぁと毎回変わらない感想で終わります。
いやでも音ゲー最近したらめっちゃきつかったしだいぶ衰えてしまったのでは…。

この辺で

OneNoteのページをC#のコードでコピーする

私は知っている…誰も書いてない = 需要がないことをなぁ…!

というわけで、OneNoteのページを移動させるのがめんどくさいのでなんかアプリケーション作ってコピーしようと思ったのですが、OneNoteのApplicationClassにはCopyPageContentみたいな気の利くメソッドがありません。どういうことだ。

ApplicationClassをオブジェクトブラウザで見てるとこんなメソッドがあることはわかります。
・GetPageContent(string pageId)
・UpdatePageContent(string pageXml)
・DeletePageContent(string pageId, string objectID)
…なんかDeletePageContentは違う気がする(DeleteHierarchyの気がしてきた)。

まあ確かに世の中取得と追加ができればコピーはできるでしょう。コピーして元のページを消せば移動もOK!
なんとありがたい!!今すぐ悔い改めてほしい!!!

とりあえずページのコピーができれば移動も達成できるのでコピーの仕方について書いていきます。

コピーしたXMLのdiffを確認しよう

Office界では同じ処理がすでにある場合、既存の処理によって得られる結果を確認するというのは定石ですね。どうかしてるぜ!
Visual Studio CodeのCompareで比較してみます。

f:id:fantasticswallow:20170326111707p:plain

まあ雰囲気だけ感じ取ってもらえばいいのですが、変化しているのは
・Page.@ID
・OE.@objectID
・CallbackID.@callbackID
の3つのIDです。

PageのIDは存在するページのIDを指定する必要があります。このIDを基にUpdatePageContentはページの書き換えを行います。そのためコピー先のページIDに書き換えます。ここはReplace(元のID, 新しいページのID)で終わるので楽です。
OEのobjectIDはOE要素を追加した際に自動で振られるIDです。競合してはいけないため、IDを残してしまうとUpdatePageContentに失敗します。しかし特にこちら側で割り当てる必要もないので(勝手に追加される)、すべてのobjectIDを消せばこちらはOKです。
CallbackIDも同じく勝手に振られるのですが、画像のデータについてはこちら側で指定する必要があります。最も面倒な作業。

まあ実際やっていきましょう。

続きを読む

VS拡張でAdornmentLayerを下に置きたいとき

ラボ畜から解放され社畜への一歩を着々とウワーッ。

先週あたりにVisual Studio 2017のRTMがリリースされましたね。しかもRC入れてたら更新でRTMになってて驚き。
なんだかいろいろ便利機能が増えてるらしいのでちまちまと試していきたいですね。

ところで今や痛VS拡張はこれという何かが分からないので、自分で作っていれるのが主流なのか、それとも私が知らないだけでどこかでまだ作られてるのか知りませんが、最近は自分で作ったのを使っています。
最近はVSIXもかなり楽に作れるので初めてのVSIXに痛背景をおすすめしまs


しかし痛背景を追加しようと思ったとき、AdornmentLayerが前面にくると結構面倒なので後ろ側に回しましょう。

追記(3/16):@kosmosebiさんにViewportAdornemntTextViewCreationListenerのOrderで変更できるという情報をもらったので過去の文を後ろに回して書き直しました。
PredefinedAdornmentLayersが持ってる定数を基にいじれるらしいです。感謝しかない…
PredefinedAdornmentLayers の説明が不十分 | ブチザッキ


ViewportAdornmentを追加したときにすでに書かれているサンプルをもとにImageを追加するようにしたのが次のコードです。

public ViewportAdornment1(IWpfTextView view)
{
    this.view = view ?? throw new ArgumentNullException("view");

    var opacity = 0.4;
    var file = @"";

    var imgSrc = new BitmapImage(new Uri(file));
    this.image = new Image
    {
        Source = imgSrc,
        Stretch = Stretch.UniformToFill,
    };
 
    this.adornmentLayer = view.GetAdornmentLayer("ViewportAdornment1");

    image.Opacity = opacity;

    this.view.ViewportHeightChanged += this.OnSizeChanged;
    this.view.ViewportWidthChanged += this.OnSizeChanged;
    view.Closed += (o, e) => this.adornmentLayer.RemoveAdornmentsByTag("vsbg-adornmentTag");
}

OnSizeChangedは今回は関係ないので載せてません。大体サンプル通りです。


これでAdornmentLayerを追加して、AddAdornmentとかするとわかるのですが、メソッド内で使用しているAdornmentLayerはかなり上の方の要素になっています。
(2017限定? 2015でもちょくちょくそんな現象あった気がするけど)
こうなると正直見づらいですし(画像が文字の前に来てしまう)、"定義をここに表示"みたいなコントロールを出す系の機能については追加したAdornmentLayerのコントロールにフォーカスを奪われてしまいます。痛くするのにここまでのデメリットをつけるのはつらいです。そのため、追加するAdornmentLayerを後ろの方に回しましょう。

(以下追記部分。過去の文は水平線以下)

ここでAdornmentLayerのZ-Orderを無理やり変更してたのが過去の文なわけですが、実際はViewportAdornemntを追加すると作成される"ViewportAdornmentTextViewCreationListener"のほうで変更ができます。

こんな感じのコードがデフォルトで追加されています。

/// <summary>
/// Defines the adornment layer for the scarlet adornment. This layer is ordered
/// after the selection layer in the Z-order
/// </summary>
[Export(typeof(AdornmentLayerDefinition))]
[Name("ViewportAdornment1")]
[Order(After = PredefinedAdornmentLayers.Caret)]
private AdornmentLayerDefinition editorAdornmentLayer;

(そういえばこんなの変更した記憶があるような…)

さて、このまま実行するとどこに追加されるかというと、通常のエディタ画面の場合、最前面に配置されます。
そもそも前面に置くときBeforeとAfterどっちだよという感じですが、「Before = Caretの場合、Caretのほうが前面に配置されること」を指定します。つまり背面に置きたい場合はBeforeを、前面に置きたい場合はAfterを指定します。

では今回はどうしましょう?というと最背面に置きたいので…どこだ?
ブチザッキ情報によるとDifferenceChangesを指定するとよいみたいですが、指定できるパラメータに若干知らない子がいるのでちょっと調べてみましょう。

これまたWpfTextView._baseLayer.Childrenに含まれるImplementation.AdormnemtLayer._nameを調べます。
するとこのような結果になります。

(最背面)
outlining
negativetextmarkerlayer
ViewportAdornment1 (After = DifferenceChanges)
BraceCompletion
CurrentLineHighlighter
ViewportAdornment1 (After = DifferenceSpace)
VsTextMarker
ViewportAdornment1 (After = DifferenceWordChanged)
TextMarker
SelectionAndProvisionHighlight
BlockStructure
Inter Line Adornment
RoslynLineSeparator
Squiggle
RoslynSuggestions
TextContentLayer
CaretElement
RoslynRenameDashboard
ViewportAdornment1 (After = Caret)
(最前面)

After = Caretを指定すると本当に前面に来ていることが分かります。
逆に最背面はoutliningです。どうもOutliningあたりから並びが謎い。
Difference~~を指定した場合、その時点で存在していなくても上のような位置に入ってきます。DifferenceChangesはかなり後ろに来ることが分かります。

RoslynRenameDashboardがViewportAdornmentに挟まれる理由としてはおそらくCaret, ViewportAdornment1がロードされた後にRoslynRenameDashboardがAfter Caretで読み込まれるためだと思われます。
そのためRoslynRenameDashboardを指定するのはつらそう(そもそも何のレイヤーかわからないけど)

ひとまず後ろの方に置きたい!って要望をかなえる場合これでいいかと。

/// <summary>
/// Defines the adornment layer for the scarlet adornment. This layer is ordered
/// after the selection layer in the Z-order
/// </summary>
[Export(typeof(AdornmentLayerDefinition))]
[Name("ViewportAdornment1")]
[Order(Before = PredefinedAdornmentLayers.DifferenceChanges)]
private AdornmentLayerDefinition editorAdornmentLayer;

outliningは指定したくないし…。
ちなみに結局stringで_nameを指定しているだけなので、Order(Before = "negativetextmarkerlayer")と入れても通ります。
何のレイヤーかわかりませんけど。そしてなぜかこうするとoutliningよりも背面になります。謎だ。


(ここから過去の文)

後ろに回すにあたって、まずWpfTextViewのAdornmentLayerの管理方法を確認しておきます。
WpfTextView._baseLayerがViewStackという型の値をもっており、このViewStackのChildrenに複数のAdornmentLayerが含まれています。
そしてViewStackは(たしか)Canvasを継承?親要素?してた…ような気がします。

正直実装周りはどうでもいいのですが、WPFCanvasなりの並び順を変更するプロパティとしては Panel.ZIndex 添付プロパティ があります。
今回はCanvasなりが絡んでいて、WPFなのでこれでいけそうですね。
ZIndexは添付プロパティなのでPanel.SetZIndexで指定しましょう。

指定するのはどこでもいいのですが、GetAdornmentLayerの後ろで指定してみるとこんな感じ。

this.adornmentLayer = view.GetAdornmentLayer("ViewportAdornment1");

Panel.SetZIndex((System.Windows.UIElement)adornmentLayer, -550);

SetZIndexの第1引数がUIElementである必要があります。IAdornmentLayerは当然UIElementと関係ありませんが、実際のImplementation.AdormnemtLayerはUIElementを継承しているのでこのキャストが通ります。
今回は後ろに回したいので第2引数は適当にマイナスの数値を入れています。こんくらいあれば大体後ろになるだろう計算。
数字はお好みで。



ひさびさにかいたわりによくわからない記事となってしまった…。
日本語が壊滅している…つらい…。

ようやく春の休みなのでちょこちょこプログラミングを思い出しつつブログも書きたい。

この辺で

Slack APIのスレッドの扱いについて

進捗…ガハッ…

Slackにスレッド機能が追加されたらしいですね。

slackhq.com

www.itmedia.co.jp

見てたらdevcussionのSlackには来てたのでAPIではどう扱うのかのさわり的なところを書いておきます。
Slack APIを触っている人間がどれだけいるのかは知りませんが…

スレッドのリプライメッセージについて

まず、Slackのスレッドは親となる通常メッセージにリプライメッセージを子としてつけていくことで実現しています。
この親メッセージとリプライメッセージが一つのスレッドとなります。

実際のデータを見ながら追っていきましょう。
(ただし内容については架空のものです)

まずチャンネルに流れるメッセージは通常次のような形となっています

{
    "type": "message",
    "user": "Uxxxxxxxx",
    "text": "ねむい",
    "ts": "99999999.000001"
}

このメッセージにスレッド機能をつかってリプライをつけてみます。
つけたリプライは次のようなデータとなります。

{
    "type": "message",
    "user": "Uyyyyyyyy",
    "text": "ビャーーーッ",
    "thread_ts": "99999999.000001",
    "parent_user_id": "Uxxxxxxxx",
    "ts": "99999999.000333"
}

このとき、スレッドとなった親メッセージは次のような値に変化しています。

{
    "type": "message",
    "user": "Uxxxxxxxx",
    "text": "ねむい",
    "thread_ts": "99999999.000001",
    "reply_count": 1,
    "replies": [
        {
            "user": "Uyyyyyyyy",
            "ts": "99999999.000333"
        }
    ],
    "subscribed": false,
    "ts": "99999999.000001"
}

このように基本的にはスレッドを構成するメッセージにはthread_tsフィールドが存在します。
同じthread_tsを持つメッセージをスレッドとして管理できればスレッド機能への対応は可能です。

…え? スレッドメッセージがリプライかどうか見分ける方法って? フィールドがあるかないかじゃないですかね…

スレッドメッセージの取得方法は?

これは別に対応する必要がなくて、普通にx.history系で取れます。x.repliesで取ってくる必要はないです。
またRealTime Messagingでも普通に流れてくるはずです。

さらに言えばスレッドメッセージはthread_tsで基本的には実現しているので、クライアントが対応していなければありがたいことに普通にチャンネルに流れてきます。
つまるところ、スレッドしてリプライメッセージをまとめるか、そのまま表示するか程度の差でしかないです。公式SlackはUIでスレッドとして別にメッセージがあるように見せていますが、実際は他のメッセージと同じ扱いです。

クライアントを作るのが大変そうって? わかる

スレッドにポストする方法は?

これは普通にスレッドの親メッセージのtsを"thread_ts"パラメータに入れるだけです。
リプライしたいメッセージにthread_tsがついててもついてなくても関係なく、chat.postMessageでthread_tsのパラメータを指定すれば勝手にスレッドのリプライとしてやってくれます。

ところで一つ注意点があります。(1/19時点) thread_tsに不正な値を入れてもchat.postMessageは通ります。tsフォーマットさえ守ればおそらく通るはずです。
これの問題点はリプライ扱いなので公式クライアントでは見れなくなります。というかなんだこれ。

ちなみにAPIレスポンスではちゃんと拾えています。
f:id:fantasticswallow:20170119203621p:plain

im.historyによるレスポンスと公式クライアントの見え方です。少なくともuuuuというメッセージは見えません。
そして世界のどこのSlackにもなさそうな"1484824875.990999"とかいうふざけたthread_tsですが通っています。
ウーーン

reply_broadcastとかいうやつ

殺意しか湧かないですね!

Slackのスレッド機能にはもう一つ機能があります。リプライメッセージをチャンネルにも通知するという機能です。
"Also send to {チャンネル名}"とかそういうチェックがリプライの所にあると思います。それですそれ。

さっきの画像の"Also send as direct message"もそうです。
これらはAPIではreply_broadcastとして扱われます。

この機能を使う、つまりチェックを入れて投稿するにはchat.postMessageの"reply_broadcast"パラメータをtrueにするだけです。
問題はやってくるレスポンスデータですよええ…

こんなんが来ます。

{
    "channel_id": "00000000",
    "channel_type": "D",
    "timestamp": "1484826106000008",
    "is_multiteam": false,
    "attachments": [
        {
            "from_url": "https:\/\/devcussion.slack.com\/archives",
            "fallback": "[January 19th, 2017 3:21 AM] fantasticswallow: \u3042\u3042\u3042\u3042",
            "ts": "1484824875.000002",
            "author_subname": "fantasticswallow",
            "channel_id": "D1A25G6E8",
            "channel_name": "Direct Message",
            "is_msg_unfurl": true,
            "text": "\u3042\u3042\u3042\u3042",
            "author_link": "https:\/\/devcussion.slack.com\/team\/fantasticswallow",
            "author_icon": "https:\/\/avatars.slack-edge.com\/2016-01-16\/18649887269_b1c2847c8a63b9a64a8c_48.png",
            "mrkdwn_in": [
                "text"
            ],
            "id": 1,
            "footer": "2 replies"
        },
        {
            "fallback": "Foo\uff01",
            "author_subname": "fantasticswallow",
            "text": "Foo\uff01",
            "mrkdwn_in": [
                "text"
            ],
            "author_link": "https:\/\/devcussion.slack.com\/team\/fantasticswallow",
            "from_url": "https:\/\/devcussion.slack.com\/archives",
            "ts": "1484826106.000008",
            "author_icon": "https:\/\/avatars.slack-edge.com\/2016-01-16\/18649887269_b1c2847c8a63b9a64a8c_48.png",
            "id": 2
        }
    ],
    "text": "",
    "type": "message",
    "subtype": "reply_broadcast",
    "user": "U0JK7N9D5",
    "ts": "1484826106.000010"
}

ウウウーーーーン……なんだこれ…
なんかもう頑張って対応…って無理じゃね…

RealTime Messagingにおけるスレッドイベント

最後にRTMでもらえるスレッド周りのイベントを紹介しましょう。
リプライメッセージそのものは普通にメッセージと同じでやってくるので特別に用意されていません。

まずmessage_repliedっていうのがあります。
https://api.slack.com/events/message/message_replied
これは簡単で、「すでにあるメッセージがスレッドになったぜ!」って通知をしてくれます。それだけです。

次にreply_broadcastがあります
https://api.slack.com/events/message/reply_broadcast
上のreply_broadcastがポストされたら飛んできます。



というわけでSlack APIのスレッド周りのお話でした。
reply_broadcastに殺意が湧くのとUIどうしろと感が半端ないこと以外は平和でしたね!

基本的にスレッドも普通のメッセージと同じフィールドを持っているのでスレッドガン無視も一つの手かなぁとは思います。
それはそれでどうなのよという感じですが、かといって対応しようにもそう簡単に実装できるものでもない気が…

この辺で

2016年を振り返るらしい

どちらかというと明日が冬コミ3日目という情報の方が信じられない…

毎年目標もなく気が向いたらブログを書くだけの人間ですが、振り返りは書いておきましょう。

プヨグラミング

思った以上に何も進歩がなかった…
最近こそNereidでひさびさにおうちプログラミングの機会は増えましたが、7月から11月とかマジでVisual Studioを起動した記憶がない…

それもこれもラボ畜がわるい。わたしはらぼちくにくるうけもの…。

来年はもう少し書きたい、せめてNereidは完成させたい。せめてSlackアプリはつくりおえ…ウッ

ゲーム

気が付いたらSteamが入っていた。

地球防衛して大復活の5面で焼かれていました。大復活難しい…。
地球防衛については割と頑張りましたが武器コンプリートをする前に心が折れてしまった悲しみ。マジで後半は天の兵団と竜の宴ループでしかなかったしなぁ。グングニルが早めに出たのでまだなんとかなったのですがとはいえやはり作業量がつらい。
なおフェンサーは最後まで使いこなすことはできませんでした。エアレイダーのリムペットガンで戦ってる方が戦力になる程度には才能が足りない。

そさげ

ぱどどらは今年は耐えきりましたが、ティターニアの8コンボ教室が突破できなくて残っていたモチベーション?使命感?みたいなのは死にました。
もう無理…転生ウズメでたら起こしてください…。いや起こさなくていいや…。
転生ミネルヴァでやるのは割と楽しかったのですが、なんか完全に時代に置いていかれた感じがあるのでもう無理かなぁという。

ゴ魔乙はギルドバトルで急速にやる気が墜落した気がします。
ギルドへの参加をやめて適当にプレイしてた頃のモチベーションを取り戻した方がいい気がする。
それはそうとしてソードが楽しいですね。使いこなせないけど。

10月くらいから新しくシャドウバーーーースを始めました。
私ふぁぼツバメ!しゃどうばーす知ってる? 本格アグロバトルがスマホでこんなに簡単に以下略。
違うんだ、私は宴冥府ルナちゃんとかノノパメラとかで遊びたいだけなんだ…なんだこの超越とかいうゴリラは…
宴冥府はその辺の鷹が飛んできて死ぬしパメラちゃんはテミス撃たれるしどっちも大体超越の前には無力だし……
とりあえず対戦する超越の8割が4ターン目までに0コス導きを使うの反則だと思いますううーーーー。

いやでもニンジンとかネクロアサシンとかでめっちゃ楽しい動きができるようになったのでルナちゃんは楽しい。勝てるとは言ってない。
なんだかんだで楽しんではいます。でもえいらは5コスでいいし運命は手札消滅でいいし超越はPP10以降じゃないと撃てなくなってほしい。
超越のセラフよりもどうにもならない感じはなんなんだ。

というか土にもふぇありーーさーくるみたいに1コススペルで土の印2枚とかひとつで3回くらい使える土の印とかください…。

お花

闇の組織の陰謀によりFlower Knight Girlを始めてしまった感じです。
巫女アネモネかわいい。

f:id:fantasticswallow:20161230104923j:plain

イベントはそこそこ楽です。ガチャ?知らんなぁ

f:id:fantasticswallow:20161230104809j:plain

パーティはそこそこそろってきたのですがまだ足りない気はします。
とりあえず虹が欲しい……。
総合力の割には善戦してくれることが多いのですがさすがにパーティ1に全振りしすぎて他のパーティでボスいくと簡単に全滅するのがつらい。

スノードロップほしいなぁと思いながら続けていますが果たしてどうなることやら…

大学その他もろもろ

無事にけんきゅーーーしつとかいうところでらぼちくになりました。
研究というのが大変につらいことだけわかりました。というか私は研究が向いてないのではという感じがしました。
普段から大変適当な人間なのでしっかりしたあれそれができないんだなぁという。
(もしかして:仕事も向いてない)

発想力もなければ論文執筆能力もなくすぐれたプレゼン能力もないのでもうだめです。

そんなこんなで来年からしゅーしょくです。内定という何かをもらえたのでお仕事にはありつけそうです。
問題は何か月もつんでしょうかというあれ。
お金をもらいつつ無心に働ければいいんですけどねぇ…。


というわけでなんかうつうつしてきたところで終わりです。
なんかだいがく関係でめっちゃ余裕のない一年だったなぁという気分です。来年からもそうなのかなぁ…せめてTLは追えるくらいの余裕がほしいなぁ…。
まず帰ってきて何もやる気が出ないの時点でダメさを感じる。

来年は死んだ目で日々を過ごさないようにだけ気を付けたいなぁと思います。

この辺で

追記:あれげは?と聞かれましたがばるどはーとしか買ってなかったので書くことがなかったというあれです。
来年の予定は未定です。オトメドメインは買いたいかなぁ程度。