通过OAuth访问imgUr(上传到用户帐户)

为了开始做这个“简单”的任务,我已经研究了一个我在这里作为例子遵循并重现步骤的程序,程序可以上传一个“匿名”的图像:

Private ReadOnly ClientId As String = "My Client ID" ' => "..............." Private ReadOnly ClientSecret As String = "My Client Secret" ' => "........................................" ' Usage: ' Dim url As String = UploadImage("C:\Image.jpg") : MessageBox.Show(url) Public Function UploadImage(ByVal image As String) Dim w As New WebClient() w.Headers.Add("Authorization", "Client-ID " & ClientId) Dim Keys As New System.Collections.Specialized.NameValueCollection Try Keys.Add("image", Convert.ToBase64String(File.ReadAllBytes(image))) Dim responseArray As Byte() = w.UploadValues("https://api.imgur.com/3/image", Keys) Dim result = Encoding.ASCII.GetString(responseArray) Dim reg As New System.Text.RegularExpressions.Regex("link"":""(.*?)""") Dim match As Match = reg.Match(result) Dim url As String = match.ToString.Replace("link"":""", "").Replace("""", "").Replace("\/", "/") Return url Catch s As Exception MessageBox.Show("Something went wrong. " & s.Message) Return "Failed!" End Try End Function 

但是我真正想要做的是将图像上传到我的用户帐户,即http://elektrostudios.imgur.com 。

我发现这个问题,但他在答案中说的是不清楚我的(由于我的新手知识),无论如何,我已经尝试使用上面的function,但只是发送BEARER头与我的ClientSecret ID因为如果我理解好oauth 2 api文档说的令牌也可以是ClientSecret ID?但我没有得到预期的结果。

所以寻找方式来获得正确的访问令牌我见过这个问题,这帮助我发现RestSharp库,并知道如何发送请求,我做了一些修改,以使用Imgur的API,但我得到这个错误-响应:

 {"data":{"error":"client_id and response_type are required","request":"\/oauth2\/authorize","method":"POST"},"success":false,"status":400} 

这是我的:

 Public Sub GetAccessToken() Dim xrc As RestClient = New RestClient Dim grant_type As String = "authorization_code" Dim request As New RestRequest(Method.POST) Dim strBody As String Dim response As RestResponse Dim strResponse As String request.Method = Method.POST request.RequestFormat = DataFormat.Xml 'Base URL xrc.BaseUrl = "https://api.imgur.com" 'Resource request.Resource = "oauth2/authorize" 'Format body strBody = String.Format("client_id={0}&response_type={1}", ClientId, ClientSecret) 'Add body to request request.AddBody("Authorization", strBody) 'Execute response = xrc.Execute(request) 'Parse Response strResponse = response.Content MessageBox.Show(response.Content.ToString) End Sub 

所以我的问题是2合1:

我如何使用所需的东西(如访问令牌)将图像上传到Imgur用户帐户?

PS:请记住,即使获取访问令牌,我不知道如何使用它后,存储它。

更新:

我试图使用@Pulutonix解决scheme,但是当我尝试请求令牌时,它会引发一个exception“ Need a valid PIN first ”,我正在使用一个有效的ClientId和ClientSecret,我错过了更多的东西?,这里是码:

 Private imgUR As New imgurAPI("my client id", "my client secret") Private Sub Button1_Click() Handles Button1.Click Dim wb As New WebBrowser imgUR.RequestPinBrowser(wb) ' The instruction below throws an exception: ' "Need a valid PIN first" Dim result As imgurAPI.imgUrResults = imgUR.RequestToken wb.Dispose() ' check result If result = imgurAPI.imgUrResults.OK Then ' assumes the file exists imgUR.UploadImage("C:\Test.jpg", False) Clipboard.SetText(imgUR.LastImageLink) MessageBox.Show(imgUR.LastImageLink) Else MessageBox.Show(String.Format("Error getting access token. Status:{0}", result.ToString)) End If End Sub 

这很长,因为它或多或less是一个完整的API:

 Public Class imgurAPI ' combination of this API and imgUR server responses Public Enum imgUrResults OK = 200 ' AKA Status200 ' errors WE return OtherAPIError = -1 ' so far, just missing ImgLink InvalidToken = -2 InvalidPIN = -3 ' pins expire InvalidRequest = -4 TokenParseError = -5 ' results we get from server BadRequestFormat = 400 ' Status400 AuthorizationError = 401 ' Status401 Forbidden = 403 ' Status403 NotFound = 404 ' Status404 ' bad URL Endpoint RateLimitError = 429 ' Status429 ' RateLimit Error ServerError = 500 ' Status500 ' internal server error UknownStatus = 700 ' We havent accounted for it (yet), ' may be trivial or new End Enum ' container for the cool stuff they send us Friend Class Token Public Property AcctUserName As String Public Property AccessToken As String Public Property RefreshToken As String Public Property Expiry As DateTime Public Sub New() AcctUserName = "" AccessToken = "" RefreshToken = "" Expiry = DateTime.MinValue End Sub Friend Function IsExpired() As Boolean If (Expiry > DateTime.Now) Then Return False Else ' if expired reset everything so some moron doesnt ' expose AccessToken and test for "" AcctUserName = "" AccessToken = "" RefreshToken = "" Expiry = DateTime.MinValue Return True End If End Function End Class ' NO simple ctor!!! ' constructor initialized with ClientID and SecretID Public Sub New(clID As String, secret As String) clientID = clID clientSecret = secret myPin = "" imgToken = New Token LastImageLink = "" UseClipboard = True AnonOnly = False End Sub ' constructor initialized with ClientID and SecretID Public Sub New(clID As String) clientID = clID clientSecret = "" myPin = "" imgToken = New Token LastImageLink = "" UseClipboard = True AnonOnly = True End Sub Private clientID As String Private clientSecret As String Private AnonOnly As Boolean = True ' tokens are not public Private imgToken As Token Public Property LastImageLink As String Public Property UseClipboard As Boolean ' precise moment when it expires for use in code Public ReadOnly Property TokenExpiry As DateTime Get If imgToken IsNot Nothing Then Return imgToken.Expiry Else Return Nothing End If End Get End Property Public Function GetExpiryCountdown() As String Return String.Format("{0:hh\:mm\:ss}", GetExpiryTimeRemaining) End Function ' time left as a TimeSpan Public Function GetExpiryTimeRemaining() As TimeSpan Dim ts As New TimeSpan(0) If imgToken Is Nothing Then Return ts End If If DateTime.Now > imgToken.Expiry Then Return ts Else ts = imgToken.Expiry - DateTime.Now Return ts End If End Function Public Function IsTokenValid() As Boolean If imgToken Is Nothing Then Return False End If If String.IsNullOrEmpty(imgToken.AcctUserName) Then Return False End If If imgToken.IsExpired Then Return False End If Return True End Function ' Currently, the PIN is set from a calling App. Might be possible ' to feed the log in to imgUr to get a PIN Private myPin As String Public WriteOnly Property Pin As String Set(value As String) myPin = value End Set End Property ' Navigates to the web page. ' see wb_DocumentCompleted for code to ' parse the PIN from the document Public Sub RequestPinBrowser(BrowserCtl As WebBrowser) If AnonOnly Then ' you do not need a PIN for Anon Throw New ApplicationException("A PIN is not needed for ANON Uploads") Exit Sub End If If BrowserCtl Is Nothing Then Throw New ArgumentException("Missing a valid WebBrowser reference") Exit Sub End If ' imgur API format ' https://api.imgur.com/oauth2/authorize?client_id=YOUR_CLIENT_ID&response_type=REQUESTED_RESPONSE_TYPE&state=APPLICATION_STATE Dim OAuthUrlTemplate = "https://api.imgur.com/oauth2/authorize?client_id={0}&response_type={1}&state={2}" Dim ReqURL As String = String.Format(OAuthUrlTemplate, clientID, "pin", "ziggy") BrowserCtl.Url = New Uri(ReqURL) End Sub Public Function GetAccessToken() As imgUrResults ' there are different types of token requests ' which vary only by the data submitted Dim sReq As String = String.Format("client_id={0}&client_secret={1}&grant_type=pin&pin={2}", clientID, clientSecret, myPin) If myPin = String.Empty Then Return imgUrResults.InvalidPIN End If If AnonOnly Then Return imgUrResults.InvalidRequest ' call generic token processor Return RequestToken(sReq) End Function ' request a Token Private Function RequestToken(sRequest As String) As imgUrResults Dim url As String = "https://api.imgur.com/oauth2/token/" Dim myResult As imgUrResults = imgUrResults.OK ' create request for the URL, using POST method Dim request As WebRequest = WebRequest.Create(url) request.Method = "POST" ' convert the request, set content format, length Dim data As Byte() = System.Text.Encoding.UTF8.GetBytes(sRequest) request.ContentType = "application/x-www-form-urlencoded" request.ContentLength = data.Length ' write the date to request stream Dim dstream As Stream = request.GetRequestStream dstream.Write(data, 0, data.Length) dstream.Close() ' json used on the response and potential WebException Dim json As New JavaScriptSerializer() ' prepare for a response Dim response As WebResponse = Nothing Dim SvrResponses As Dictionary(Of String, Object) Try response = request.GetResponse ' convert status code to programmatic result myResult = GetResultFromStatus(CType(response, HttpWebResponse).StatusCode) Catch ex As WebException ' a bad/used pin will throw an exception Dim resp As String = New StreamReader(ex.Response.GetResponseStream()).ReadToEnd() SvrResponses = CType(json.DeserializeObject(resp.ToString), Dictionary(Of String, Object)) myResult = GetResultFromStatus(Convert.ToString(SvrResponses("status"))) End Try 'Console.WriteLine(CType(response, HttpWebResponse).StatusDescription) 'Console.WriteLine(CType(response, HttpWebResponse).StatusCode) ' premature evacuation If myResult <> imgUrResults.OK Then If dstream IsNot Nothing Then dstream.Close() dstream.Dispose() End If If response IsNot Nothing Then response.Close() End If Return myResult End If ' read the response stream dstream = response.GetResponseStream Dim SvrResponseStr As String Using sr As StreamReader = New StreamReader(dstream) ' stream to string SvrResponseStr = sr.ReadToEnd 'Console.WriteLine(SvrResponse) End Using ' close streams dstream.Close() dstream.Dispose() response.Close() Try ' use json serialier to parse the result(s) ' convert SvrRsponse to Dictionary SvrResponses = CType(json.DeserializeObject(SvrResponseStr), Dictionary(Of String, Object)) ' get stuff from Dictionary imgToken.AccessToken = Convert.ToString(SvrResponses("access_token")) imgToken.RefreshToken = Convert.ToString(SvrResponses("refresh_token")) imgToken.AcctUserName = Convert.ToString(SvrResponses("account_username")) ' convert expires_in to a point in time Dim nExp As Integer = Convert.ToInt32(Convert.ToString(SvrResponses("expires_in"))) imgToken.Expiry = Date.Now.Add(New TimeSpan(0, 0, nExp)) ' Pins are single use ' throw it away since it is no longer valid myPin = "" Catch ex As Exception 'MessageBox.Show(ex.Message) myResult = imgUrResults.TokenParseError End Try Return myResult End Function ' public interface to check params before trying to upload Public Function UploadImage(filename As String, Optional Anon As Boolean = False) As imgUrResults If AnonOnly Then Return DoImageUpLoad(filename, AnonOnly) Else If IsTokenValid() = False Then Return imgUrResults.InvalidToken End If End If ' should be the job of the calling app to test for FileExist Return DoImageUpLoad(filename, Anon) End Function ' actual file uploader Private Function DoImageUpLoad(fileName As String, Optional Anon As Boolean = False) As imgUrResults Dim result As imgUrResults = imgUrResults.OK LastImageLink = "" Try ' create a WebClient Using wc = New Net.WebClient() ' read image Dim values = New NameValueCollection() From { {"image", Convert.ToBase64String(File.ReadAllBytes(fileName))} } ' type of headers depends on whether this is an ANON or ACCOUNT upload If Anon Then wc.Headers.Add("Authorization", "Client-ID " + clientID) Else wc.Headers.Add("Authorization", "Bearer " & imgToken.AccessToken) End If ' upload, get response Dim response = wc.UploadValues("https://api.imgur.com/3/upload.xml", values) ' read response converting byte array to stream Using sr As New StreamReader(New MemoryStream(response)) Dim uplStatus As String Dim SvrResponse As String = sr.ReadToEnd Dim xdoc As XDocument = XDocument.Parse(SvrResponse) ' get the status of the request uplStatus = xdoc.Root.Attribute("status").Value result = GetResultFromStatus(uplStatus) If result = imgUrResults.OK Then LastImageLink = xdoc.Descendants("link").Value ' only overwrite the server result status If String.IsNullOrEmpty(LastImageLink) Then ' avoid NRE elsewhere LastImageLink = "" ' we did something wrong parsing the result ' but this one is kind of minor result = imgUrResults.OtherAPIError End If End If End Using If UseClipboard AndAlso (result = imgUrResults.OK) Then Clipboard.SetText(LastImageLink) End If End Using Catch ex As Exception Dim errMsg As String = ex.Message ' rate limit If ex.Message.Contains("429") Then result = imgUrResults.RateLimitError ' internal error ElseIf ex.Message.Contains("500") Then result = imgUrResults.ServerError End If End Try Return result End Function Private Function GetResultFromStatus(status As String) As imgUrResults Select Case status.Trim Case "200" Return imgUrResults.OK Case "400" Return imgUrResults.BadRequestFormat Case "401" Return imgUrResults.AuthorizationError Case "403" Return imgUrResults.Forbidden Case "404" Return imgUrResults.NotFound Case "429" Return imgUrResults.RateLimitError Case "500" Return imgUrResults.ServerError Case Else ' Stop - work out other returns Return imgUrResults.UknownStatus End Select End Function Private Function GetResultFromStatus(status As Int32) As imgUrResults ' some places we get a string, others an integer Return GetResultFromStatus(status.ToString) End Function End Class 

如何使用它

该过程需要用户的浏览器来导航和请求PIN。 对于testing/开发,我使用了WebBrowser控件,并从返回的页面中获取PIN码。

注意:为了testing,我的imgUR帐户被设置为DESKTOP,因为我们从DESKTOP应用程序发送。 此外,这是你发送图像到您的帐户。 没有办法将OTHERS上传到您的帐户,而无需提供您的秘密ID和/或embedded您的主ImgURlogin名和密码在App中。 这就是ImgUR所devise的。

A.创build一个imgUR对象:

 Friend imgUR As imgurAPI imgUR = New imgurAPI(<your Client ID>,<your secret code>) 

B.获得一个Pin – 方法一

 ' pass the app's WebBrowser Control imgUR.RequestPinBrowser(wb) 

这将带您进入一个imgur页面,您必须授权上传到您的帐户的密码的问题。 input您的账户名称,密码,点击允许。 将显示带有PIN码的新页面。 将网页中的PIN码复制到其他控件,然后将其提供给imgurAPI类。

下面的代码parsingPIN页面,并将其转换为另一个控件。

方法二

  • 使用您自己的外部浏览器,请转到

https://api.imgur.com/oauth2/authorize? client_id=YOUR_CLIENT_ID&response_type=pin&state=ziggy

  • login
  • 将您收到的PIN码复制到TextBox或将其发送到imgurAPI:
  • 设置imgUR.Pin = <<PIN YOU RECEIVED>>imgUR.Pin = <<PIN YOU RECEIVED>>

这个过程是一样的,只是你想不得不在表单中包含WebBrowser控件。 个人识别码只有很短的时间,所以你必须使用它来立即获得一个访问令牌。

C.获取访问令牌

 ' imgUrResults is an enum exposed by the class Dim result As imgurAPI.imgUrResults = imgUR.RequestToken 

笔记:

  • imgUR类将保留该令牌
  • 令牌当前在1小时内(3600秒)

D:上传文件
上传使用imgUR.UploadImage(filename, boolAnon)

文件名 – 要上传的文件

boolAnon – 布尔标志。 False =将该file upload到您的帐户与匿名通用池方法。

例:

 ' get token Dim result As imgurAPI.imgUrResults = imgUR.RequestToken ' check result If result = imgurAPI.imgUrResults.OK Then ' assumes the file exists imgUR.UploadImage("C:\Temp\London.jpg", False) Else MessageBox.Show(String.Format("Error getting access token. Status:{0}", result.ToString)) End If 

在file upload之后,程序在响应中查找链接。 如果可以parsing链接,则可以从LastImageLink属性中获取并粘贴到剪贴板。

其他属性,设置和方法

LastImageLink (string) – 上传的最后一张图片的url

UseClipBoard (Bool) – 如果为true,则imgurAPI类将链接上传到剪贴板

TokenExpiry (Date) – 当前令牌过期的date时间

GetTokenTimeRemaining ()作为TimeSpan – 表示当前令牌到期之前的时间的TimeSpan

公共函数GetTokenCountdown()作为string – TimeRemaining的格式化的string

Public WriteOnly Property Pin As String – 获取访问令牌所需的PIN

公共职能IsTokenValid()作为布尔 – 是当前令牌有效

公共函数IsTokenExpired ()作为布尔值 – TimeRemaining vs DateTime.Now的简单布尔版本

笔记

  • 代币可以更新或扩展。 但是,由于他们持续了一个小时,这似乎很多。
  • PINS只有很短的时间。 一旦PIN被交换为令牌,imgurAPI(这个类)将清除PIN。 如果获得令牌有问题,则必须先获得一个新的PIN(或者如果刚刚拿到了最后一个,就粘贴上一个)。
  • 除非您更改了帐户中的设置,否则上传的图像对整个世界都是不可见的。
  • 您可以重置您的SecretID(设置 – >应用程序)。 如果你这样做,你还需要重新设置使用这个API类的应用程序,然后重新编译(或从configuration文件中读取它)。

如果您使用WebBrowser控件获取PIN码,则可以将此代码添加到DocumentCompleted事件中,以从HTML中删除PIN码:

 ' wb is the control Dim htmlDoc As System.Windows.Forms.HtmlDocument = wb.Document Dim elP As System.Windows.Forms.HtmlElement = htmlDoc.GetElementById("pin") If elP IsNot Nothing Then sPin = elP.GetAttribute("value") If String.IsNullOrEmpty(sPin) = False Then ' user has to push the button for `imgUR.Pin = tbPIN.Text` ' this is in case the HTML changes, the user can override ' and input the correct PIN Me.tbPIN.Text = sPin End If End If 

关于OAuth模型

这是非官方的 – 通过阅读文档和使用API​​获得的信息。 截至此date,适用于imgur API v3。

没有什么是自动获取PIN码。 无论如何,您必须导航到浏览器中的url,并input您的帐户名称和密码才能获得PIN码。 这是通过devise使您自己亲自授权某个外部应用访问您的帐户内容。

方法一上面使用.NET WebBrowser控件来做到这一点。 使用这个方法,我们可以确定你和imgur类都使用相同的Endpoint / URL,因为它通过浏览器控件发送给你。

方法二只是你在一些浏览器,任何浏览器去那里。 login,获取PIN码,并将其粘贴到imgurAPI类。

不pipe方法如何,使用的正确的端点/ URL是:

https://api.imgur.com/oauth2/authorize?client_id=YOUR_CLIENT_ID&response_type=pin&state=foobar

在使用imgurAPI类的表单上使用浏览器,我们显然可以确定您和类都使用相同的URL和ClientID。 DocumentComplete的代码将文本提取到文本框中, 但是您仍然需要在类中进行设置:

 myimgUR.PIN = tbPinCode.Text 

PINS是一次性使用,并到期。

所以特别是在开发时,你停止了代码,添加一些东西,然后自然重新运行,代码将不再有旧的令牌或PIN。 如果最后一个PIN是最近的,而且没有提交,那么您可能不需要换一个,但是我发现很难记住是否是这种情况。

class级将PINS视为一次性使用。 一旦收到一个令牌,它将清除该variables,因为它们已经被使用并且不再有效。


最终编辑

  • 添加了仅限匿名模式

要仅以匿名模式(一般站点,而不是您的帐户)使用该类上传,不需要SecretID。 为此,使用新的构造函数重载:

 Public Sub New(clientID As String) 

这会将该类设置为仅使用Anon,某些方法在使用基于帐户的方法(如GetToken时将返回错误或排除exception。 如果仅使用ClientID对其进行初始化,则它将保持AnonOnly模式,直到使用ClientID和SecretID重新创build对象。

没有真正的理由使用它作为AnonOnly(除非你没有帐户),因为UploadImage方法允许你指定它作为Anon上传文件:

 Function UploadImage(filename As String, Optional Anon As Boolean = False) As imgUrResults 
  • 修改/澄清了imgUrResults枚举

这意味着包罗万象:一些返回表示类检测到一个问题,另外一些则是服务器响应,这些响应只是简单的传递。

  • 删除了IsTokenExpired

IsTokenValid更彻底。 还有其他的方法来获得剩余时间或实际到期。

  • 添加了各种错误捕获/处理
    • 请求PIN时请检查有效的WebBrowser控件
    • 上传图片后,优化获取服务器状态码的方法
    • 重做了一些处理,以优先考虑远程服务器的状态