例大祭5 マッピング支援ツール wakimap5 v0.041


拡大する

拡大する

2008/05/23 v0.041

リスト、引き出し線の描画がON/OFF可能なように変更。

詳しくは

過去のエントリや、アーカイブに同梱されている readme.txt をご覧下さい。

例大祭5 マッピング支援ツール wakimap5 v0.04

免責事項

本ソフトウェアを使用したことにより生じたいかなる問題についても作者は一切責任を負わないこととします。自己責任でご利用下さい。

また、サークル情報は例大祭5公式サイトから独自に編集したものですが、その正確性を保証するものではありません。

本ツール、並びに同梱されている情報について、博麗神社社務所に問い合わせることはおやめ下さい。

なお、背景のフロア画像は公式サイトで公開されている画像を等比縮小したものを利用しています。

ダウンロード

wakimap5 version 0.041

なお、実行には .net framework 2.0 が必要です。

例大祭5 マッピング支援ツール wakimap5 v0.04


拡大する

拡大する

2008/05/22 v0.04

「名前を付けて保存」「読み込み」を行った場合サークルのURIが設定されていない場合のページが正しく表示できなかったバグを修正。

ブラウザウインドウで使用しているブラウザコントロールIEの問題で不正な処理を行った場合、エラーを起こして終了してしまうため、問題のあるURLをフィルタリングするように変更。

リストに変更が加えられた場合、30秒間隔で自動的にリストを保存する機能を追加。実行ファイルのあるディレクトリのautosave.datに保存されます。

詳しくは

過去のエントリや、アーカイブに同梱されている readme.txt をご覧下さい。

例大祭5 マッピング支援ツール wakimap5 v0.03

免責事項

本ソフトウェアを使用したことにより生じたいかなる問題についても作者は一切責任を負わないこととします。自己責任でご利用下さい。

また、サークル情報は例大祭5公式サイトから独自に編集したものですが、その正確性を保証するものではありません。

本ツール、並びに同梱されている情報について、博麗神社社務所に問い合わせることはおやめ下さい。

なお、背景のフロア画像は公式サイトで公開されている画像を等比縮小したものを利用しています。

ダウンロード

wakimap5 version 0.04

なお、実行には .net framework 2.0 が必要です。

例大祭5 マッピング支援ツール wakimap5 v0.03


拡大する

拡大する

2008/05/21 v0.03

メニュー、ステータスバーを追加。
左右リスト描画オプションを追加。

リストの描画スタイルについて

v0.03から「リストを左右に分けて描画」オプションが実装されました。
このオプションが有効になっている場合、リストが左右に描画されます。
会場の左側にあるサークルは左側のリストに、右側のサークルは右側のリストにそれぞれ描画されるようになります。

チェックを外すと、従来通りの右側1列レイアウトで描画します。

詳しくは

過去のエントリや、アーカイブに同梱されている readme.txt をご覧下さい。

例大祭5 マッピング支援ツール wakimap5 v0.02

免責事項

本ソフトウェアを使用したことにより生じたいかなる問題についても作者は一切責任を負わないこととします。自己責任でご利用下さい。

また、サークル情報は例大祭5公式サイトから独自に編集したものですが、その正確性を保証するものではありません。

本ツール、並びに同梱されている情報について、博麗神社社務所に問い合わせることはおやめ下さい。

なお、背景のフロア画像は公式サイトで公開されている画像を等比縮小したものを利用しています。

ダウンロード

wakimap5 version 0.03

なお、実行には .net framework 2.0 が必要です。

例大祭5 マッピング支援ツール wakimap5 v0.02


拡大する

2008/05/17 v0.02

検索ウインドウ、ブラウザウインドウを追加。
メインウインドウのリサイズに対応。

これはなに?

例大祭5での配置図マッピングを支援するためのツールです。
コミケカタロムっぽいものを目指していまし

つかいかた

右上のリストボックスには全サークルのリストが、右下のリストボックスには選択したサークルのリストがそれぞれ表示されています。

2つのリストボックスの間には、選択されているサークルのサークル名やペンネーム、WebサイトのURIが表示されます。

配置図上の任意のスペースをクリックすると、対応するサークルが全サークルリストで選択されます。

サークルが選択されている状態で、キーボードの1-9を押すと、対応する色でそのサークルを塗ります。0を押すと色指定を解除します。

