フォロワーの差分(OAuth 版)

http://d.hatena.ne.jp/chabom/20100818/1282092637 のコードはBasic 認証でもう使えないので、OAuth 対応した。

利用するには、Consumer key, Consumer secret, Access Token, Access Token Secret は Twitter Developer Platform — Twitter Developers でアプリを登録して取得する必要があります。

signature 作るところで、16 進表記は大文字にしないといけないのに気づかずにハマッた。System.Web.HttpUtility.UrlEncode は小文字で出力される。バカチン。

Hexadecimal characters in encodings MUST be upper case

http://oauth.net/core/1.0/#encoding_parameters
$API_URL    = "https://api.twitter.com/1/statuses/followers.xml"
$API_METHOD = "GET"
$OLD_FILE   = "followers_old.txt"

# System.Web.HttpUtility.UrlEncode だと 16 進数が小文字になってダメなので
function Encode-URL($s){
    ($s.ToCharArray() | foreach {
			if ($_ -match "[a-z]|[-,_,.,~,)]|[0-9]") { 
				$_
			} else {
				"{0}{1:X}" -f "%", [System.Text.Encoding]::ASCII.GetBytes($_)[0]
			}
		}) -join ""
}
	
function Create-OAuthHeader {
	param ( [string]$url, [string]$method )

	$CONSUMER_KEY    = "Consumer key"
	$CONSUMER_SECRET = "Consumer secret"
	$TOKEN           = "Access Token"
	$TOKEN_SECRET    = "Access Token Secret"

	# unix-time
	$timestamp = [int](((Get-Date) - [DateTime]"1970/01/01 09:00:00").TotalSeconds)

	$rand = New-Object System.Random
	$nonce = $rand.next(123400, 9999999).ToString()

	$params = @("oauth_consumer_key=$CONSUMER_KEY",
							"oauth_nonce=$nonce",
							"oauth_signature_method=HMAC-SHA1",
							"oauth_timestamp=$timestamp",
							"oauth_token=$TOKEN",
							"oauth_version=1.0") | sort
	
	# signature を作る
	$base = @( $method, (Encode-URL $url), (Encode-URL ($params -join "&")) )
	$base = $base -join "&"
	$hmac = New-Object System.Security.Cryptography.HMACSHA1
	$hmac.Key = [System.Text.Encoding]::ASCII.GetBytes($CONSUMER_SECRET + "&" + $TOKEN_SECRET)
	$hash = $hmac.ComputeHash( [System.Text.Encoding]::ASCII.GetBytes($base) )
	$signature = Encode-URL( [System.Convert]::ToBase64String($hash) )
	
	$params += "oauth_signature=$signature"
	return $params -join ","
}

$wc = New-Object System.Net.WebClient

$users = @()
$cursor = -1
while ($cursor -ne 0) {
	$header = Create-OAuthHeader $API_URL $API_METHOD
	$wc.Headers.Set("Authorization", "OAuth $header")

	$xml = [xml] $wc.DownloadString($API_URL + "?cursor=$cursor")
	$cursor = $xml.users_list.next_cursor

  # $users に 現在のfollowersを入れる
	$xml.users_list.users.user | foreach { $users += $_.screen_name }
}
$wc.Dispose()

# diff
Compare-Object $(Get-Content $OLD_FILE) $users -SyncWindow 100

# 現在のfollowersをold.txtに出力する
Set-Content -Path $OLD_FILE -Value $users

"current followers: " + $users.length