ぐうたら備忘録

趣味で作ったものなどをぼちぼち書いていきます。

C#×WPFでデスクトップマスコットを作った話

どんなもの?

そもそも私がこのアプリを作ったきっかけって、メイプルストーリーのメイプルキノコをデスクトップに居座らせたかったからってだけなんですよね。
なのでメイプルキノコがデスクトップでぴょこぴょこ動きます。
あと、それだけでは物足りないので色々便利な機能を付けてみました。右クリックで現れるコンテキストメニューから機能を選べます。

機能紹介

時計とツイート機能
f:id:gootalife:20180522172829g:plain

タイマー機能
f:id:gootalife:20180522172834g:plain

他にも天気予報の取得や、メモ機能などを搭載しています。

また、マスコットはResourcesフォルダ内にmascot.gifという名前の画像を入れていただくと画像の差し替えができます。

プラグインによる機能追加

IPluginインタフェースを継承したクラスライブラリとその依存関係をPluginsフォルダに入れると機能の追加が自由にできるようになっています。(誰か作って)
ぶっちゃけインタフェース周りは初見だったのでめちゃくちゃ手こずりました。

ソースコード

ソースコードは以下で公開しています。
コメントまみれで逆に見づらいかもですが自分が忘れやすいので...
github.com
キノコの素材は公開すると版権が危うい気がしたので差し替えてあります。
ご自身で画像をご用意ください。

Discordの通知を棒読みちゃんに読み上げてもらおう!~Discord.Netを添えて~

この記事はSLP KBIT Advent Calendar 2017 - Adventarの19日目の記事となります。

いい感じの導入

生粋のゲーマーである私は、「壁を隔てたお友達」と遊ぶ時に
Discordというナウいゲーマー熱いアプリを公開当初から通話に使っています。
そこで通話するとき、聞き専(「リビングゲーマー」、「ママが怖い」等の理由で、
ボイスチャットに声で参加できない人)のチャットを見るために、
ゲーム画面から目を離せないときがあります。そこで棒読みちゃんに読んでもらおうと思いました。
(Discord自体に標準で読み上げ機能はあるけどもゆっくりボイスがいい)
そこで爆誕したDiscordと棒読みちゃんの仲介人である「Yomisen」を紹介したいと思います。

用意したもの

アプローチの手順

  1. Discordにログイン
  2. チャットの通知を受け取る
  3. 棒読みちゃんに指示を送る
  4. ゆっくりしていってね

実は超簡単な棒読みちゃんへの指示出し

棒読みちゃんのサブフォルダ「SampleSrc\IpcClientChannelで読み上げ指示を送る(ローカル専用・.NET専用)\Src\BouyomiChanSample」にあるBouyomiChanClient.csをプロジェクトにそのまま追加します。
これは作者様が開発用に提供してくださっているものです。
BouyomiChanClientクラスを使用すると

var bc = new BouyomiChanClient();
bc.AddTalkTask("読み上げたい内容");
bc.Dispose();

または、

using (var bc = new BouyomiChanClient())
{
    bc.AddTalkTask("読み上げたい内容");
}

のように簡単に棒読みちゃんに読み上げたい内容を送信できます。(棒読みちゃんを事前に起動しておく)
また、using構文はリソース解放の記述をしなくてもブロックを抜けると自動的にリソースを開放してくれます。
なので、下のほうがリソース解放の書き忘れの心配がないので安心できると思います。

ソースコード

概要

  • Program.cs (メイン)
  • DiscordApiHelper.cs (メールアドレスとパスワードでトークンを取得してくれるヘルパークラス)
  • BouyomiChanClient.cs (棒読みちゃんとの接続を簡単にしてくれるクラス)

Program.cs

一部省略

using Discord;
using Discord.WebSocket;
using FNF.Utility;
using System;
using System.Threading.Tasks;

namespace Yomisen
{
    class Program
    {
        static void Main(string[] args) => MainAsync().GetAwaiter().GetResult();

        /// <summary>
        /// 非同期Mainメソッド
        /// </summary>
        static async Task MainAsync()
        {
            // トークンのチェック
            await CheckTokenAsync();
            // ログイン処理
            Console.WriteLine("ログイン中…");
            var client = new DiscordSocketClient();
            await client.LoginAsync(TokenType.User, Properties.Settings.Default.Token);
            await client.StartAsync();
            Console.WriteLine("ログイン完了");
            // 始めのあいさつ(大事)
            var task = Task.Run(() =>
            {
                using (var start = new BouyomiChanClient())
                {
                    start.AddTalkTask("棒読みちゃん起動~!");
                }
            });
            // メッセージ受信時のイベントを追加
            client.MessageReceived += Talk;
            // 各種コマンド
            InputCommand();
            // 終わりのあいさつ(大事)
            task = Task.Run(() =>
            {
                using (var end = new BouyomiChanClient())
                {
                    end.AddTalkTask("棒読みちゃん終了~!");
                }
            });
            Console.WriteLine("キー入力で終了");
            Console.ReadLine();
        }

