v9.7.2018.0421   支持运行时修改DAL连接字符串
大石头 编写于 2018-04-21 14:00:47
X
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using NewLife.Log;
using NewLife.Web;

namespace NewLife.Net.Fetion
{
    /// <summary>Wap飞信</summary>
    /// <remarks>
    /// 参考博客园 <a target="_blank" href="http://www.cnblogs.com/youwang/archive/2012/01/07/2315933.html">小桥流水</a>
    /// 
    /// 1,登录退出
    /// 2,根据手机号码或者飞信号发送信息
    /// 3,根据手机号码查找飞信号
    /// 4,根据飞信号查找手机号码
    /// 5,根据手机号码或者飞信号添加好友
    /// 6,好友列表,支持设置备注和请求所只用的昵称
    /// 7,支持设置任意好友的备注
    /// 8,支持获取任意好友的手机号码
    /// 9,支持向陌生人发送强制信息(看人品)
    /// </remarks>
    public class WapFetion : DisposeBase
    {
        #region 属性
        private String _Mobile;
        /// <summary>手机号码</summary>
        public String Mobile { get { return _Mobile; } set { _Mobile = value; } }

        private String _Password;
        /// <summary>密码</summary>
        public String Password { get { return _Password; } set { _Password = value; } }

        private Boolean hasLogined;

        private WebClientX _Client;
        /// <summary>客户端</summary>
        public WebClientX Client
        {
            get
            {
                if (_Client == null)
                {
                    var client = new WebClientX(false, true);
                    //client.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
                    var result = client.DownloadString(server + "/im/");

                    _Client = client;
                }
                return _Client;
            }
            set { _Client = value; }
        }

        private Boolean _ShowResponse;
        /// <summary>是否显示响应。一般只用于调试</summary>
        public Boolean ShowResponse { get { return _ShowResponse; } set { _ShowResponse = value; } }
        #endregion

        #region 构造
        /// <summary>构造函数</summary>
        /// <param name="mobile">手机号码</param>
        /// <param name="password">密码</param>
        public WapFetion(String mobile, String password)
        {
            Mobile = mobile;
            //Password = password;
            Password = UrlEncode(password);
        }

        /// <summary>子类重载实现资源释放逻辑时必须首先调用基类方法</summary>
        /// <param name="disposing">从Dispose调用(释放所有资源)还是析构函数调用(释放非托管资源)</param>
        protected override void OnDispose(bool disposing)
        {
            base.OnDispose(disposing);

            if (_Client != null) _Client.Dispose();

            if (hasLogined) Logout();
        }
        #endregion

        #region 发送数据
        String UrlEncode(String text) { return HttpUtility.UrlEncode(text, Encoding.UTF8); }