同様の操作を、右クリックメニューで行うこともできます。

配置図上では、右クリックを押しっぱなしにしたままマウスをドラッグすることでスクロールすることができます。

[新規]ボタンをクリックするとリストをクリアします。

[読込]ボタンをクリックすると、リストファイルから選択サークル情報を読み込みます。

[保存]ボタンをクリックすると、現在の選択サークル情報をリストに保存します。
[上書]ボタンをクリックすると、上書き保存が可能な場合には上書き保存を行います。

[画像保存]ボタンをクリックすると、現在表示されている配置図画像(選択中のサークルを示す赤枠と紫の座標ガイドを除く)をPNG形式で保存します。

サークル情報部にある[検]ボタンをクリックすると、そのサークルのサークル名とペンネームをgoogleで検索します。

[開]ボタンをクリックすると、WebページのURIがある場合にはブラウザでそのURIを開きます。

なお、本バージョンでは印刷機能をサポートしていません。ペイントや任意のビューアなどを使って印刷して下さい。

別ウインドウ操作について

v0.02から「検索ウインドウ」と「ブラウザウインドウ」が追加されました。

検索ウインドウ

ウインドウ下部にあるテキストボックスに検索したい文字列を入力し、検索ボタンをクリックすると、その文字列を「サークル名」「ペンネーム」に持つサークルを検索します。
また、テキストボックスはオートコンプリートが有効になっているので先頭から一致入力候補が表示されるようになっています。

検索結果として表示されたリストをクリックすると、メインウインドウのリストに反映されます。

ブラウザウインドウ

メインウインドウのURI表示部分にある[自動]ボタンが有効になっている場合、サークルを選択するとそのサークルのWebサイトを自動的にブラウザウインドウ上に読み込みます。
この設定をOFFにするには、[自動]ボタンをクリックして無効化して下さい。

選択されたサークルにWebサイトのURIが設定されていない場合、googleで検索するためのページが表示されます。

既知のバグ

キーボードで連続して色を指定した場合、その直後にカーソルキーで移動しようとすると色指定をはじめたアイテムの位置からの移動となる。

選択サークルの数が増えると、配置図からリストがはみ出す。

サークルにURIが設定されていない場合のページで、"I'm Feeling Lucky"をクリックしても検索結果が表示されることがある。(googleの仕様?)

その他諸々。

免責事項

本ソフトウェアを使用したことにより生じたいかなる問題についても作者は一切責任を負わないこととします。自己責任でご利用下さい。

また、サークル情報は例大祭5公式サイトから独自に編集したものですが、その正確性を保証するものではありません。

本ツール、並びに同梱されている情報について、博麗神社社務所に問い合わせることはおやめ下さい。

なお、背景のフロア画像は公式サイトで公開されている画像を等比縮小したものを利用しています。

ダウンロード

wakimap5 version 0.02

なお、実行には .net framework 2.0 が必要です。

例大祭5 マッピング支援ツール wakimap5 v0.01pre


拡大する

これはなに?

例大祭5での配置図マッピングを支援するためのツールです。
コミケカタロムっぽいものを目指していまし

つかいかた

右上のリストボックスには全サークルのリストが、右下のリストボックスには選択したサークルのリストがそれぞれ表示されています。

2つのリストボックスの間には、選択されているサークルのサークル名やペンネーム、WebサイトのURIが表示されます。

配置図上の任意のスペースをクリックすると、対応するサークルが全サークルリストで選択されます。

サークルが選択されている状態で、キーボードの1-9を押すと、対応する色でそのサークルを塗ります。0を押すと色指定を解除します。

同様の操作を、右クリックメニューで行うこともできます。

配置図上では、右クリックを押しっぱなしにしたままマウスをドラッグすることでスクロールすることができます。

[新規]ボタンをクリックするとリストをクリアします。

[読込]ボタンをクリックすると、リストファイルから選択サークル情報を読み込みます。

[保存]ボタンをクリックすると、現在の選択サークル情報をリストに保存します。
[上書]ボタンをクリックすると、上書き保存が可能な場合には上書き保存を行います。

[画像保存]ボタンをクリックすると、現在表示されている配置図画像(選択中のサークルを示す赤枠と紫の座標ガイドを除く)をPNG形式で保存します。

サークル情報部にある[検]ボタンをクリックすると、そのサークルのサークル名とペンネームをgoogleで検索します。

