# File-Name:       google_sg.R           
# Date:            2012-02-10                             
# Author:          Drew Conway (drew.conway@nyu.edu)
# Purpose:         Pierwszy plik kodu do rozdziału 11, z funkcjami budującymi obiekt sieci
#                   igraph na podstawie grafu społecznego Twittera. W roli danych wejściowych
#                   stosujemy dane pobierane z usługi Google SocialGraph API; po ich sparsowaniu
#                   będziemy później mogli wygenerować z nich grafy sieci dla poszczególnych
#                   użytkowników, przygotowane do dalszej analizy.
# Data Used:       Pozyskane z Google SocialGraph API (http://code.google.com/apis/socialgraph/)
# Packages Used:   igraph, RCurl, RJSONIO

# All source code is copyright (c) 2012, under the Simplified BSD License.  
# For more information on FreeBSD see: http://www.opensource.org/licenses/bsd-license.php

# All images and materials produced by this code are licensed under the Creative Commons 
# Attribution-Share Alike 3.0 United States License: http://creativecommons.org/licenses/by-sa/3.0/us/

# All rights reserved.

######################################################
####                                              ####
####         OSTRZEŻENIE DLA CZYTELNIKÓW          ####
####                                              ####
#### W kwietniu 2012 roku usługa Google Social    ####
#### Graph została zarzucona, więc poniższy kod   ####
#### nie może być poprawnie wykonany; został      ####
#### zachowany dla ilustracji sposobu korzystania ####
#### z zewnętrznych źródeł danych z użyciem API.  ####
#### Właściwe analizy można przeprowadzić na      ####
#### podstawie danych dołączonych do kodu, pocho- ####
#### dzących sprzed wyłączenia usługi i dotyczą-  ####
#### cych jednego z Autorów.                      ####
######################################################

# UWAGA: w przypadku uruchamiania z konsoli R (w trybie interaktywnym) należy wykonać polecenie 'setwd'
# w celu zmiany katalogu roboczego na katalog zawierający plik skryptu.
# Inaczej może dojsć do błędów wczytywania danych i zapisu obrazków!

# Załadowanie bibliotek
library(igraph)
library(RCurl)
library(RJSONIO)

# Funkcje do budowania sieci ego dla danego użytkownika Twittera,
# z danych udostępnianych przez Google Social Graph API

# Funkcja wysokiego poziomu, do parsowania kompletu danych GSG API w formacie JSON
twitter.network <- function(user) {
    api.url <- paste("https://socialgraph.googleapis.com/lookup?q=http://twitter.com/",
        user, "&edo=1&edi=1", sep = "")
    api.get <- getURL(api.url)
    # Aby zabezpieczyć się przed nieudanymi żądaniami, odpytujemy usługę
    # w pętli, do skutku, aż uda się odebrać dane.
    while(grepl("Service Unavailable. Please try again later.", api.get)) {
        api.get <- getURL(api.url)
    }
    api.json <- fromJSON(api.get)
    return(build.ego(api.json))
}

# Funkcja wykonująca większość pracy, budując sieć ego na bazie relacji danego użytkownika.
build.ego <- function(json) {
    # Wyłuskujemy tylko dane pochodzące z Twittera
    ego <- find.twitter(names(json$nodes))
    # Budujemy stopnie wejściowe i wyjściowe wokół węzła użytkownika
    nodes.out <- names(json$nodes[[1]]$nodes_referenced)
    if(length(nodes.out) > 0) {
        # Całkowity brak powiązań
        twitter.friends <- find.twitter(nodes.out)
        if(length(twitter.friends) > 0) {
              # Brak powiązań w grafie społecznym Twittera
            friends <- cbind(ego, twitter.friends)
        }
        else {
            friends <- c(integer(0), integer(0))
        }
    }
    else {
        friends <- c(integer(0), integer(0))
    }
    nodes.in <- names(json$nodes[[1]]$nodes_referenced_by)
    if(length(nodes.in) > 0) {
        twitter.followers <- find.twitter(nodes.in)
        if(length(twitter.followers) > 0) {
            followers <- cbind(twitter.followers, ego)
        }
        else {
            followers <- c(integer(0), integer(0))
        }
    }
    else {
        followers <- c(integer(0), integer(0))
    }
    ego.el <- rbind(friends, followers)
    return(ego.el)
}

# Niektóre dane zwracane z GSG nie pochodzą z serwisu Twitter, ale z innych
# serwisów społecznościowych. Piszemy więc funkcję pomocniczą do odfiltrowywania
# danych według źródła pochodzenia.
find.twitter <- function(node.vector) {
    twitter.nodes <- node.vector[grepl("http://twitter.com/", node.vector, fixed = TRUE)]
    if(length(twitter.nodes) > 0) {
        twitter.users <- strsplit(twitter.nodes, "/")
        user.vec <- sapply(1:length(twitter.users),
            function(i) (ifelse(twitter.users[[i]][4] == "account", NA, twitter.users[[i]][4])))
        return(user.vec[which(!is.na(user.vec))])
    }
    else {
        return(character(0))
    }
}

# Następnie budujemy funkcję do konstruowania przeszukiwania metodą 'kuli śniegowej',
# wychodząc od zadanego użytkownika. Dla naszych celów będziemy zawsze ograniczać
# się do odległości dwóch relacji od użytkownika początkowego, ale samą funkcję
# napiszemy tak, żeby można było konstruować również większe sieci.
twitter.snowball <- function(seed, k=2) {
    # Pobranie sieci ego dla danego użytkownika; na jej podstawie zbudujemy resztę grafu sieci.
    snowball.el <- twitter.network(seed)

    # W następnej rundzie próbkowania metodą kuli śniegowej
    # używamy węzły sąsiadujące jako nowe węzły początkowe
    new.seeds <- get.seeds(snowball.el, seed)
    rounds <- 1  # pierwsza runda próbkowania zakończona!

    # Lista wszystkich odwiedzonych już węzłów, utrzymywana dla zmniejszenia liczby wywołań API
    all.nodes <- seed

  
    # Zaczynamy próbkowanie metodą kuli śniegowej...
    while(rounds < k) {
        next.seeds <- c()
        for(user in new.seeds) {
            # Pobieramy dane tylko dla nieodwiedzonych jeszcze węzłów
            if(!user %in% all.nodes) {
                user.el <- twitter.network(user)
                if(dim(user.el)[2] > 0) {
                    snowball.el <- rbind(snowball.el, user.el)
                    next.seeds <- c(next.seeds, get.seeds(user.el, user))
                    all.nodes <- c(all.nodes, user)
                }
            }
        }
        new.seeds <- unique(next.seeds)
        new.seeds <- new.seeds[!which(new.seeds %in% all.nodes)]
        rounds <- rounds + 1
    }
    # Można zakładać, że doszło do zduplikowania wierszy.
    # Dla porządku usuwamy duplikaty, bo prawdziwy graf społęczny Twittera
    # nie zawiera równoległych krawędzi.
    snowball.el <- snowball.el[!duplicated(snowball.el),]
    return(graph.edgelist(snowball.el))
}

# Prosta funkcja pomocnicza zwracająca zbiór unikatowych węzłów
# z danej iteracji próbkowania metodą śnieżnej kuli.
get.seeds <- function(snowball.el, seed) {
    new.seeds <- unique(c(snowball.el[,1], snowball.el[,2]))
    return(new.seeds[which(new.seeds != seed)])
}
  
