为了开始执行这个"简单"的任务,我已经研究了一个程序,我在这里作为一个例子来跟踪并重现这些步骤,程序可以"匿名"上传一个图像:
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.
我发现了这个问题,但他在回答中说的不清楚(由于我的新手知识),无论如何我试图使用上面的功能,但只是发送BEARER
带有我的ClientSecret
ID 的标题'因为如果我理解好什么的OAuth 2 API文档说,令牌也可以是ClientSecret
标识?,但我没有得到预期的结果.
所以搜索获得正确的访问令牌的方式我已经看到这个问题,这有助于我发现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:请记住,即使获取访问令牌,我也不知道如何在存储它之后使用它.
更新:
我正在尝试使用@ Plutonix解决方案,但是当我尝试请求令牌时,它会抛出异常" 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
Ňɏssa Pøngjǣ.. 7
这很长,因为它或多或少是一个完整的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
如何使用它
该过程需要Web浏览器以供用户导航和请求PIN.对于测试/开发,我使用了一个WebBrowser控件并从返回的页面中获取了PIN.
注意:对于测试,我的imgUR帐户设置为DESKTOP,因为我们是从DESKTOP应用程序发送的.此外,这是您将图像发送到您的帐户.其他人没有办法上传到您的帐户,而不会在应用程序中提供您的秘密ID和/或嵌入您的主ImgUR登录名和密码.这就是ImgUR设计它的方式.
A.创建一个imgUR对象:
Friend imgUR As imgurAPI imgUR = New imgurAPI(, )
B.获得一个Pin - 方法一
' pass the app's WebBrowser Control imgUR.RequestPinBrowser(wb)
这会将您带到一个imgur页面,您必须在其中授权发布PIN才能上传到您的帐户.输入您的帐户名称,密码,单击"允许".将显示带有PIN的新页面.将PIN从网页复制到其他控件,可以将其提供给imgurAPI类.
下面有代码来解析PIN页面并将其转换为另一个控件.
方法二
使用您自己的外部浏览器,转到
https://api.imgur.com/oauth2/authorize? client_id=YOUR_CLIENT_ID&response_type=pin&state=ziggy
登录
将您收到的PIN复制到一个TextBox
或多个东西,以将其发送到imgurAPI:
设置引脚: imgUR.Pin = <
无论哪种方式都是相同的,只需要在表单中包含WebBrowser控件即可.PIN只能在短时间内使用,因此您必须立即使用它来获取访问令牌.
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 =将此文件上传到您的帐户,而不是Anon常规池方法.
例:
' 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
文件上载后,该过程将在响应中查找链接.如果可以解析链接,它将从LastImageLink
属性中可用并粘贴到ClipBoard.
LastImageLink(String) - 上传的最后一张图片的URL
UseClipBoard(Bool) - 当为true时,imgurAPI类将上传图像的链接发布到剪贴板
TokenExpiry(Date) - 当前令牌到期的DateTime
GetTokenTimeRemaining() As TimeSpan - A TimeSpan representing how long before the current token expires
Public Function GetTokenCountdown() As String - Formatted string of TimeRemaining
Public WriteOnly Property Pin As String - the PIN required to get an access token
Public Function IsTokenValid() As Boolean - is the current token valid
Public Function IsTokenExpired() As Boolean - simple Boolean version of TimeRemaining vs DateTime.Now
Notes
Tokens can be renewed or extended. But since they last for an hour, this seems plenty.
PINS are only good for a short time. Once a PIN is exchanged for a token the imgurAPI (this class) clears the PIN. If there is a problem getting the Token, you will have to get a new PIN first (or paste the last one if you just got it a few minutes ago).
Uploaded images are not visible to the world at large unless/until you change the setting on your account.
You can reset your SecretID (Settings -> Applications). If you do, you will need to also reset it for apps using this API class, and recompile (or read it from a config file).
If you use a WebBrowser
control to get a PIN, you can add this code to the DocumentCompleted
event to scrape the PIN from the HTML:
' 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
this is unofficial - info learned from reading the docs and working with the API. Applies to imgur API v3 as of this date.
There is nothing automated about getting a PIN. One way or another you must navigate to a URL in a browser and enter your account name and Password to get a PIN. This is by design so that you, yourself, personally are authorizing some external app to access your account content.
Method One above uses a .NET WebBrowser control to do this. With this method we can be sure that both you and the imgur class are using the same Endpoint/URL because it sends you there via the browser control.
Method Two is just you go there in some browser, any browser. Log in, get a PIN, and paste it to the imgurAPI class.
No matter the method, the correct Endpoint/URL to use is:
https://api.imgur.com/oauth2/authorize?client_id=YOUR_CLIENT_ID&response_type=pin&state=foobar
Using a browser on the form using the imgurAPI class, we obviously we can be sure that both you and the class are using the same URL and ClientID. The code for DocumentComplete
fetches the PIN into the TextBox only you still need to set it in the class:
myimgUR.PIN = tbPinCode.Text
PINS are single use, and expire.
So when developing especially, and you stop the code, add some stuff then rerun naturally, the code will no longer have the old Token or PIN. If the last PIN was recent and not submitted, you might not have to get a new one, but I find it is hard to remember if that is the case.
The class treats PINS as single use. Once a Token has been received, it clears out the variable since they have been used and are no longer valid.
Final Edit
Added an Anon Only mode
To use the class to upload ONLY in Anon mode (to the general site, not your account), the SecretID is not needed. For this, use the new constructor overload:
Public Sub New(clientID As String)
This sets the class to work an Anon ONLY and certain methods will return an error or throe an exception when using Account based methods such as GetToken
. If you initialize it with just ClientID, it remains in AnonOnly mode until you recreate the object with the both the ClientID and SecretID.
There is no real reason to use it as AnonOnly (unless you do not have an account) since the UploadImage method allows you to specify it as an Anon upload by file:
Function UploadImage(filename As String, Optional Anon As Boolean = False) As imgUrResults
Revised/clarified the imgUrResults Enum
This is meant to be all-encompassing: some returns indicate a problem detected by the class, others are server responses which are simply passed along.
Removed IsTokenExpired
IsTokenValid
is more thorough. There are other methods to get the time remaining or the actual Expiry.
Added assorted error trapping/handling
Check for a valid WebBrowser control when requesting a PIN
Refined the method used to get the server status code after an image is uploaded
Reworked some handling to give prioritize remote server status over class returns
.
这很长,因为它或多或少是一个完整的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
该过程需要Web浏览器以供用户导航和请求PIN.对于测试/开发,我使用了一个WebBrowser控件并从返回的页面中获取了PIN.
注意:对于测试,我的imgUR帐户设置为DESKTOP,因为我们是从DESKTOP应用程序发送的.此外,这是您将图像发送到您的帐户.其他人没有办法上传到您的帐户,而不会在应用程序中提供您的秘密ID和/或嵌入您的主ImgUR登录名和密码.这就是ImgUR设计它的方式.
A.创建一个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页面,您必须在其中授权发布PIN才能上传到您的帐户.输入您的帐户名称,密码,单击"允许".将显示带有PIN的新页面.将PIN从网页复制到其他控件,可以将其提供给imgurAPI类.
下面有代码来解析PIN页面并将其转换为另一个控件.
方法二
使用您自己的外部浏览器,转到
https://api.imgur.com/oauth2/authorize? client_id=YOUR_CLIENT_ID&response_type=pin&state=ziggy
登录
将您收到的PIN复制到一个TextBox
或多个东西,以将其发送到imgurAPI:
设置引脚: imgUR.Pin = <<PIN YOU RECEIVED>>
无论哪种方式都是相同的,只需要在表单中包含WebBrowser控件即可.PIN只能在短时间内使用,因此您必须立即使用它来获取访问令牌.
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 =将此文件上传到您的帐户,而不是Anon常规池方法.
例:
' 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
文件上载后,该过程将在响应中查找链接.如果可以解析链接,它将从LastImageLink
属性中可用并粘贴到ClipBoard.
LastImageLink(String) - 上传的最后一张图片的URL
UseClipBoard(Bool) - 当为true时,imgurAPI类将上传图像的链接发布到剪贴板
TokenExpiry(Date) - 当前令牌到期的DateTime
GetTokenTimeRemaining() As TimeSpan - A TimeSpan representing how long before the current token expires
Public Function GetTokenCountdown() As String - Formatted string of TimeRemaining
Public WriteOnly Property Pin As String - the PIN required to get an access token
Public Function IsTokenValid() As Boolean - is the current token valid
Public Function IsTokenExpired() As Boolean - simple Boolean version of TimeRemaining vs DateTime.Now
Notes
Tokens can be renewed or extended. But since they last for an hour, this seems plenty.
PINS are only good for a short time. Once a PIN is exchanged for a token the imgurAPI (this class) clears the PIN. If there is a problem getting the Token, you will have to get a new PIN first (or paste the last one if you just got it a few minutes ago).
Uploaded images are not visible to the world at large unless/until you change the setting on your account.
You can reset your SecretID (Settings -> Applications). If you do, you will need to also reset it for apps using this API class, and recompile (or read it from a config file).
If you use a WebBrowser
control to get a PIN, you can add this code to the DocumentCompleted
event to scrape the PIN from the HTML:
' 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
this is unofficial - info learned from reading the docs and working with the API. Applies to imgur API v3 as of this date.
There is nothing automated about getting a PIN. One way or another you must navigate to a URL in a browser and enter your account name and Password to get a PIN. This is by design so that you, yourself, personally are authorizing some external app to access your account content.
Method One above uses a .NET WebBrowser control to do this. With this method we can be sure that both you and the imgur class are using the same Endpoint/URL because it sends you there via the browser control.
Method Two is just you go there in some browser, any browser. Log in, get a PIN, and paste it to the imgurAPI class.
No matter the method, the correct Endpoint/URL to use is:
https://api.imgur.com/oauth2/authorize?client_id=YOUR_CLIENT_ID&response_type=pin&state=foobar
Using a browser on the form using the imgurAPI class, we obviously we can be sure that both you and the class are using the same URL and ClientID. The code for DocumentComplete
fetches the PIN into the TextBox only you still need to set it in the class:
myimgUR.PIN = tbPinCode.Text
PINS are single use, and expire.
So when developing especially, and you stop the code, add some stuff then rerun naturally, the code will no longer have the old Token or PIN. If the last PIN was recent and not submitted, you might not have to get a new one, but I find it is hard to remember if that is the case.
The class treats PINS as single use. Once a Token has been received, it clears out the variable since they have been used and are no longer valid.
Final Edit
Added an Anon Only mode
To use the class to upload ONLY in Anon mode (to the general site, not your account), the SecretID is not needed. For this, use the new constructor overload:
Public Sub New(clientID As String)
This sets the class to work an Anon ONLY and certain methods will return an error or throe an exception when using Account based methods such as GetToken
. If you initialize it with just ClientID, it remains in AnonOnly mode until you recreate the object with the both the ClientID and SecretID.
There is no real reason to use it as AnonOnly (unless you do not have an account) since the UploadImage method allows you to specify it as an Anon upload by file:
Function UploadImage(filename As String, Optional Anon As Boolean = False) As imgUrResults
Revised/clarified the imgUrResults Enum
This is meant to be all-encompassing: some returns indicate a problem detected by the class, others are server responses which are simply passed along.
Removed IsTokenExpired
IsTokenValid
is more thorough. There are other methods to get the time remaining or the actual Expiry.
Added assorted error trapping/handling
Check for a valid WebBrowser control when requesting a PIN
Refined the method used to get the server status code after an image is uploaded
Reworked some handling to give prioritize remote server status over class returns
.