[開]ボタンをクリックすると、WebページのURIがある場合にはブラウザでそのURIを開きます。

なお、本バージョンでは印刷機能をサポートしていません。ペイントや任意のビューアなどを使って印刷して下さい。

既知のバグ

キーボードで連続して色を指定した場合、その直後にカーソルキーで移動しようとすると色指定をはじめたアイテムの位置からの移動となる。

選択サークルの数が増えると、配置図からリストがはみ出す。

その他諸々。

免責事項

本ソフトウェアを使用したことにより生じたいかなる問題についても作者は一切責任を負わないこととします。自己責任でご利用下さい。

また、サークル情報は例大祭5公式サイトから独自に編集したものですが、その正確性を保証するものではありません。

本ツール、並びに同梱されている情報について、博麗神社社務所に問い合わせることはおやめ下さい。

なお、背景のフロア画像は公式サイトで公開されている画像を等比縮小したものを利用しています。

ダウンロード

wakimap5 version 0.01pre

なお、実行には .net framework 2.0 が必要です。

C#で、ニコニコ動画のコメントXMLデータを取得する


ツールを使ったニコニコ動画へのアクセスは推奨されていません。

下記のコード・ツールを使った事により生じるあらゆる責任を

筆者は負えませんのであらかじめご了承下さい。

ニコニコ動画処理クラス

ログイン・動画/コメント情報の取得などを行う niconico クラスと、コメント情報を格納する nicoComment クラス

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Net;
using System.IO;
using System.Xml;

namespace nicoCommentGetter
{
    class niconico
    {
        // ////////////////////////////////////////////////////////////////////////////////////////
        // private members
        // ////////////////////////////////////////////////////////////////////////////////////////

        // uri of api to logging in
        private const string URI_Login = "https://secure.nicovideo.jp/secure/login?site=niconico";
        
        // cookie container for login-cookie
        private CookieContainer cookie_login = null;
        
        // regex pattern for smilevideo uri
        private const string REGEX_PTN_sm_uri = @"^http://www.nicovideo.jp/watch/sm(?<videoid>[0-9]+)$";

        // getflv uri
        private const string URI_getflv = "http://www.nicovideo.jp/api/getflv?v=sm";

        // ////////////////////////////////////////////////////////////////////////////////////////
        // public members/properties
        // ////////////////////////////////////////////////////////////////////////////////////////

        // returns true is this instance is logged in.
        public bool isLogin
        {
            get { return this._isLogin; }
        }
        private bool _isLogin = false;

        public niconico(string username, string password)
        {
            // login
            bool login_result = login(username, password);
        }

        /// <summary>
        /// login into nicovideo
        /// </summary>
        /// <param name="username">username (usual mailaddress)</param>
        /// <param name="password">password</param>
        /// <returns>login succeeded or not</returns>
        public bool login(string username, string password)
        {
            // hashtable to hold the arguments of POST request.
            Hashtable post_arg = new Hashtable(3);

            post_arg["mail"] = username;
            post_arg["password"] = password;
            post_arg["next_url"] = "";

            // create cookie-container
            this.cookie_login = new CookieContainer();
            // send POST request
            string ret = HttpPost(URI_Login, post_arg, ref this.cookie_login);

            // check if result contains "ログインエラー"
            if (ret.IndexOf("ログインエラー") != -1)
            {
                this.cookie_login = null;
                this._isLogin = false;
                return false;
            }

            this._isLogin = true;
            return true;
        }



        public Hashtable getMovieInfo(string URI)
        {
            // video id
            string vid = "";

            // check already logged in or not
            if (this._isLogin == false)
                return null;
            // check have a valid cookie or not
            if (this.cookie_login == null)
                return null;

            // check url
            Regex regex_uri = new Regex(REGEX_PTN_sm_uri);
            Match regex_match = regex_uri.Match(URI);
            if (regex_match.Success)
            {
                vid = regex_match.Groups["videoid"].ToString();
            }
            else
            {
                return null;
            }

            // send request (GET)
            string getflv_uri = URI_getflv + vid;
            string response = HttpGet(getflv_uri, ref this.cookie_login);

            // parse the result
            // hashtable to hold the values
            Hashtable ret = new Hashtable();
            // urldecode
            string dec = System.Web.HttpUtility.UrlDecode(response, Encoding.UTF8);
            // parse by "&"
            string[] pairs = dec.Split(new char[] { '&' });
            // store key-value into hashtable
            foreach (string item in pairs)
            {
                // parse by '='
                string[] keyvalue = item.Split(new char[] { '=' });
                if (keyvalue.Length != 2)
                    continue;
                // store each entry
                ret[keyvalue[0]] = keyvalue[1];
            }

            return ret;
        }