        /// <summary>
        /// メッセージを受け取った時の処理
        /// </summary>
        static async Task Talk(SocketMessage arg)
        {
            await Task.Run(() =>
            {
                // チャンネル一覧にあるなら
                if (Properties.Settings.Default.TextChannels.IndexOf(arg.Channel.Id.ToString()) >= 0)
                {
                    // 読み上げ
                    using (var bc = new BouyomiChanClient())
                    {
                        bc.AddTalkTask(arg.Content);
                    }
                }
            });
        }

    ~以下省略~

    }
}

メッセージの通知を受信したときのイベントで、棒読みちゃんへの指示を行っています。
受信したメッセージのチャンネルIDが読み上げたいチャンネルIDとして登録されていれば読み上げが行われるようにします。

~省略~
// メッセージ受信時のイベントを追加
client.MessageReceived += Talk;
~省略~

// 読み上げ
static async Task Talk(SocketMessage arg)
{
    await Task.Run(() =>
    {
        // チャンネル一覧にあるなら
        if (Properties.Settings.Default.TextChannels.IndexOf(arg.Channel.Id.ToString()) >= 0)
        {
            // 読み上げ
            using (var bc = new BouyomiChanClient())
            {
                bc.AddTalkTask(arg.Content);
            }
        }
    });
}

Properties.Settings.Default.~~はアプリの設定ファイルで、設定値等を保存しておくことができるものです。
今回は、チャンネルIDの配列を保存しています。
f:id:gootalife:20171212174558p:plain
機能として、コマンド入力によって読み上げたいテキストチャンネルのIDを登録・削除・列挙することができます。
トークンのリセットをした場合は一度終了となり、再度ログイン作業が必要となります。
f:id:gootalife:20171212172653p:plain
テキストチャンネルのIDを調べる方法

  1. 設定>テーマ>開発者モード をオン
  2. テキストチャンネルの名前の上で右クリック
  3. IDをコピー でクリップボードにコピーされる

f:id:gootalife:20171212172943p:plain

> add 012345678901234567

のように18桁のIDを正しく入力することでテキストチャンネルのIDを登録できます。
(適当に登録しても、自身のアカウントの知れる範囲のものでないと通知は来ません)

DiscordApiHelper.cs

Discord.Netではサポートされていないメールアドレスとパスワードによる
トークンの取得を実現するためのヘルパークラスです。

using Newtonsoft.Json;
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;

namespace Yomisen
{
    public static class DiscordApiHelper
    {
        /// <summary>
        /// EmailとPasswordでトークンを取得します
        /// </summary>
        public static async Task<string> LogInAsync(string mail, string password)
        {
            var baseUrl = "https://discordapp.com/api/";
            var appName = "Yomisen";
            var discordAccount = new { email = mail, password = password };
            var json = JsonConvert.SerializeObject(discordAccount);
            var content = new StringContent(json, Encoding.UTF8, "application/json");

            var res = await GetClient(appName).PostAsync(new Uri($@"{baseUrl}/auth/login"), content);

            if (res.IsSuccessStatusCode == true)
            {
                var resJson = await res.Content.ReadAsStringAsync();
                var deserializedJson = JsonConvert.DeserializeAnonymousType(resJson, new { token = "" });
                return deserializedJson.token;
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// ヘッダーの追加
        /// </summary>
        public static HttpClient GetClient(string appName)
        {
            var client = new HttpClient();
            client.DefaultRequestHeaders.Add("User-Agent", appName);
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

            return client;
        }
    }
}

discordapp.com
このあたりの書式に沿ってヘッダーの編集などをしてJSON形式でトークンを貰います。
このトークンは、
Discord上で「ctrl+shift+i」>Appliacationタブ>Local Storage>https://discordapp.com>token
の値と同値になると思います。
f:id:gootalife:20171212182844p:plain

BouyomiChanClient.cs

特に手を加えてないので省略しますが、中を覗くと
IPCChannelを使って通信しているみたいです。興味のある方は、
棒読みちゃんをDL、または下記の私のGitHubリポジトリから閲覧してみて下さい。

使い方

