代码改变世界

在C#的winForm程序中调用和执行javascript

2012-11-21 15:58  埋头前进的码农  阅读(1076)  评论(0编辑  收藏  举报

目前有很多的SNS社区或类SNS的网站,例如开心、51、校内等,但是发现大多数社区在邀请好友的时候都没有提供对QQ邮箱或者QQ空间好友列表获取的功能,不过似乎海内支持,但是网上相关QQ的文章还不是很多,希望这篇文章能给你带来一些帮助。

QQ空间及邮箱登陆的校验方式:

QQ空间及邮箱登陆的时候,用户输入的密码首先会被页面中的一段Js加密,然后加密过后的密码会加上验证码形成一个新的字串,接着这个新的字串被MD5(32位)加密,加密过后形成最终的密码,这就是我们为什么经常会发现我们在提交的时候我们的密码框中的密码个数会增长的原因,接着在你提交的时候,当前的这个请求会将前一次获取验证码返回的set-cookie值加入到当前请求头的cookie中以保持验证码请求与当前请求的一致性,然后再一并将新的密码和用户名Post到QQ的服务器上去。

好了,看了上面的QQ校验方式以后,我们再来看看怎样使用代码来登录QQ空间及邮箱来获取我们需要的好友和联系人。

QQ空间及邮箱自动登录获取联系人的解决方案:

QQ空间及邮箱登陆首先我们需要将用户输入的密码进行Js加密,但是我们在使用代码登录的时候我们并没有使用到浏览器,那我们怎样驱动Js呢?不用怕,在Java和.NET中都有相应的方式在服务器代码端驱动JS,这里着重讲一下.NET的方法:

首先到微软的网站上下载Windows Script Control,它是一个ActiveX(R) 控件。下载安装完成后,新建一个C#应用程序项目,在解决方案资源管理器中选中引用节点, 右键点击选择添加引用菜单,弹出添加引用对话框,单击浏览找到安装Windows Script Control的目录,选取msscript.ocx文件确定。那么在引用节点下会增加一个MSScriptControl组件,下面是他Interop 后的所有对象。

 

 

ScriptControl 对支持 ActiveX(TM) Script 的宿主 Script 引擎提供简单接口。接下来我们对被转化成ScriptControlClass类的ScriptControl的属性和方法进行一些说明。

属性

AllowUI 属性:应用于 ScriptControl 本身或 Scirpt 引擎显示的用户界面元素,可读写。

CodeObject 属性:返回对象,该对象用于调用指定模块的公用成员。只读。

Error 属性:返回 Error 对象,其中包含所发生的最后一个错误的相关详细信息。只读。

Language 属性:设置或返回正在使用的 Script 语言名称。可读写。

Modules 属性:为 ScriptControl 对象返回模块集合。只读。

Procedures 属性:返回在指定模块中定义的过程集合。只读。

SitehWnd 属性:设置或返回窗口的 hWnd,通过执行 Script 代码,此窗口用于显示对话框和其他用户界面元素。可读写。

State 属性:设置或返回 ScriptControl 对象的模式。可读写。

Timeout 属性:设置或返回时间(毫秒),此时间后用户可选择中止 Script 代码的执行或允许代码继续执行。可读写。

UseSafeSubset 属性:设置或返回 Boolean 值,指明宿主应用程序是否有保密性要求。如果宿主应用程序需要安全控制,则 UseSafeSubset 为 True,否则为 False。可读写。

方法:

 

  1. AddCode 方法:向模块添加指定代码。可多次调用 AddCode 方法。
  2. AddObject 方法:使主机对象模型对 Script 引擎可用。
  3. Eval 方法:计算表达式并返回结果。
  4. ExecuteStatement 方法:执行指定的语句。
  5. Reset 方法:放弃所有已经添加到 ScriptControl 中的 Script 代码和对象。
  6. Run 方法:运行指定过程。

 

事件:

 

  1. Error 事件:出现运行时错误时,发生此事件。
  2. Timeout 事件:当超出了 Timeout 属性指定的时间且用户在结果对话框中选定了 End 时,发生此事件。

 

说明:

AllowUI 属性如果设置为false,则显示对话框之类的语句不起作用,如在 VBScript 中MsgBox 语句,JavaScript中的alert等,并且如果执行的脚本超出TimeOut设置的毫秒数,也不会跳出超出时间提醒的对话框,反之则相反;重新设 置 Language 属性会清空AddCode加载的代码;对于TimeOut属性,发生超时时,ScriptControl 检查对象的 AllowUI 属性,确定是否允许显示用户界面元素。

为了使控件更容易使用,用ScriptEngine类封装一下,下面是完整代码:

 

using System;

using MSScriptControl;

using System.Text;

namespace ScriptNameSpace

{

/// <summary>

/// 脚本类型

/// </summary>

public enum ScriptLanguage

{

/// <summary>

/// JScript脚本语言

/// </summary>

JScript,

/// <summary>

/// VBscript脚本语言

/// </summary>

VBscript,

/// <summary>

/// JavaScript脚本语言

/// </summary>

JavaScript

}

/// <summary>

/// 脚本运行错误代理

/// </summary>

public delegate void RunErrorHandler();

/// <summary>

/// 脚本运行超时代理

/// </summary>

public delegate void RunTimeoutHandler();

/// <summary>

/// ScriptEngine类

/// </summary>

public class ScriptEngine

{

private ScriptControl msc;

//定义脚本运行错误事件

public event RunErrorHandler RunError;

//定义脚本运行超时事件

public event RunTimeoutHandler RunTimeout;

/// <summary>

///构造函数

/// </summary>

public ScriptEngine()

: this(ScriptLanguage.VBscript)

{ }

/// <summary>

/// 构造函数

/// </summary>

/// <param name="language">脚本类型</param>

public ScriptEngine(ScriptLanguage language)

{

this.msc = new ScriptControlClass();

this.msc.UseSafeSubset = true;

this.msc.Language = language.ToString();

((DScriptControlSource_Event)this.msc).Error += new DScriptControlSource_ErrorEventHandler(ScriptEngine_Error);

((DScriptControlSource_Event)this.msc).Timeout += new DScriptControlSource_TimeoutEventHandler(ScriptEngine_Timeout);

}

/// <summary>

/// 运行Eval方法

/// </summary>

/// <param name="expression">表达式</param>

/// <param name="codeBody">函数体</param>

/// <returns>返回值object</returns>

public object Eval(string expression, string codeBody)

{

msc.AddCode(codeBody);

return msc.Eval(expression);

}

/// <summary>

/// 运行Eval方法

/// </summary>

/// <param name="language">脚本语言</param>

/// <param name="expression">表达式</param>

/// <param name="codeBody">函数体</param>

/// <returns>返回值object</returns>

public object Eval(ScriptLanguage language, string expression, string codeBody)

{

if (this.Language != language)

this.Language = language;

return Eval(expression, codeBody);

}

/// <summary>

/// 运行Run方法

/// </summary>

/// <param name="mainFunctionName">入口函数名称</param>

/// <param name="parameters">参数</param>

/// <param name="codeBody">函数体</param>

/// <returns>返回值object</returns>

public object Run(string mainFunctionName, object[] parameters, string codeBody)

{

this.msc.AddCode(codeBody);

return msc.Run(mainFunctionName, ref parameters);

}

/// <summary>

/// 运行Run方法

/// </summary>

/// <param name="language">脚本语言</param>

/// <param name="mainFunctionName">入口函数名称</param>

/// <param name="parameters">参数</param>

/// <param name="codeBody">函数体</param>

/// <returns>返回值object</returns>

public object Run(ScriptLanguage language, string mainFunctionName, object[] parameters, string codeBody)

{

if (this.Language != language)

this.Language = language;

return Run(mainFunctionName, parameters, codeBody);

}

/// <summary>

/// 放弃所有已经添加到 ScriptControl 中的 Script 代码和对象

/// </summary>

public void Reset()

{

this.msc.Reset();

}

/// <summary>

/// 获取或设置脚本语言

/// </summary>

public ScriptLanguage Language

{

get { return (ScriptLanguage)Enum.Parse(typeof(ScriptLanguage), this.msc.Language, false); }

set { this.msc.Language = value.ToString(); }

}

/// <summary>

/// 获取或设置脚本执行时间,单位为毫秒

/// </summary>

public int Timeout

{

get { return 0; }

}

/// <summary>

/// 设置是否显示用户界面元素

/// </summary>

public bool AllowUI

{

get { return this.msc.AllowUI; }

set { this.msc.AllowUI = value; }

}

/// <summary>

/// 宿主应用程序是否有保密性要求

/// </summary>

public bool UseSafeSubset

{

get { return this.msc.UseSafeSubset; }

set { this.msc.UseSafeSubset = true; }

}

/// <summary>

/// RunError事件激发

/// </summary>

private void OnError()

{

if (RunError != null)

RunError();

}

/// <summary>

/// OnTimeout事件激发

/// </summary>

private void OnTimeout()

{

if (RunTimeout != null)

RunTimeout();

}

private void ScriptEngine_Error()

{

OnError();

}

private void ScriptEngine_Timeout()

{

OnTimeout();

}

}

}

 

 

在找到.NET驱动JS的方法以后,那么我们还需要找到QQ用来加密的那个JS文件,具体的JS文件大家可以到QQ的登录页面去DownLoad,如果不知道是哪一个JS大家可以通过抓包的方式去找到,这里就不再赘述了。下面我们就可以用MSScriptControl来驱动JS获取第一次加密的密码了,方法如下:

 

/// <summary>

/// 得到第一次加密后的密码

/// </summary>

/// <param name="jsFilePath">js文件</param>

/// <param name="funcName">加密的方法名</param>

/// <param name="paramers">加密方法需要传进的参数(一个是密码,另一个是页面上可获取的一个标志码)</param>

/// <returns>加密过后的密码</returns>

private object GetPassword(string jsFilePath, string funcName, params object[] paramers)

{

StreamReader reader = new StreamReader(jsFilePath);

string sScript = reader.ReadToEnd();

ScriptEngine se = new ScriptEngine(ScriptLanguage.JavaScript);

object obj = se.Run(funcName, paramers, sScript);

return obj;

}

 

通过上面的这个函数我们就可以对密码进行第一次加密了,下一步我们需要获取验证码。

我们可以使用HttpWebRequest类来Get请求这个地址:http://ptlogin2.qq.com/getimage来获取验证码,不过一定要记住把这次请求返回的cookie保存下来,我们可以将它保存到CookieContainer对象中,以便将这次返回的cookie加入到登录POST请求的头当中保持前后请求的一致性。

现在我们将获得的验证码转化成流或者其他的方式供不同的平台输出,我们发现QQ空间及邮箱的验证码生成的图像不是太复杂,所以可以考虑使用图像识别的方式自动的获取图像对应的字符(现在有很多第三方验证码识别软件),当然为了成功率考虑,最好还是让用户手动的输入。

好了有了验证码了,我们现在就将验证码字符加上刚刚我们加过密的密码形成新字串,再通过MD5加密

 

string pwd=FormsAuthentication.HashPasswordForStoringInConfigFile(this.Password, "MD5").ToLower();

 

形成最终我们需要的密码。

最后我们就可以将用户输入的用户名、密码一并POST到QQ相应的地址上去,这样我们就成功登陆QQ空间或邮箱了。登录成功过后你当然就可以获取联系人好友等等的操作了。