PowerShellで2chビューワ もっとイカスの作った

これPowerShellスクリプトを組み合わせて2chビューワを作ったけど、こんなことができるとわかって、もっとイカ2chビューワを思いついた。

ソースはあとにして、ソースは以下から

どんな風に2chをビューするかというと、

まず「2ch」ドライブにcdする。

PS > cd 2ch:
PS 2ch:\>

ここでdirすると・・・

PS 2ch:\> dir | more

板名                 カテゴリー
----                 ----------
地震headline         地震
地震速報             地震
臨時地震             地震
臨時地震+            地震
ふるさと納税         おすすめ
ビジネスnews+        おすすめ
新型感染症           おすすめ
ポイント・マイル     おすすめ
面白ネタnews         be
なんでも質問         be
朝生                 be
速報headline         ニュース
ビジネスnews+        ニュース
ニュース速報+        ニュース
ニュース二軍+        ニュース
萌えニュース+        ニュース
芸スポ速報+          ニュース
美人ニュース+        ニュース
痛いニュース+        ニュース
科学ニュース+        ニュース
お詫び+              ニュース
ニュース実況+        ニュース
ニュース速報         ニュース
交通情報             ニュース
芸能音楽速報         ニュース
アニメ漫画速報       ニュース
<Space> 次のページ、<CR> 次の行、Q 終了

板一覧が表示される。

そして、見たい板にcdする。

PS 2ch:\> cd 痛いニュース+
PS 2ch:\痛いニュース+>

ここでdirすると・・・

PS 2ch:\痛いニュース+> dir | more

ID              スレッド名                               レス
--              ----------                               ---
1203341698.dat  【社会】東京女子医大の元外科医を看護...  44
1203584635.dat  石破防衛相、イージス艦衝突事故で遺族...  32
1203441030.dat  出会い系で知り合った中1女子を強姦、石... 70
1203583985.dat  重力を利用して発電                       12
1203338084.dat  キングコング西野 2ちゃんねらーに「根... 493
1203580010.dat  「AKIRA」のハリウッド実写映画化...  45
1203567091.dat  「これが本当のカンフーサッカー」日本...  127
1203344559.dat  ガソリンまた値上げ ワロタ・・                37
1202013605.dat  民主 在日韓国人などの参政権で難航も      205
1203326351.dat  亀田大毅いよいよ解雇か…ジム「面倒見...  161
1203583826.dat  ドラクエ9、今年は発売されない            19
1203569785.dat  Gスポットの謎、解明か                    39
1203566601.dat  海上自衛隊、行方不明父子の親族に口止...  65
1201861948.dat  毒餃子問題、中国ではネットだけでなく...  400
1203408558.dat  【ゲンダイ】石原都知事は無責任、奥田...  42
1202133296.dat  【川柳】サラリーマン川柳 入選100...  860
1203514365.dat  「恐らく生存していない」=自民・笹川...  152
1150282472.dat  【大阪】警官に卵投げた3少年逮捕 「...  948
1203432245.dat  暮れなずむ町で息子がつこうた? 金八...  112
1203439034.dat  鳥取県の小学校でおいしいはずの給食に...  105
1192624249.dat  元教え子の男子高校生とチョメチョメの...  944
1203160616.dat  ◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆ スレ...  44
1202803457.dat  酒気帯びで事故を起こし、雪を食べてい...  95
1202915768.dat  作家・柳美里、女性週刊誌記者の実名を...  479
1203499809.dat  今春“復帰”するヒロミに居場所はあるの? 59
1198665544.dat  多田野が入団会見「おいしいと聞いたの...  146
<Space> 次のページ、<CR> 次の行、Q 終了

スレッド一覧が表示される。

そして、見たいスレッドにcdする。

PS 2ch:\痛いニュース+> cd 1203341698.dat
PS 2ch:\痛いニュース+/1203341698.dat>

ここでdirすると・・・

PS 2ch:\痛いニュース+/1203522504.dat> dir | more