        public XmlTextReader getCommentXML(string URI, int commentCount)
        {
            Hashtable minfo = getMovieInfo(URI);
            if (minfo == null)
                return null;

            // uri of message server
            string uri_message = minfo["ms"] as string;
            if (uri_message == null)
                return null;

            // thread id
            string tid = minfo["thread_id"] as string;
            if (tid == null)
                return null;

            // request argument
            string req = string.Format(
                "<thread res_from=\"-{0}\" version=\"20061206\" thread=\"{1}\" />",
                commentCount, tid
                );

            // send request
            string response = HttpPost(uri_message, req, ref this.cookie_login);

            // recreate stream (for xml stream)
            // ref: http://namespacetest.blogspot.com/2007/06/xmltextreaderstringxml.html
            MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(response), false);
            // create XML data
            XmlTextReader reader = new XmlTextReader(ms);

            return reader;
        }



        // ////////////////////////////////////////////////////////////////////////////////////////
        // common functions
        //
        // get/post request ref:
        // http://www.atmarkit.co.jp/fdotnet/dotnettips/326cookie/cookie.html

        /// <summary>
        /// Get data using GET request
        /// </summary>
        /// <param name="url">URI to send request</param>
        /// <param name="cc">CookieContainer to hold the cookie data</param>
        /// <returns>response data (string)</returns>
        private string HttpGet(string url, ref CookieContainer cc)
        {
            // Create HttpWebRequest
            HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);
            req.CookieContainer = cc;

            WebResponse res = req.GetResponse();

            // read response
            Stream resStream = res.GetResponseStream();
            StreamReader sr = new StreamReader(resStream, Encoding.UTF8);
            string result = sr.ReadToEnd();
            sr.Close();
            resStream.Close();