        const String server = "http://f.10086.cn";
        String Post(String uri, String data, params Object[] ps)
        {
            if (!hasLogined) Login();

            if (ps != null && ps.Length > 0) data = String.Format(data, ps);
            var d = Encoding.UTF8.GetBytes(data);
            Client.Headers[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded";
            if (ShowResponse) WriteLog("{0} {1}", uri, data);
            Client.Encoding = Encoding.UTF8;
            d = Client.UploadData(server + uri, d);
            var result = Encoding.UTF8.GetString(d);
            if (ShowResponse) WriteLog(result);
            return result;
        }

        String Get(String uri, params Object[] ps)
        {
            if (!hasLogined) Login();

            if (ps != null && ps.Length > 0) uri = String.Format(uri, ps);
            if (ShowResponse) WriteLog(uri);
            Client.Encoding = Encoding.UTF8;
            var result = Client.DownloadString(server + uri);
            if (ShowResponse) WriteLog(result);
            return result;
        }
        #endregion

        #region 登录退出
        /// <summary>登陆</summary>
        /// <returns></returns>
        public String Login()
        {
            if (hasLogined) return null;
            hasLogined = true;

            var result = Post("/im/login/inputpasssubmit1.action", "m={0}&pass={1}&loginstatus=1", Mobile, Password);
            if (result.Contains("失败")) throw new NetException("登录失败!");
            return result;
        }

        /// <summary>注销</summary>
        /// <returns></returns>
        public String Logout()
        {
            return Post("/im/index/logoutsubmit.action", "");
        }
        #endregion

        #region 发送消息
        /// <summary>通过手机号,给自己会好友发送消息</summary>
        /// <param name="mobile">手机号</param>
        /// <param name="message">消息</param>
        public void Send(String mobile, String message)
        {
            if (String.IsNullOrEmpty(message)) throw new ArgumentNullException("message");
            if (String.IsNullOrEmpty(mobile)) mobile = Mobile;

            if (mobile == Mobile)
                SendToMyself(message);
            else
            {
                Int32 uid = GetUid(mobile);
                if (uid != 0)
                    Send(uid, message);
                else
                    AddFriend(mobile);
            }
        }

        /// <summary>获取用户UID</summary>
        /// <param name="mobile"></param>
        /// <returns></returns>
        protected Int32 GetUid(String mobile)
        {
            // 在好友列表里面找
            var fs = _Friends;
            var f = fs == null ? null : fs.FirstOrDefault(e => e.Mobile == mobile);
            if (f != null) return f.ID;

            String uri = "/im/index/searchOtherInfoList.action";
            String data = "searchText=" + mobile;

            String result = Post(uri, data);
            Match mc = Regex.Match(result, @"toinputMsg\.action\?touserid=(\d+)");
            if (!mc.Success) return 0;

            var id = Int32.Parse(mc.Groups[1].Value);
            if (id > 0)
            {
                // 如果没有,就强制创建列表
                fs = _Friends ?? (_Friends = new List<Friend>());
                // 分别根据编号和手机号查找
                f = fs.FirstOrDefault(e => e.Mobile == mobile);
                if (f == null) f = fs.FirstOrDefault(e => e.Mobile == mobile);
                // 如果没有,创建一个
                if (f == null)
                {
                    f = new Friend();
                    f.Client = this;
                    fs.Add(f);
                }
                // 设置编号和手机号
                f.ID = id;
                f.Mobile = mobile;
            }

            return id;
        }

        /// <summary>发往目标UID</summary>
        /// <param name="uid"></param>
        /// <param name="message"></param>
        public void Send(Int32 uid, String message)
        {
            if (uid < 1) throw new ArgumentNullException("uid");
            if (String.IsNullOrEmpty(message)) throw new ArgumentNullException("message");

            String uri = "/im/chat/sendMsg.action?touserid=" + uid;
            String data = "msg=" + UrlEncode(message);
            String result = Post(uri, data);
            if (!result.Contains("成功")) throw new XException("发送失败!");
        }

        /// <summary>发送给自己</summary>
        /// <param name="message"></param>
        public void SendToMyself(String message)
        {
            if (String.IsNullOrEmpty(message)) throw new ArgumentNullException("message");

            String uri = "/im/user/sendMsgToMyselfs.action";
            String data = "msg=" + UrlEncode(message);
            String result = Post(uri, data);
            if (!result.Contains("成功")) throw new XException("发送失败!");
        }

        /// <summary>向非好友发送强制信息。经测试,很难成功,貌似必须同城用户才行</summary>
        /// <param name="uid"></param>
        /// <param name="message"></param>
        public void SendStranger(Int32 uid, String message)
        {
            if (uid < 1) throw new ArgumentNullException("uid");
            if (String.IsNullOrEmpty(message)) throw new ArgumentNullException("message");

            // 首先创建强制会话
            Get("/im/chat/toStrangers.action?touserid=" + uid);
            String uri = "/im/chat/sendStrangerMsg.action?touserid=" + uid;
            String data = "backUrl=&touchTextLength=&touchTitle=&msg=" + UrlEncode(message);
            String result = Post(uri, data);
            if (!result.Contains("成功")) throw new XException("发送失败!");
        }
        #endregion

        #region 添加好友
        /// <summary>添加好友</summary>
        /// <param name="mobile">对方手机号码</param>
        /// <param name="localName">本地名</param>
        /// <param name="nickName">我的昵称,让对方知道我是谁</param>
        public void AddFriend(String mobile, String localName = null, String nickName = null)
        {
            AddFriend(Int32.Parse(mobile), 0, localName, nickName);
        }

        /// <summary>添加好友</summary>
        /// <param name="number">手机号码或者飞信号码,由第二个参数决定</param>
        /// <param name="type">0:手机号;1:飞信号</param>
        /// <param name="localName">本地名</param>
        /// <param name="nickName">我的昵称,让对方知道我是谁</param>
        public void AddFriend(Int32 number, Int32 type = 0, String localName = null, String nickName = null)
        {
            if (number <= 0) throw new ArgumentNullException("number");
            if (String.IsNullOrEmpty(nickName)) nickName = Mobile.Substring(0, 5);

            if (!String.IsNullOrEmpty(localName) && localName.Length > 5) localName = localName.Substring(0, 5);
            if (!String.IsNullOrEmpty(nickName) && nickName.Length > 5) nickName = nickName.Substring(0, 5);

            if (!String.IsNullOrEmpty(localName)) localName = UrlEncode(localName);
            if (!String.IsNullOrEmpty(nickName)) nickName = UrlEncode(nickName);

            Post("/im/user/insertfriend1.action", "");
            Post("/im/user/insertfriend2.action", "type={0}&number={1}", type, number);
            Post("/im/user/insertfriend3.action", "buddylist=0&nickname=&type={0}&number={1}", type, number);
            Post("/im/user/insertfriendsubmit.action", "nickname={3}&buddylist=0&localName={2}&type={1}&number={0}", number, type, localName, nickName);
        }
        #endregion

        #region 好友列表
        private List<Friend> _Friends;
        /// <summary>好友列表</summary>
        public List<Friend> Friends { get { return _Friends ?? (_Friends = GetFriends()); } set { _Friends = value; } }

        static Regex reg_friends = new Regex("<img[^>]*alt=\"([^\"]*)\"[^>]*>\\s*(?:</img>)\\s*<a[^>]*/im/chat/toinputMsg\\.action\\?touserid=(\\d+)[^>]*>([^<]*)</a>\\s*([^<]*?)\\s*<br", RegexOptions.Compiled);
        static Regex reg_next = new Regex("href=\"([^\"]*)\"[^>]*>下一页</a>", RegexOptions.Compiled);
        List<Friend> GetFriends()
        {
            var list = new List<Friend>();

            String uri = "/im/index/index.action?type=all";
            while (true)
            {
                var rs = Get(uri);

                var ms = reg_friends.Matches(rs);
                if (ms == null) break;

                foreach (Match item in ms)
                {
                    var entity = new Friend();
                    entity.ID = Int32.Parse(item.Groups[2].Value);
                    entity.Name = item.Groups[3].Value;
                    entity.Status = item.Groups[1].Value;
                    entity.Description = item.Groups[4].Value;

                    // 最后设置这个,否则,给Name赋值的时候,会引发一系列反应
                    entity.Client = this;

                    list.Add(entity);
                }

                // 获取下一页
                var m = reg_next.Match(rs);
                if (!m.Success) break;

                uri = m.Groups[1].Value;
                if (String.IsNullOrEmpty(uri)) break;
                uri = HttpUtility.HtmlDecode(uri);
            }

            return list;
        }

        /// <summary>更新本地名</summary>
        /// <param name="uid"></param>
        /// <param name="localName"></param>
        public void UpdateLocalName(Int32 uid, String localName)
        {
            if (uid < 1) throw new ArgumentNullException("uid");
            //if (String.IsNullOrEmpty(localname)) throw new ArgumentNullException("localname");

            Post("/im/user/updateLocalnames.action", String.Format("touserid={0}&localName={1}", uid, localName));
        }

        /// <summary>获取指定用户的手机号</summary>
        /// <param name="uid"></param>
        /// <returns></returns>
        public String GetMobile(Int32 uid)
        {
            if (uid < 1) throw new ArgumentNullException("uid");

            var rs = Get("/im/user/userinfoByuserid.action?touserid=" + uid);
            if (String.IsNullOrEmpty(rs)) return null;

            var reg = new Regex("手机号码:(\\d{11})", RegexOptions.Compiled);
            var m = reg.Match(rs);
            if (!m.Success) return null;

            return m.Groups[1].Value;
        }

        #region 好友
        /// <summary>好友</summary>
        public class Friend
        {
            private WapFetion _Client;
            /// <summary>客户端</summary>
            internal WapFetion Client { get { return _Client; } set { _Client = value; } }

            private Int32 _ID;
            /// <summary>编号</summary>
            public Int32 ID { get { return _ID; } set { _ID = value; } }

            private String _Name;
            /// <summary>名称</summary>
            public String Name { get { return _Name; } set { _Name = value; if (Client != null) Client.UpdateLocalName(ID, Name); } }

            private String _Mobile;
            /// <summary>号码</summary>
            public String Mobile { get { return _Mobile; } set { _Mobile = value; } }

            private String _Status;
            /// <summary>状态</summary>
            public String Status { get { return _Status; } set { _Status = value; } }

            private String _Description;
            /// <summary>描述</summary>
            public String Description { get { return _Description; } set { _Description = value; } }

            /// <summary>刷新信息,目前主要获取手机号</summary>
            public void Refresh()
            {
                if (Client != null) Mobile = Client.GetMobile(ID);
            }

            /// <summary>已重载。</summary>
            /// <returns></returns>
            public override string ToString()
            {
                var m = Mobile;
                if (String.IsNullOrEmpty(m)) m = ID.ToString();
                return String.Format("[{0}]{1}({2}) {3}", Status, Name, m, Description);
            }
        }
        #endregion
        #endregion

        #region 日志
        /// <summary>日志</summary>
        public ILog Log { get; set; } = Logger.Null;

        /// <summary>写日志</summary>
        /// <param name="format"></param>
        /// <param name="args"></param>
        public void WriteLog(String format, params Object[] args)
        {
            Log?.Info(format, args);
        }
        #endregion
    }
}