レス番号 : 1
タイトル : 瓦斯φ ★
日付     : 2008/02/18(月) 22:34:58 0
本文     :  仮眠中の看護師に睡眠剤とみられる液体を注射して性的暴行をしようとしたとして、警視庁牛込署が
            元東京女子医大病院心臓血管外科医師、山崎暁容疑者(35)=東京都新宿区若松町=を強姦
            (ごうかん)致傷と傷害容疑で逮捕していたことが分かった。山崎容疑者は「よく覚えていない」と容疑
            を否認している。

            調べでは、山崎容疑者は昨年12月11日午前2時ごろ、新宿区河田町の同病院会議室で、仮眠中
            だった20代の女性看護師の尻に服の上から睡眠剤とみられる注射を打ち、性的暴行をしようとした
            疑い。さらに女性が気付いて抵抗したため、顔面を殴るなどして1週間のけがも負わせた疑い。

            女性が会議室の外に逃げ、駆けつけた同僚が取り押さえた。山崎容疑者と女性は宿直勤務で、女性
            は会議室で1人で寝ていた。東京女子医大は昨年12月26日付で山崎容疑者を懲戒解雇にした。

            同病院の永井厚志院長は「社会的に許されない行為で極めて遺憾。捜査の推移を見守りたい」と話し
            ている。
            http://mainichi.jp/select/jiken/news/20080219k0000m040112000c.html

レス番号 : 2
タイトル : オレオレ!オレだよ、名無しだよ!!
日付     : 2008/02/18(月) 22:37:07 0
本文     :  きちがいだ。

レス番号 : 3
タイトル : オレオレ!オレだよ、名無しだよ!!
日付     : 2008/02/18(月) 22:37:22 0
<Space> 次のページ、<CR> 次の行、Q 終了

キタ━━━━━━(゚∀゚)━━━━━━ !!!!!

これのいいところはパスを覚えておけば、すぐにレスまでいけること。

PS c:\> cd 2ch:\痛いニュース+/1203522504.dat
PS 2ch:\痛いニュース+/1203522504.dat>

まだキャッシュとかしていないから無駄が多いけど、かなりおもしろい。PowerShellの可能性を垣間見た気がする。

以下全ソース

PS2chSnapIn.cs

using System;
using System.Management.Automation;
using System.ComponentModel;
using System.Diagnostics;

namespace PS2ch.Configuration {
    /// <summary>
    /// PowerShellにPS2chのスナップインをインストールするクラス
    /// </summary>
    [RunInstaller(true)]
    public class PS2chSnapIn : PSSnapIn {
        public PS2chSnapIn() { }

        public override string Description {
            get { return "PowerShellで2chの閲覧を可能にします"; }
        }
        public override string Name {
            get { return "PS2ch"; }
        }
        public override string Vendor {
            get { return "coma2n"; }
        }
    }
}

PS2chBoard.cs

using System;
using System.Diagnostics;

namespace PS2ch {
    /// <summary>
    /// 2chの板の情報を格納するクラス
    /// </summary>
    [Serializable]
    [DebuggerStepThrough]
    public class PS2chBoard {
        private string name;
        private string category;
        private string directory;
        private string domain;

        /// <summary>
        /// 板名を取得、設定します。
        /// </summary>
        public string Name {
            get { return name; }
            set { name = value; }
        }
        /// <summary>
        /// 板のカテゴリを取得、設定します。
        /// </summary>
        public string Category {
            get { return category; }
            set { category = value; }
        }
        /// <summary>
        /// 板のディレクトリ名を取得、設定します。
        /// </summary>
        public string Directory {
            get { return directory; }
            set { directory = value; }
        }
        /// <summary>
        /// 板のドメインを取得、設定します。
        /// </summary>
        public string Domain {
            get { return domain; }
            set { domain = value; }
        }

        public PS2chBoard() { }
        public PS2chBoard(string name, string category, string directory, string domain) {
            this.name = name;
            this.category = category;
            this.directory = directory;
            this.domain = domain;
        }

        public override string ToString() {
            return string.Format("Name = {0}", Name);
        }
    }
}

PS2chThread.cs

using System;
using System.Diagnostics;

namespace PS2ch {
    /// <summary>
    /// 2chのスレッドの情報を格納するクラス
    /// </summary>
    [Serializable]
    [DebuggerStepThrough]
    public class PS2chThread {
        private string id;
        private string name;
        private int count;
        private PS2chBoard board;

        /// <summary>
        /// スレッドのIDを取得、設定します。
        /// </summary>
        public string ID {
            get { return id; }
            set { id = value; }
        }
        /// <summary>
        /// スレッド名を取得、設定します。
        /// </summary>
        public string Name {
            get { return name; }
            set { name = value; }
        }
        /// <summary>
        /// レス数を取得、設定します。
        /// </summary>
        public int Count {
            get { return count; }
            set { count = value; }
        }
        /// <summary>
        /// スレッドが属する板を取得、設定します。
        /// </summary>
        public PS2chBoard Board {
            get { return board; }
            set { board = value; }
        }

