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

 寻路灬鸿 发布于 2023-01-09 18:53

为了开始执行这个"简单"的任务,我已经研究了一个程序,我在这里作为一个例子来跟踪并重现这些步骤,程序可以"匿名"上传一个图像:

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带有我的ClientSecretID 的标题'因为如果我理解好什么的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

About the OAuth Model

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

.

1 个回答
  • 这很长,因为它或多或少是一个完整的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
    

    About the OAuth Model

    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

    .

    2023-01-09 18:54 回答
撰写答案
今天,你开发时遇到什么问题呢?
立即提问
热门标签
PHP1.CN | 中国最专业的PHP中文社区 | PNG素材下载 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有