这很长,因为它或多或少是一个完整的 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
- Log In
- 将您收到的 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
Notes:
- imgUR 类将保留令牌
- 令牌目前将在 1 小时(3600 秒)后过期
D:上传文件
上传使用imgUR.UploadImage(filename, boolAnon)
文件名 - 要上传的文件
boolAnon - 布尔标志。 False = 将此文件上传到您的帐户,而不是 Anon 通用池方法。
Example:
' 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
属性并粘贴到剪贴板。
其他属性、设置和方法
最后一张图片链接(字符串)- 最后上传的图像的 URL
使用剪贴板(Bool) - 如果为 true,imgurAPI 类会将上传图像的链接发布到剪贴板
令牌到期(Date) - 当前令牌过期的日期时间
获取令牌剩余时间() As TimeSpan - 表示当前令牌到期前多长时间的 TimeSpan
公共函数 GetTokenCountdown()As String - TimeRemaining 的格式化字符串
公共只写属性引脚作为字符串 - 获取访问令牌所需的 PIN
公共函数 IsTokenValid()As Boolean - 当前令牌是否有效
公共函数 IsTokenExpired() As Boolean - TimeRemaining 与 DateTime.Now 的简单布尔版本
Notes
- 令牌可以更新或延长。但由于它们持续一个小时,这似乎已经足够了。
- PINS 只能在短时间内有效。一旦 PIN 被交换为令牌,imgurAPI(此类)就会清除 PIN。如果获取令牌时出现问题,您必须先获取新的 PIN(如果您是几分钟前才获取的,则粘贴最后一个 PIN)。
- 上传的图像对全世界来说是不可见的,除非/直到您更改帐户的设置。
- 您可以重置您的 SecretID(设置 -> 应用程序)。如果这样做,您还需要为使用此 API 类的应用程序重置它,并重新编译(或从配置文件中读取它)。
如果您使用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 中学到的信息。截至目前,适用于 imgur API v3。
有nothing自动获取 PIN。您必须以某种方式导航到浏览器中的 URL,然后输入您的帐户名和密码才能获取 PIN。这是设计使然,以便您本人亲自授权某些外部应用程序访问您的帐户内容。
方法一上面使用 .NET WebBrowser 控件来执行此操作。通过这种方法,我们可以确保您和 imgur 类都使用相同的 Endpoint/URL,因为它通过浏览器控件将您发送到那里。
方法二只是你在某个浏览器中去那里,任何浏览器。登录,获取 PIN,并将其粘贴到 imgurAPI 类中。
无论采用哪种方法,正确使用的端点/URL 是:
在使用 imgurAPI 类的表单上使用浏览器,我们显然可以确定您和该类都使用相同的 URL 和 ClientID。代码为DocumentComplete
将 PIN 码提取到文本框中only你仍然需要在类中设置它:
myimgUR.PIN = tbPinCode.Text
PINS 是一次性的,并且会过期。
因此,特别是在开发时,如果您停止代码,添加一些内容然后自然地重新运行,代码将不再具有旧的令牌或PIN。如果最后一个 PIN 是最近使用的并且not提交后,您可能不必再买一个新的,但我发现很难记住是否是这样。
该类将 PINS 视为一次性使用。一旦收到令牌,它就会清除变量,因为它们已被使用并且不再有效。
最终编辑
要使用该类仅以匿名模式上传(到一般站点,而不是您的帐户),不需要 SecretID。为此,请使用新的构造函数重载:
Public Sub New(clientID As String)
这将类设置为仅在 Anon 上工作,并且在使用基于帐户的方法(例如)时,某些方法将返回错误或出现异常GetToken
。如果您仅使用 ClientID 对其进行初始化,它将保留在AnonOnly模式,直到您使用 ClientID 和 SecretID 重新创建对象。
没有真正的理由将其用作 AnonOnly(除非您没有帐户),因为 UploadImage 方法允许您将其指定为按文件匿名上传:
Function UploadImage(filename As String,
Optional Anon As Boolean = False) As imgUrResults
这意味着包罗万象:一些返回指示类检测到的问题,其他返回是简单传递的服务器响应。
IsTokenValid
更彻底。还有其他方法可以获取剩余时间或实际到期时间。
- Added assorted error trapping/handling
- 请求 PIN 时检查有效的 WebBrowser 控件
- 完善图片上传后获取服务器状态码的方法
- 重新设计了一些处理,使远程服务器状态优先于类返回
.