        public PS2chThread() { }
        public PS2chThread(string id, string name, int count, PS2chBoard board) {
            this.id = id;
            this.name = name;
            this.count = count;
            this.board = board;
        }

        public override string ToString() {
            return string.Format("ID = {0}, Name = {1}", ID, Name);
        }
    }
}

PS2chResponse.cs

using System;
using System.Diagnostics;

namespace PS2ch {
    /// <summary>
    /// 2chのレスの情報を格納するクラス
    /// </summary>
    [Serializable]
    [DebuggerStepThrough]
    public class PS2chResponse {
        private int index;
        private string title;
        private string date;
        private string body;

        /// <summary>
        /// レス番を取得、設定します。
        /// </summary>
        public int Index {
            get { return index; }
            set { index = value; }
        }
        /// <summary>
        /// タイトルを取得、設定します。
        /// </summary>
        public string Title {
            get { return title; }
            set { title = value; }
        }
        /// <summary>
        /// 日付を取得、設定します。
        /// </summary>
        public string Date {
            get { return date; }
            set { date = value; }
        }
        /// <summary>
        /// 本文を取得、設定します。
        /// </summary>
        public string Body {
            get { return body; }
            set { body = value; }
        }

        public PS2chResponse() { }
        public PS2chResponse(int index, string title, string date, string body) {
            this.index = index;
            this.title = title;
            this.date = date;
            this.body = body;
        }

        public override string ToString() {
            return string.Format("Title = {0}", Title);
        }
    }
}

PS2chProvider.cs

using System;
using System.IO;
using System.Net;
using System.Web;
using System.Text;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Management.Automation;
using System.Management.Automation.Provider;
using System.Diagnostics;

namespace PS2ch {
    /// <summary>
    /// 2chの閲覧を可能にするプロバイダクラス
    /// </summary>
    [CmdletProvider("PS2ch", ProviderCapabilities.ShouldProcess)]
    public class PS2chProvider : NavigationCmdletProvider {
        public PS2chProvider() { }