            return result;
        }

        /// <summary>
        /// Get data using POST request (Hash version)
        /// </summary>
        /// <param name="url">URI to send request</param>
        /// <param name="vals">POST parameter</param>
        /// <param name="cc">CookieContainer to hold the cookie data</param>
        /// <returns>response data (string)</returns>
        private string HttpPost(string url, Hashtable vals, ref CookieContainer cc)
        {
            // concatinate all key-value pair
            string param = "";
            foreach (string k in vals.Keys)
            {
                param += String.Format("{0}={1}&", k, vals[k]);
            }
            byte[] data = Encoding.ASCII.GetBytes(param);

            // create HttpWebRequest
            HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);
            req.Method = "POST";
            req.ContentType = "application/x-www-form-urlencoded";
            req.ContentLength = data.Length;
            req.CookieContainer = cc;

            // write POST data
            Stream reqStream = req.GetRequestStream();
            reqStream.Write(data, 0, data.Length);
            reqStream.Close();

            WebResponse res = req.GetResponse();

            // read response
            Stream resStream = res.GetResponseStream();
            StreamReader sr = new StreamReader(resStream, Encoding.UTF8);
            string result = sr.ReadToEnd();
            sr.Close();
            resStream.Close();

            return result;
        }

        /// <summary>
        /// Get data using POST request (Plain Text Version)
        /// </summary>
        /// <param name="url">URI to send request</param>
        /// <param name="arg">POST parameter(plain text)</param>
        /// <param name="cc">CookieContainer to hold the cookie data</param>
        /// <returns>response data (string)</returns>
        private string HttpPost(string url, string arg, ref CookieContainer cc)
        {
            byte[] data = Encoding.ASCII.GetBytes(arg);

            // create HttpWebRequest
            HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);
            req.Method = "POST";
            req.ContentType = "application/x-www-form-urlencoded";
            req.ContentLength = data.Length;
            req.CookieContainer = cc;

            // write POST data
            Stream reqStream = req.GetRequestStream();
            reqStream.Write(data, 0, data.Length);
            reqStream.Close();

            WebResponse res = req.GetResponse();

            // read response
            Stream resStream = res.GetResponseStream();
            StreamReader sr = new StreamReader(resStream, Encoding.UTF8);
            string result = sr.ReadToEnd();
            sr.Close();
            resStream.Close();

            return result;
        }

    }




    public class nicoComment
    {
        private string _comment = "";
        public string comment
        {
            get { return this._comment; }
        }

        private bool _anonymity = false;
        public bool anonymity
        {
            get { return this._anonymity; }
        }

        private string _date = "";
        public string date
        {
            get { return this._date; }
        }

        private string _mail = "";
        public string mail
        {
            get { return this._mail; }
        }

        private int _no = 0;
        public int no
        {
            get { return this._no; }
        }

        private string _thread = "";
        public string thread
        {
            get { return this._thread; }
        }

        private string _user_id = "";
        public string user_id
        {
            get { return this._user_id; }
        }

        private int _vpos = 0;
        public int vpos
        {
            get { return this._vpos; }
        }

        private bool _premium = false;
        public bool premium
        {
            get { return this._premium; }
        }

        /// <summary>
        /// constructor
        /// </summary>
        /// <param name="xmlnode">xml node instance holding comment info</param>
        public nicoComment(XmlNode xmlnode)
        {
            _comment = xmlnode.InnerText;
            _date = xmlnode.Attributes["date"].Value;
            _no = Convert.ToInt32(xmlnode.Attributes["no"].Value);
            _thread = xmlnode.Attributes["thread"].Value;
            _user_id = xmlnode.Attributes["user_id"].Value;
            _vpos = Convert.ToInt32(xmlnode.Attributes["vpos"].Value);

            _mail = "";
            if (xmlnode.Attributes["mail"] != null)
                _mail = xmlnode.Attributes["mail"].Value;

            _premium = false;
            if (xmlnode.Attributes["premium"] != null)
                _premium = (xmlnode.Attributes["premium"].Value == "1");

            _anonymity = false;
            if( xmlnode.Attributes["anonymity"] != null)
                _anonymity = (xmlnode.Attributes["anonymity"].Value == "1");
    
        }

        // comparer

        /// <summary>
        /// comparer by user-id
        /// </summary>
        public class comment_uid_comparer : IComparer<nicoComment>
        {
            #region IComparer<nicoComment> メンバ

            public int Compare(nicoComment x, nicoComment y)
            {
                string x1 = x.user_id;
                string x2 = y.user_id;
                return string.Compare(x1, x2);
            }

            #endregion
        }

        /// <summary>
        /// comparer by vpos
        /// </summary>
        public class comment_vpos_comparer : IComparer<nicoComment>
        {
            #region IComparer<nicoComment> メンバ

            public int Compare(nicoComment x, nicoComment y)
            {
                int x1 = x.vpos;
                int x2 = y.vpos;
                return x1 - x2;
            }

            #endregion
        }

        /// <summary>
        /// comparer by comment number
        /// </summary>
        public class comment_number_comparer : IComparer<nicoComment>
        {
            #region IComparer<nicoComment> メンバ

            public int Compare(nicoComment x, nicoComment y)
            {
                //throw new Exception("The method or operation is not implemented.");
                int x1 = x.no;
                int x2 = y.no;
                return x1 - x2;
            }

            #endregion
        }


}
}

メイン処理

メインのフォーム処理。GUIの配置などは省略。アーカイブの方を参照して下さい。

コントロールのプレフィクスは、lbl=ラベル、txt=テキストエディット、trv=ツリービュー、nud=ニューメリカルアップダウンです。

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Xml;

namespace nicoCommentGetter
{
    public partial class Form1 : Form
    {
        // niconico class
        private niconico nico = null;

        public Form1()
        {
            InitializeComponent();
        }

