####################################################################### # # # Title: TWISP 1.0 # # Author: pezhore # # # # Description: Evaluates followers for potential spammers based on # # (1) Number of friends v. followers # # (2) Bio keywords # # (3) Number of friends with regards to age of account # # (4) If they've got a bio pic # # # # Requrements: Powershell v2 CTP2, Twitter Account # # # # Usage: .\twisp.ps1 [-minSpammer n] # # Optionally specify minimum index of spamitude # # # # Changelog: # # 1.0 Finalized DM and Blocking capabilities # # 0.9 Added Direct Message Capability # # 0.8 Added Basic GUI # # 0.7 Added full twitter functionality # # 0.1 Initial revision # # # ####################################################################### #requires -version 2 # Parameter for custom index of spamitude param([int]$minSpammer = 8) [void] [Reflection.Assembly]::LoadWithPartialName("System.Web") [void] [Reflection.Assembly]::LoadWithPartialName("System.Text") # Twitter doesn't return 100 codes, so turn expect 100 off [System.Net.ServicePointManager]::Expect100Continue = $false; # Script wide credentials $script:g_creds = $null; # Create Type Twit to contain user information add-type @" public struct Twit { public string Name; public int Followers; public int Friends; public int Age; public string Bio; public bool hasPic; public int index; } "@ #------------------------------------------- # get-BioIndex # Description: returns the spammer index based on the $person's bio # based on keywords split by ',' # Parameters: [twit]$person # Output: [int]$index # #------------------------------------------- function get-bioIndex([twit]$person, [string]$keywords){ # Set default index to 0 $index = 0 # Get $person's Bio $bio = $person.Bio # Begin Evaluation if ($bio -match "(?i)\bweb.?2\.0") {$index += 3} if ($bio -match "(?i)\bfollow.?me") {$index += 4} if ($bio -match "(?i)http://") {$index += 4} if ($bio -match "(?i)\bexpert") {$index += 4} if ($bio -match "(?i)\bsocial") {$index += 5} if ( ($bio -match "(?i)\bmedia.?") -and ($bio -match "(?i)\expert") ) {$index += 5} if ($bio -match "(?i)\\bfollowers") {$index += 7} if ($bio -match "(?i)\bSEO") {$index += 8} if ($bio -match "(?i)\bcash") {$index += 10} return $index } #end function get-FriendIndex #------------------------------------------- # get-FriendIndex # Description: returns the spammer index based on the $person's number of # friends with regard to $person's age (in days) # Parameters: [twit]$person # Output: [int]$index # #------------------------------------------- function get-FriendIndex([twit]$person){ # Set default index to 0 $index = 0 # Begin Evaluation if ( ($person.friends -gt 300) -and ($person.friends -le 700) ){ switch($person.age){ {$_ -le 2} {$index = 5} {($_ -gt 2) -and ($_ -le 10)}{$index = 4} {($_ -gt 10) -and ($_ -le 20)}{$index = 2} {$_ -gt 20}{$index = 1} } }elseif ( ($person.friends -gt 700) -and ($person.friends -le 1000) ){ switch($person.age){ {$_ -le 2} {$index = 8} {($_ -gt 2) -and ($_ -le 10)}{$index = 7} {($_ -gt 10) -and ($_ -le 20)}{$index = 6} {$_ -gt 20}{$index = 5} } }elseif ( ($person.friends -gt 1000) -and ($person.friends -le 3000) ){ switch($person.age){ {$_ -le 2} {$index = 10} {($_ -gt 2) -and ($_ -le 10)}{$index = 10} {($_ -gt 10) -and ($_ -le 20)}{$index = 9} {$_ -gt 20}{$index = 8} } }elseif ( $person.friends -gt 3000 ){ $index = 10 } # Return index return $index } # END function get-FriendIndex #------------------------------------------- # get-RatioIndex # Description: returns the spammer index based on the $person's ratio of # friends to followers # Parameters: [twit]$person # Output: [int]$index # #------------------------------------------- function get-RatioIndex([twit]$person){ # Set default index to 0 $index = 0 # Begin Evaluation if ($person.followers -eq 0){ # $person has no followers switch($person.friends){ {$_ -le 150} {$index = 0} # Follows 150 people with zero followers {($_ -gt 150) -and ($_ -le 300)} {$index = 3} # Follows 151-300 people with zero followers {($_ -gt 300) -and ($_ -le 500)} {$index = 6} # Follows 301-500 people with zero followers {($_ -gt 500) -and ($_ -le 700)} {$index = 8} # Follows 501-700 people with zero followers {$_ -gt 700} {$index = 10} # Follows over 700 people with zero followers } }else{ # $person has followers $ratio = $person.friends / $person.followers # Set Ratio if ($person.friends -le 500){ # Follows less than 500 people switch($ratio){ {$_ -le 1} {$index = -1} # Ratio is less than 1, i.e. more followers than friends {($_ -gt 1) -and ($_ -le 1.5)}{$index = 1} {($_ -gt 1.5) -and ($_ -le 2)}{$index = 3} {($_ -gt 2) -and ($_ -le 2.5)}{$index = 4} {($_ -gt 2.5) -and ($_ -le 3)}{$index = 6} {$_ -gt 3}{$index = 9} } }elseif ( ($person.friends -gt 500) -and ($person.friends -le 2000) ){ # Follows between 501 and 2000 people switch($ratio){ {$_ -le 1} {$index = 0} {($_ -gt 1) -and ($_ -le 1.5)}{$index = 4} {($_ -gt 1.5) -and ($_ -le 2)}{$index = 5} {($_ -gt 2) -and ($_ -le 2.5)}{$index = 7} {($_ -gt 2.5) -and ($_ -le 3)}{$index = 8} {$_ -gt 3}{$index = 10} } }elseif ($person.friends -gt 2000){ # Follows more than 2000 people switch($ratio){ {$_ -le 1} {$index = 1} {($_ -gt 1) -and ($_ -le 1.5)}{$index = 5} {($_ -gt 1.5) -and ($_ -le 2)}{$index = 7} {($_ -gt 2) -and ($_ -le 2.5)}{$index = 9} {($_ -gt 2.5) -and ($_ -le 3)}{$index = 10} {$_ -gt 3}{$index = 10} } } # end evaluation of ratio when $person has followers } #end else ($person has followers) # Return index return $index } #END function get-RatioIndex #------------------------------------------- # eval-TwitterUser # Description: returns the spammer index for $person # Parameters: [twit]$person # Output: [int]$index # #------------------------------------------- Function eval-TwitterUser([twit]$person){ # Evaluate Ratio $ratioIndex = get-RatioIndex $person # Evaluate Friends $friendIndex = get-FriendIndex $person # Evaluate Bio $bioIndex = get-BioIndex $person # Evaluate Picture if (!($person.hasPic)){ $picIndex = 2 }else { $picIndex = 0 } #Compute index $index = $ratioIndex + $friendIndex + $picIndex + $bioIndex return $index } #END Function eval-TwitterUser #------------------------------------------- # Get-TwitterAge # Description: returns the age in days of a twitter date # Parameters: [string]$createdAt - in twitter date format (i.e. Tue Mar 06 16:56:23 +0000 2007) # Output: [int]$diff - difference between now and twitter creation day. # #------------------------------------------- Function Get-TwitterAge([string] $createdAt){ # Do some regex magic, split then merge back to a readable system.datetime string $date = $createdAt -replace " \+(\w*)", "" $date = $date.split(' ') $date = $date[0]+", "+$date[2]+" "+$date[1]+" "+$date[4] $date = [system.datetime]::parse($date) $now = get-date $diff = $now.Subtract($date).days return $diff } #END Function Get-TwitterAge #------------------------------------------- # Get-TwitterFriendOrFollower # Description: Used to parse XML from twitter for either friend or follower # depending upon $command, returns array of Twits # Parameters: [string]$Username - Twitter username # [string]$Password - Twitter password # [string]$Command - Either Friend or Follower # [string]$ID - Optional # Output: [Twit]$TwitList - Array of Twits # #------------------------------------------- Function Get-TwitterFriendOrFollower ( [string] $Username, [string] $Password, [string] $Command, [string] $ID ) { if ($WebClient -eq $null) { $Global:WebClient=new-object System.Net.WebClient } if ($Username) { $WebClient.Credentials = (New-Object System.Net.NetworkCredential -argumentList $Username, $Password) } if ($ID) { $URL="http://twitter.com/statuses/$Command/$ID.xml?page=" } else { $URL="http://twitter.com/statuses/$Command.xml?page=" } $page = 1 $me = new-object Twit $friends = @() [Twit[]]$TwitList = @() do { $rawResponse = $WebClient.DownloadString($url+$Page) $response = (([xml]($rawResponse)).users.user) if ($response -ne $null) { foreach ($u in $response) { $me.name = $u["screen_name"].psbase.InnerText $me.followers = $u["followers_count"].psbase.InnerText $me.friends = $u["friends_count"].psbase.InnerText $me.bio = $u["description"].psbase.InnerText $me.hasPic = (!($u["profile_image_url"].psbase.InnerText -eq $NULL)) $me.age = get-TwitterAge $u["created_at"].psbase.InnerText $TwitList += $me } } $page ++ } while ($response.count -gt 0) return $TwitList } #------------------------------------------- # Execute-HTTPPost # Description: Execute HTTP POST call on provided $url with $data # Parameters: [string]$url - Url to request # [string]$data - Data to include in post request #------------------------------------------- function Execute-HTTPPostCommand() { param([string] $url = $null, [string] $data = $null); if ( $url -and $data ) { [System.Net.WebRequest]$webRequest = [System.Net.WebRequest]::Create($url); $webRequest.ServicePoint.Expect100Continue = $false; if ( $url.ToLower().Contains("twitter.com") ) { $webRequest.Credentials = Get-TwitterCredentials $webRequest.PreAuthenticate = $true; } $webRequest.ContentType = "application/x-www-form-urlencoded"; $webRequest.Method = "POST"; $webRequest.Headers.Add("X-Twitter-Client", "PoshTweet"); $webRequest.Headers.Add("X-Twitter-Version", "1.0"); $webRequest.Headers.Add("X-Twitter-URL", "http://devcentral.f5.com/poshtweet"); [byte[]]$bytes = [System.Text.Encoding]::UTF8.GetBytes($data); $webRequest.ContentLength = $bytes.Length; [System.IO.Stream]$reqStream = $webRequest.GetRequestStream(); $reqStream.Write($bytes, 0, $bytes.Length); $reqStream.Flush(); [System.Net.WebResponse]$resp = $webRequest.GetResponse(); $rs = $resp.GetResponseStream(); [System.IO.StreamReader]$sr = New-Object System.IO.StreamReader -argumentList $rs; [string]$results = $sr.ReadToEnd(); $results; } } #------------------------------------------- # New-TwitterDirectMessage # Description: Sends a direct message to user $user with message $text # Parameters: [string]$user - Target Twitter user # [string]$text - Text to send #------------------------------------------- function New-TwitterDirectMessage() { param([string]$user = $null, [string]$text = $null); if ( $user -and $text ){ $enctext = [System.Web.HttpUtility]::UrlEncode("$text"); $results = Execute-HTTPPostCommand "http://twitter.com/direct_messages/new.xml" "user=$user&text=$enctext" } } #------------------------------------------- # block-TwitterUsers # Description: Blocks each twitter user in string $spammers # Parameters: [string]$spammers - String of Twitter spammers #------------------------------------------- function block-TwitterUsers([string]$spammers){ $split = $spammers.split('@') foreach ($u in $split){ if ($u -ne $null){ $spam += $u } } $spam = $spam.split(' ') foreach ($id in $spam){ $results = Execute-HTTPPostCommand "http://twitter.com/blocks/create/${id}.xml" "id=${id}"; } } #------------------------------------------- # Begin GUI #------------------------------------------- #------------------------------------------- # PopUp # Description: Displays a popup with text $text # Parameters: [string]$text - Text to display in popup #------------------------------------------- function PopUp ([string]$text){ #region Import the Assemblies [reflection.assembly]::loadwithpartialname("System.Windows.Forms") | Out-Null [reflection.assembly]::loadwithpartialname("System.Drawing") | Out-Null #endregion #region Generated Form Objects $form1 = New-Object System.Windows.Forms.Form $Reportingok = New-Object System.Windows.Forms.Button $label1 = New-Object System.Windows.Forms.Label $InitialFormWindowState = New-Object System.Windows.Forms.FormWindowState #endregion Generated Form Objects #---------------------------------------------- #Generated Event Script Blocks #---------------------------------------------- #Provide Custom Code for events specified in PrimalForms. $Reportingok_OnClick= { $form1.close() } $OnLoadForm_StateCorrection= {#Correct the initial state of the form to prevent the .Net maximized form issue $form1.WindowState = $InitialFormWindowState } #---------------------------------------------- #region Generated Form Code $form1.Text = 'Twisp' $form1.Name = 'form1' $form1.StartPosition = 4 $form1.DataBindings.DefaultDataSourceUpdateMode = 0 $System_Drawing_Size = New-Object System.Drawing.Size $System_Drawing_Size.Width = 161 $System_Drawing_Size.Height = 89 $form1.ClientSize = $System_Drawing_Size $Reportingok.TabIndex = 1 $Reportingok.Name = 'Reportingok' $System_Drawing_Size = New-Object System.Drawing.Size $System_Drawing_Size.Width = 75 $System_Drawing_Size.Height = 23 $Reportingok.Size = $System_Drawing_Size $Reportingok.UseVisualStyleBackColor = $True $Reportingok.Text = 'OK' $System_Drawing_Point = New-Object System.Drawing.Point $System_Drawing_Point.X = 45 $System_Drawing_Point.Y = 54 $Reportingok.Location = $System_Drawing_Point $Reportingok.DataBindings.DefaultDataSourceUpdateMode = 0 $Reportingok.add_Click($Reportingok_OnClick) $form1.Controls.Add($Reportingok) $label1.TabIndex = 0 $System_Drawing_Size = New-Object System.Drawing.Size $System_Drawing_Size.Width = 108 $System_Drawing_Size.Height = 23 $label1.Size = $System_Drawing_Size $label1.Text = $text $System_Drawing_Point = New-Object System.Drawing.Point $System_Drawing_Point.X = 12 $System_Drawing_Point.Y = 9 $label1.Location = $System_Drawing_Point $label1.DataBindings.DefaultDataSourceUpdateMode = 0 $label1.Name = 'label1' $form1.Controls.Add($label1) #endregion Generated Form Code #Save the initial state of the form $InitialFormWindowState = $form1.WindowState #Init the OnLoad event to correct the initial state of the form $form1.add_Load($OnLoadForm_StateCorrection) #Show the Form $form1.ShowDialog()| Out-Null } #End PopUp #------------------------------------------- # GenerateForm # Description: Displays main form. # Parameters: [twit]$spammers - array of Twits #------------------------------------------- function GenerateForm ( $spammers ) { #region Import the Assemblies [reflection.assembly]::loadwithpartialname("System.Windows.Forms") | Out-Null [reflection.assembly]::loadwithpartialname("System.Drawing") | Out-Null #endregion #region Generated Form Objects $TwispMain = New-Object System.Windows.Forms.Form $names = New-Object System.Windows.Forms.CheckedListBox $textBox2 = New-Object System.Windows.Forms.TextBox $exit = New-Object System.Windows.Forms.Button $report = New-Object System.Windows.Forms.Button $block = New-Object System.Windows.Forms.Button $InitialFormWindowState = New-Object System.Windows.Forms.FormWindowState #endregion Generated Form Objects #---------------------------------------------- #Generated Event Script Blocks #---------------------------------------------- #Provide Custom Code for events specified in PrimalForms. $block_OnClick= { $spammers = @() # Strip out the '@' before each name foreach ($u in $names.checkedItems){ $temp = $u.split(' ') $block = $temp[0] $block = '@'+$block $spammers += $block } Block-TwitterUsers -spammers $spammers popup "Blocking complete!" } $exit_OnClick= { $TwispMain.close() } $report_OnClick= { $spammers = @() # Strip out the '@' before each name foreach ($u in $names.checkedItems){ $temp = $u.split(' ') $block = $temp[0] $block = '@'+$block $spammers += $block } # Send DM to @spam reporting the users $user = "spam" New-TwitterDirectMessage $user $spammers popup "Reporting complete!" # Block the users Block-TwitterUsers -spammers $spammers popup "Blocking complete!" } $handler_checkedListBox1_SelectedIndexChanged= { } $OnLoadForm_StateCorrection= {#Correct the initial state of the form to prevent the .Net maximized form issue $TwispMain.WindowState = $InitialFormWindowState } #---------------------------------------------- # Generate Form stuff $TwispMain.AutoSize = $True $TwispMain.Text = 'Twisp' $TwispMain.Name = 'TwispMain' $TwispMain.StartPosition = 4 $TwispMain.DataBindings.DefaultDataSourceUpdateMode = 0 $System_Drawing_Size = New-Object System.Drawing.Size $System_Drawing_Size.Width = 582 $System_Drawing_Size.Height = 502 $TwispMain.ClientSize = $System_Drawing_Size $names.FormattingEnabled = $True $System_Drawing_Size = New-Object System.Drawing.Size $System_Drawing_Size.Width = 546 $System_Drawing_Size.Height = 319 $names.Size = $System_Drawing_Size $names.DataBindings.DefaultDataSourceUpdateMode = 0 $count = 0 foreach ($n in $spammers){ $text = $n.name+" "+$n.index $names.Items.Add($text)|Out-Null $count ++ } $names.Name = 'names' $System_Drawing_Point = New-Object System.Drawing.Point $System_Drawing_Point.X = 13 $System_Drawing_Point.Y = 117 $names.Location = $System_Drawing_Point $names.TabIndex = 12 $names.add_SelectedIndexChanged($handler_checkedListBox1_SelectedIndexChanged) $TwispMain.Controls.Add($names) $textBox2.Multiline = $True $System_Drawing_Size = New-Object System.Drawing.Size $System_Drawing_Size.Width = 554 $System_Drawing_Size.Height = 94 $textBox2.Size = $System_Drawing_Size $textBox2.DataBindings.DefaultDataSourceUpdateMode = 0 $textBox2.ReadOnly = $True $textBox2.BorderStyle = 0 $textBox2.Text = 'The following users are canidates for being spam followers. Their index of spamitude is indicated by the number following their name, the lower the number - the less likely they are spammers. I recommend you visit their twitter page prior to blocking or reporting the user unless you are sure they are spammers. Any questions? Shoot me a reply @pezhore.' $textBox2.Name = 'textBox2' $System_Drawing_Point = New-Object System.Drawing.Point $System_Drawing_Point.X = 5 $System_Drawing_Point.Y = 5 $textBox2.Location = $System_Drawing_Point $textBox2.TabIndex = 10 $TwispMain.Controls.Add($textBox2) $exit.TabIndex = 5 $exit.Name = 'exit' $System_Drawing_Size = New-Object System.Drawing.Size $System_Drawing_Size.Width = 75 $System_Drawing_Size.Height = 23 $exit.Size = $System_Drawing_Size $exit.UseVisualStyleBackColor = $True $exit.Text = 'exit' $System_Drawing_Point = New-Object System.Drawing.Point $System_Drawing_Point.X = 492 $System_Drawing_Point.Y = 467 $exit.Location = $System_Drawing_Point $exit.DataBindings.DefaultDataSourceUpdateMode = 0 $exit.add_Click($exit_OnClick) $TwispMain.Controls.Add($exit) $report.TabIndex = 4 $report.Name = 'report' $System_Drawing_Size = New-Object System.Drawing.Size $System_Drawing_Size.Width = 75 $System_Drawing_Size.Height = 23 $report.Size = $System_Drawing_Size $report.UseVisualStyleBackColor = $True $report.Text = 'report' $System_Drawing_Point = New-Object System.Drawing.Point $System_Drawing_Point.X = 104 $System_Drawing_Point.Y = 467 $report.Location = $System_Drawing_Point $report.DataBindings.DefaultDataSourceUpdateMode = 0 $report.add_Click($report_OnClick) $TwispMain.Controls.Add($report) $block.TabIndex = 3 $block.Name = 'block' $System_Drawing_Size = New-Object System.Drawing.Size $System_Drawing_Size.Width = 75 $System_Drawing_Size.Height = 23 $block.Size = $System_Drawing_Size $block.UseVisualStyleBackColor = $True $block.Text = 'block' $System_Drawing_Point = New-Object System.Drawing.Point $System_Drawing_Point.X = 13 $System_Drawing_Point.Y = 467 $block.Location = $System_Drawing_Point $block.DataBindings.DefaultDataSourceUpdateMode = 0 $block.add_Click($block_OnClick) $TwispMain.Controls.Add($block) # End Generate Form stuff #---------------------------------------------- #Save the initial state of the form $InitialFormWindowState = $TwispMain.WindowState #Init the OnLoad event to correct the initial state of the form $TwispMain.add_Load($OnLoadForm_StateCorrection) #Show the Form $TwispMain.ShowDialog()| Out-Null } # END function GenerateForm ($spammers) #------------------------------------------- # End GUI #------------------------------------------- #------------------------------------------- # Set-TwitterCredentials # Description: Sets the script's credentials to the specified # username $user and password $pass. If no username # or password is provided, get the credentials # Parameters: [string]$user - Credential username # [string]$pass - Credential password #------------------------------------------- function Set-TwitterCredentials() { param([string]$user = $null, [string]$pass = $null); if ( $user -and $pass ){ $script:g_creds = New-Object System.Net.NetworkCredential -argumentList ($user, $pass); } else { $creds = Get-TwitterCredentials; } } #------------------------------------------- # get-TwitterCredentials # Description: Sets the script's credentials to the specified # username $user and password $pass. If no username # or password is provided, get the credentials # Parameters: [string]$user - Credential username # [string]$pass - Credential password #------------------------------------------- function Get-TwitterCredentials() { # Check to see if global credentials have not been set if ( $null -eq $g_creds ){ trap { Write-Error "ERROR: You must enter your Twitter credentials for Twisp to work!"; continue; } # Get the Credentials and set global g_creds $c = Get-Credential if ( $c ){ $user = $c.GetNetworkCredential().Username; $pass = $c.GetNetworkCredential().Password; $script:g_creds = New-Object System.Net.NetworkCredential -argumentList ($user, $pass); } } $script:g_creds; } ####################################################################### ####################################################################### ## END OF FUNCTIONS ## ####################################################################### ####################################################################### # Set default null variables [Twit[]]$dontFollow = @() # Array of Twits the user doesn't follow [Twit[]]$spammer = @() # Array of Twits assessed to be spammers # Get Credentials from user Get-TwitterCredentials| Out-Null # Populate array of friends [Twit[]]$friends = Get-TwitterFriendOrFollower $g_creds.UserName $g_creds.Password "friends" # Populate array of followers [Twit[]]$followers = Get-TwitterFriendOrFollower $g_creds.Username $g_creds.Password "followers" # Iterate through followers foreach ($u in $followers){ $found = $FALSE # Iterate through friends foreach ($t in $friends){ if ($u.name -eq $t.name){$found = $TRUE} # If the follower is also a friend, not a spammer } if (!($found)){ $dontFollow += $u # Follower isn't a friend } } # Evaluate all twits not followed for Index of Spamitude foreach ($u in $dontFollow){ $index = eval-Twitteruser $u # If index is greater than min value add to spammer list if ($index -gt $minSpammer){ $u.index = $index $spammer += $u } } # Kick off GUI passing array of generateform $spammer