        /// <summary>
        /// 板一覧を列挙します。
        /// </summary>
        /// <returns>板のコレクション</returns>
        private static IEnumerable<PS2chBoard> Enum2chBoard() { return Enum2chBoard(null); }
        /// <summary>
        /// 板一覧を列挙します。
        /// </summary>
        /// <param name="includeName">結果に含める板名</param>
        /// <returns>板のコレクション</returns>
        private static IEnumerable<PS2chBoard> Enum2chBoard(string includeName) {
            TextReader rs = GetHttpResponseStream(
                "http://azlucky.s25.xrea.com/2chboard/2channel.brd"
            );
            rs.ReadLine();  // 一行目は無視

            string line, category = null;

            while((line = rs.ReadLine()) != null) {
                if(line.StartsWith("\t") && category != null) {
                    string[] values = line.Split('\t');
                    string name = values[3];

                    if(includeName == null || name == includeName) {
                        yield return new PS2chBoard(name, category, values[2], values[1]);
                    }

                } else {
                    category = line.Split('\t')[0];
                }
            }
        }
        /// <summary>
        /// 指定した板名の板を取得します。
        /// </summary>
        /// <param name="name">板名</param>
        /// <returns></returns>
        private static PS2chBoard Get2chBoard(string name) {
            List<PS2chBoard> result = new List<PS2chBoard>(Enum2chBoard(name));

            return result.Count > 0 ? result[0] : null;
        }
        /// <summary>
        /// 指定した板のスレッド一覧を列挙します。
        /// </summary>
        /// <param name="board"></param>
        /// <returns>スレッドのコレクション</returns>
        private static IEnumerable<PS2chThread> Enum2chThread(PS2chBoard board) { return Enum2chThread(board, null);
        /// <summary>
        /// 指定した板のスレッド一覧を列挙します。
        /// </summary>
        /// <param name="board"></param>
        /// <param name="includeThreadID">結果に含めるスレッドID</param>
        /// <returns>スレッドのコレクション</returns>
        private static IEnumerable<PS2chThread> Enum2chThread(PS2chBoard board, string includeThreadID) {
            TextReader rs = GetHttpResponseStream(
                string.Format("http://{0}/{1}/subject.txt", board.Domain, board.Directory)
            );
            string line;

            while((line = rs.ReadLine()) != null) {
                // フォーマット
                // (THREAD_ID).dat<>(スレッド名 + 半角スペース + レス数)
                string[] values = line.Split(
                    new string[] { "<>" }, StringSplitOptions.RemoveEmptyEntries
                );
                string threadID = values[0];

                if(includeThreadID == null || threadID == includeThreadID) {
                    int pos = values[1].LastIndexOf(" ");
                    string name = values[1].Substring(0, pos);
                    int count = int.Parse(values[1].Substring(pos + 1).Trim('(', ')'));

                    yield return new PS2chThread(threadID, name, count, board);
                }
            }
        }
        /// <summary>
        /// 指定したスレッドIDのスレッドを取得します。
        /// </summary>
        /// <param name="board"></param>
        /// <param name="threadID">スレッドID</param>
        /// <returns>スレッド</returns>
        private static PS2chThread Get2chThread(PS2chBoard board, string threadID) {
            List<PS2chThread> result = new List<PS2chThread>(Enum2chThread(board, threadID));

            return result.Count > 0 ? result[0] : null;
        }
        /// <summary>
        /// 指定したスレッドのレス一覧を列挙します。
        /// </summary>
        /// <param name="thread">スレッド</param>
        /// <returns>レスのコレクション</returns>
        private static IEnumerable<PS2chResponse> Enum2chResponse(PS2chThread thread) {
            TextReader rs = GetHttpResponseStream(
                string.Format("http://{0}/{1}/dat/{2}", thread.Board.Domain, thread.Board.Directory, thread.ID)
            );
            string line;

            if((line = rs.ReadLine()) == "<?xml version=\"1.0\" encoding=\"Shift_JIS\"?>") {
            } else {
                rs.ReadLine();  // "<text><![CDATA[" を飛ばす
            }
            int index = 1;
            while((line = rs.ReadLine()) != null) {
                // フォーマット
                // 1行目: (名前)<>(メール欄)<>(書き込み日時)<>(半角スペース) + (本文)<>(スレ名)
                string[] values = line.Split(new string[] { "<>" }, StringSplitOptions.RemoveEmptyEntries);

                if(values.Length > 2) {
                    yield return new PS2chResponse(
                        index++, Escape(values[0]), Escape(values[1]), Escape(values[2])
                    );
                }
            }
        }
        /// <summary>
        /// 指定したURIのサイトにリクエストを送り、そのレスポンスストリームを取得します。
        /// </summary>
        /// <param name="uri">URI</param>
        /// <returns>ストリーム</returns>
        private static TextReader GetHttpResponseStream(string uri) {
            HttpWebRequest webReq = (HttpWebRequest)HttpWebRequest.Create(uri);
            webReq.UserAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows XP)";

            using(HttpWebResponse webRes = (HttpWebResponse)webReq.GetResponse()) {
                using(StreamReader sr = new StreamReader(
                    webRes.GetResponseStream(), Encoding.GetEncoding("Shift-JIS"))) {

                    return new StringReader(sr.ReadToEnd());
                }
            }
        }
        /// <summary>
        /// 指定したパスを分割して、板名、スレッドIDを取得します。
        /// </summary>
        /// <param name="path">パス</param>
        /// <param name="boardName">板名</param>
        /// <param name="threadID">スレッドID</param>
        private void SplitPath(string path, out string boardName, out string threadID) {
            boardName = threadID = null;

            if(path.StartsWith(base.PSDriveInfo.Root)) {
                path = path.Substring(base.PSDriveInfo.Root.Length);
            }
            string[] values = path.Split(
                new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries
            );
            if(values.Length > 0) boardName = values[0];
            if(values.Length > 1) threadID = values[1];
        }
        private static string Escape(string value) {
            return HttpUtility.HtmlDecode(value).Replace("<br>", "\r\n");
        }