        private void btnDoExit_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        private void btnDoGet_Click(object sender, EventArgs e)
        {
            // clear tree
            this.trvTree.Nodes.Clear();

            // comment list
            List<nicoComment> comList = new List<nicoComment>();
            // comparer
            nicoComment.comment_uid_comparer comp_uid = new nicoComment.comment_uid_comparer();
            nicoComment.comment_vpos_comparer comp_vpos = new nicoComment.comment_vpos_comparer();

            // login
            this.nico = new niconico(this.txtLoginName.Text, this.txtPassword.Text);
            // get comment data
            XmlTextReader reader = this.nico.getCommentXML(this.txtMovieURI.Text, (int)this.nudCommentCount.Value);

            // comment edit
            if (reader != null)
            {
                // set cursor to wait-cursor
                this.Cursor = Cursors.WaitCursor;

                XmlDocument doc = new XmlDocument();
                doc.Load(reader);

                // fetch all <chat> elements
                foreach (XmlNode node in doc.GetElementsByTagName("chat"))
                {
                    // create nicoComment instance and store into list
                    comList.Add(new nicoComment(node));
                }

                // sort by user-id
                comList.Sort(comp_uid);


                // last uid (each-user-loop)
                string lastuid = "";
                // subset list
                List<nicoComment> sublist = null;

                foreach (nicoComment com in comList)
                {
                    string uid = com.user_id;
                    TreeNode user_node = null;

                    // check if this comment is a part of same user's comment
                    if (uid == lastuid)
                    {
                        sublist.Add(com);
                    }
                    else
                    {
                        // an user's comments are finished.
                        if (sublist != null)
                        {
                            // begin update
                            this.trvTree.BeginUpdate();

                            // sort sublist by vpos.
                            sublist.Sort(comp_vpos);

                            // user's children nodes array
                            TreeNode[] node_comment = new TreeNode[sublist.Count];
                            
                            // store each comment into array (for node)
                            for (int i = 0; i < sublist.Count; i++)
                            {
                                node_comment[i] = new TreeNode(sublist[i].comment);
                            }

                            // user's root node (child of root)
                            user_node = new TreeNode(sublist[0].user_id, node_comment);

                            // add user node
                            this.trvTree.Nodes.Add(user_node);

                            // end update
                            this.trvTree.EndUpdate();
                        }

                        // discard sublist and create new one.
                        sublist = new List<nicoComment>();
                        sublist.Add(com);

                        lastuid = uid;
                    }
                }

                // set cursor to default cursor
                this.Cursor = Cursors.Default;

                // done.
                if (this.trvTree != null)
                {
                    this.trvTree.ExpandAll();
                    this.trvTree.SelectedNode = this.trvTree.Nodes[0];
                }

            }
        }

        private void btnDoCopy_Click(object sender, EventArgs e)
        {
            // selected node
            TreeNode clk_topnode = this.trvTree.SelectedNode;

            if (clk_topnode == null)
                return;

            // copy text
            string str_cpy = "";

            // if node has no child
            if (clk_topnode.GetNodeCount(true) == 0)
            {
                str_cpy = clk_topnode.Text;
            }
            else
            {
                // this node has children.
                // ignore this node and concatinate children's text.

                // concatinate using StringBuilder
                System.Text.StringBuilder sb = new StringBuilder();
                foreach (TreeNode tn in clk_topnode.Nodes)
                {
                    if (tn.Text == string.Empty)
                        continue;

                    sb.Append(tn.Text);
                    sb.Append("\n");
                }
                str_cpy = sb.ToString();
            }

            // send to clipboard
            Clipboard.SetText(str_cpy);
            // show message
            MessageBox.Show(this, "copied: \r\n" + str_cpy, "Comment copy");

        }
    }
}

処理の流れ

  1. ニコニコ動画にログイン、ログインクッキーを取得
  2. 動画URLから動画ID(sm[0-9]+の数字部分)を取得
  3. 動画IDを使って getflv APIを呼び出し、動画情報を取得
  4. 動画情報に含まれるスレッドID、メッセージサーバのURIを使い、メッセージサーバにコメント情報を要求する。
  5. 取得したコメント情報(XML形式)をXmlDocumentに起こす
  6. XmlDocumentからchatタグのノードだけを処理する
    1. 各コメントについて、nicoCommentクラスを作成し、List<nicoComment>に格納する
    2. 全てのコメントを格納したら、user_idについてソートする
    3. そのリストを順に走査し、同一user_idのコメントを別のList<nicoComment>に格納する
    4. 同一user_idのコメントを格納したリストを、vposについてソートする
    5. ユーザとそのユーザが書き込んだコメントを、ツリービューに登録する
  7. ツリーのアイテムを選択した状態で"copy comment"がクリックされると、その子ノードの情報をクリップボードにコピーする

あまり例外処理とかしてないので、ユーザ情報がアレだったり動画がアレだったりするとアレかもしれません。

ファイル

ソース入りアーカイブはこちらから。

nicoCommentGetter_v0.1a.zip