  1. 棒読みちゃんを起動(音量・速度等はこちらで設定)
  2. Yomisenを起動
  3. Discordのメールアドレスとパスワードを入力(初回・リセット時のみ)
  4. テキストチャンネルIDの登録
  5. ゆっくりしていってね

GitHub

この記事では、ソースコードは一部省略していましたが、以下に公開しています。
github.com

最後に

Botを使わない方針でやってみましたが、うまく動作してくれてよかったです。
棒読みちゃんの扱いが簡単すぎて驚きました。
ユーザー指定機能もできれば追加したいですね。
あと、SkypeはアンインストールしてDiscordを使いましょう。(Discord過激派)

2017/12/20 追記
Win8以上でないと動作しません。

2018/05/22 追記
ユーザ指定機能追加しました

Visual Studio 2017 でビルド後のコマンドを指定する

ビルド後にファイルを指定の位置に移動させる

コマンドの設定画面は、
プロジェクト右クリック→プロパティ→ビルドイベント
から開くことができる。

今回は、ビルド後に、自作のプラグインのクラスライブラリ(.dll)を、
実行ファイル(.exe)が存在するフォルダのサブフォルダ「Plugins」に
自動的に移動させたいと思った。

そこで、以下のように設定した。
f:id:gootalife:20171026021857p:plain
ビルド後に、出力フォルダに移動して「Plugin」フォルダを作成して、
拡張子が.dllのファイルををすべて「Plugin」フォルダ配下に移動している。

これだけでは読み込んでくれない

サブフォルダのdllを参照するときにはconfigファイルにも変更が必要なようだ。
プロジェクトの「~.config」を開いて以下を追記

<configuration>
    <runtime>
      <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
        <probing privatePath="Plugins"/>
      </assemblyBinding>
    </runtime>
</configuration>

これでサブフォルダ内の読み込みができるようになる

privatePath="Plugins"

の部分を変更することで読み込みたいフォルダを変更、追加できる。

結果

実行ファイルのあるフォルダがごちゃごちゃしなくてスッキリ!

VirtualBoxにUbuntu16.04.3をインストールしてRailsの環境+αを作ったときのメモ

OSインストール後のセットアップメモ

完全に自分用なので保証できません

// 必要なもの
sudo apt-get install -y git libssl-dev ruby-dev libreadline-dev sqlite3 libsqlite3-dev nodejs
sudo apt-get update

// rbenvのインストール
git clone https://github.com/rbenv/rbenv.git ~/.rbenv
git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(rbenv init -)"' >> ~/.bashrc
source ~/.bashrc

// rbenvがインストールできているか確認
rbenv -v

// Rubyのバージョンの指定
rbenv install --list
rbenv install 0.0.0
rbenv global 0.0.0

// Rubyのバージョン確認
ruby -v

// Railsのインストール
gem install bundler
gem install rails

// VScodeのインストール
// 公式サイトからダウンロード
sudo dpkg -i ファイル名.deb

// 共有フォルダ
// デバイス→Guest Additions CD実行後
// 共有フォルダへのアクセス権追加
sudo gpasswd --add ユーザ名 vboxsf
// 再起動

// Google Chromeのインストール
// 公式サイトからダウンロード
sudo apt-get install libappindicator1
sudo dpkg -i ファイル名.deb

とりあえずおわり。

モンハンの装備シミュレーターを作り始めた。

いきなりどうした

PS4でモンハンの新作が来るってことは?

間違いなく買う。

だったら装備の組み合わせ考えるじゃん。
これ、モンハンの一番面白くて一番時間使うところ。

なら装備シミュレーター作っちゃえよというわけよ。

進捗

とりあえず読み込みから表示まで。
あとは装飾品とか発動するスキルの判定とかを足していく予定。
f:id:gootalife:20170822180929p:plain
VisualStudioが有能すぎてGUI周りがサクサク進む~。

2017/12/12 追記
モンハンのスキル仕様変更につき結構前から開発停止

ラズベリーパイを購入~

ラズベリーパイを購入

amazonで一式購入。(誕生日プレゼントに買ってもらったw)
お手軽な値段もしっかりしたミニミニPCですごいなーって思った。
キーボードやマウスは余ってたもので代用。

開封前の写真撮り忘れた。
f:id:gootalife:20170815153004j:plain

セットアップ

↓参考にさせていただいたサイト様。
liginc.co.jp
SDカードが64GBだったので
FAT32でのフォーマットに手こずった。色々なソフトを試した。
最終的にできたのが↓
I-O DATA ハードディスクフォーマッタ | サポートライブラリ | IODATA アイ・オー・データ機器 | IODATA アイ・オー・データ機器
ここからソフトをダウンロードして使った。

やってみたいこと

VPNサーバーを立てたり、ファイルを触れるようにしたいな~とか。

C#で簡単な計算機を作ってみた

C#GUIを触ってみた

VisualStudioを使うと見た目の部分は簡単に作れた。
(部品をD&Dするだけ、ほんとに楽だった。)
プロパティで値を変えるとそれに対応するところを書き足したり、
書き換えてくれているようだ。ボタンを押したときに
文字列の結合や計算処理をする関数を作った。

f:id:gootalife:20170722173654p:plain

動いてるところ

f:id:gootalife:20170722173657g:plain
0で割ると∞になりやす。

おわりに

VisualStudioを使うとC#はすごくサクサク書けて
気持ちがいいね~。