        protected override Collection<PSDriveInfo> InitializeDefaultDrives() {
            Collection<PSDriveInfo> driveInfos = new Collection<PSDriveInfo>();
            driveInfos.Add(
                new PSDriveInfo("2ch", base.ProviderInfo, "2ch://", "", base.Credential)
            );
            return driveInfos;
        }
        protected override void GetChildItems(string path, bool recurse) {
            if(path == base.PSDriveInfo.Root) {
                foreach(PS2chBoard o in Enum2chBoard()) WriteItemObject(o, path, true);

            } else {
                string name, threadID;

                SplitPath(path, out name, out threadID);

                if(name != null) {
                    PS2chBoard board = Get2chBoard(name);

                    if(board != null) {
                        if(threadID != null) {
                            PS2chThread thread = Get2chThread(board, threadID);

                            if(thread != null) {
                                foreach(PS2chResponse o in Enum2chResponse(thread)) WriteItemObject(o, path, false);
                            }

                        } else {
                            foreach(PS2chThread o in Enum2chThread(board)) WriteItemObject(o, path, true);
                        }
                    }
                }
            }
        }
        protected override bool IsItemContainer(string path) {
            return true;
        }
        protected override bool ItemExists(string path) {
            if(path != base.PSDriveInfo.Root) {
                string boardName, threadID;

                SplitPath(path, out boardName, out threadID);

                if(boardName != null) {
                    PS2chBoard board = Get2chBoard(boardName);

                    if(threadID != null) {
                        PS2chThread thread = Get2chThread(board, threadID);

                        return (thread != null);
                    }
                    return (board != null);
                }
            }
            return true;
        }
        protected override string MakePath(string parent, string child) {
            if(child.Length == 0) return parent;
            if(parent.Length == 0) return child;

            return string.Format("{0}{1}{2}",
                parent, (parent.EndsWith("/") ? string.Empty : "/"), child
            );
        }
        protected override string NormalizeRelativePath(string path, string basePath) {
            return path == basePath ? string.Empty : path.Substring(basePath.Length);
        }
        protected override bool IsValidPath(string path) {
            return true;
        }
    }
}

ps2ch.format.ps1xml

<?xml version="1.0" encoding="utf-8" ?>
<Configuration>
    <ViewDefinitions>
        <View>
            <Name>PS2ch.PS2chBoard</Name>
            <ViewSelectedBy>
                <TypeName>PS2ch.PS2chBoard</TypeName>
            </ViewSelectedBy>

            <TableControl>
                <TableHeaders>
                    <TableColumnHeader>
                        <Label>板名</Label>
                        <Width>20</Width>
                    </TableColumnHeader>
                    <TableColumnHeader>
                        <Label>カテゴリー</Label>
                        <Width>20</Width>
                    </TableColumnHeader>
                </TableHeaders>

                <TableRowEntries>
                    <TableRowEntry>
                        <TableColumnItems>
                            <TableColumnItem>
                                <PropertyName>Name</PropertyName>
                            </TableColumnItem>
                            <TableColumnItem>
                                <PropertyName>Category</PropertyName>
                            </TableColumnItem>
                        </TableColumnItems>
                    </TableRowEntry>
                 </TableRowEntries>
            </TableControl>
        </View>

        <View>
            <Name>PS2ch.PS2chThread</Name>
            <ViewSelectedBy>
                <TypeName>PS2ch.PS2chThread</TypeName>
            </ViewSelectedBy>

            <TableControl>
                <TableHeaders>
                    <TableColumnHeader>
                        <Label>ID</Label>
                        <Width>15</Width>
                    </TableColumnHeader>
                    <TableColumnHeader>
                        <Label>スレッド名</Label>
                        <Width>40</Width>
                    </TableColumnHeader>
                    <TableColumnHeader>
                        <Label>レス数</Label>
                        <Width>10</Width>
                    </TableColumnHeader>
                </TableHeaders>

                <TableRowEntries>
                    <TableRowEntry>
                        <TableColumnItems>
                            <TableColumnItem>
                                <PropertyName>ID</PropertyName>
                            </TableColumnItem>
                            <TableColumnItem>
                                <PropertyName>Name</PropertyName>
                            </TableColumnItem>
                            <TableColumnItem>
                                <PropertyName>Count</PropertyName>
                            </TableColumnItem>
                        </TableColumnItems>
                    </TableRowEntry>
                 </TableRowEntries>
            </TableControl>
        </View>

        <View>
            <Name>PS2ch.PS2chResponse</Name>
            <ViewSelectedBy>
                <TypeName>PS2ch.PS2chResponse</TypeName>
            </ViewSelectedBy>

            <ListControl>
                <ListEntries>
                    <ListEntry>
                        <ListItems>
                            <ListItem>
                                <Label>レス番号</Label>
                                <PropertyName>Index</PropertyName>
                            </ListItem>
                            <ListItem>
                                <Label>タイトル</Label>
                                <PropertyName>Title</PropertyName>
                            </ListItem>
                            <ListItem>
                                <Label>日付</Label>
                                <PropertyName>Date</PropertyName>
                            </ListItem>
                            <ListItem>
                                <Label>本文</Label>
                                <PropertyName>Body</PropertyName>
                            </ListItem>
                        </ListItems>
                    </ListEntry>
                </ListEntries>
            </ListControl>
        </View>
    </ViewDefinitions>

</Configuration>