├── .gitignore ├── build.sbt ├── lib └── gephi-toolkit.jar ├── readme.md ├── src └── main │ └── scala │ ├── config │ └── parser.scala │ ├── main.scala │ ├── renren │ ├── friend.scala │ ├── friendnetwork.scala │ ├── networkbase.scala │ ├── renren.scala │ ├── renrenhttpclient.scala │ └── sixdegreeverifier.scala │ └── utils │ ├── dumper.scala │ ├── grapher.scala │ ├── httpparser.scala │ └── reporter.scala └── userinfo.ini.example /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | Output* 3 | .java-version 4 | headless_simple.* 5 | userinfo.ini 6 | network.txt 7 | toolkit-javadoc/ -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | lazy val root = (project in file(".")). 2 | settings( 3 | name := "sblog", 4 | version := "1.0", 5 | scalaVersion := "2.11.6", 6 | libraryDependencies += "org.scala-lang" % "scala-compiler" % "2.11.6", 7 | libraryDependencies += "org.apache.httpcomponents" % "httpclient" % "4.4.1", 8 | libraryDependencies += "org.jsoup" % "jsoup" % "1.8.2", 9 | libraryDependencies += "org.scala-lang" % "scala-actors" % "2.11.6" 10 | ) -------------------------------------------------------------------------------- /lib/gephi-toolkit.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dyweb/scala-renren/418eab220b9c8d62ab4bf471a21afca8775a4693/lib/gephi-toolkit.jar -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # scarenren 2 | 3 | scarenren是一个基于scala的人人分析工具 4 | 5 | ## 构建 6 | 7 | 项目使用sbt构建,构建环境为`jdk1.7 scala2.11 sbt 0.13.8` 8 | 9 | ## 输出 10 | 11 | ### 好友学校统计 12 | 13 | 在生成的`Output-Data.txt`中 14 | 15 | ### 好友关系图 16 | 17 | 以JFrame的形式展示,并生成在`headless_simple.png`和`headless_simple.svg`中 18 | 19 | ### 文本格式的好友关系 20 | 21 | 在`network.txt`中,是语言无关的,可以基于该文本做很多事情。其格式为 22 | 23 | -Friend(uid, location, name, url) // 1 24 | --Friend(uid, location, name, url) // 2 25 | --Friend(uid, location, name, url) // 3 26 | ... 27 | -Friend(uid, location, name, url) // 2 28 | --Friend(uid, location, name, url) // 5 29 | 30 | 其中-代表是节点,--代表由之前-节点指向该--节点的边。比如在此例中,行1为一个节点,行2代表一个由行1节点指向行2节点的一条边。 31 | 32 | PS:存储方式略显拙计。 33 | 34 | ## 使用 35 | 36 | 首先,新建`userinfo.ini`文件,内容与`userinfo.ini.example`内类似,用于爬取。 37 | 38 | 然后,编译并运行需要指令`sbt run`,代码只在os x环境下使用过,不知道其他系统会不会有问题,第一次编译可能需要时间比较长,需要下载各种库,之后会快很多。 39 | 40 | PS: gephi在导出的时候,会有报错,形如 41 | 42 | java.lang.InterruptedException: sleep interrupted 43 | at java.lang.Thread.sleep(Native Method) 44 | at org.gephi.data.attributes.event.AttributeEventManager.run(AttributeEventManager.java:87) 45 | at java.lang.Thread.run(Thread.java:745) 46 | java.lang.InterruptedException 47 | at java.lang.Object.wait(Native Method) 48 | at java.lang.Object.wait(Object.java:503) 49 | at org.gephi.graph.dhns.core.GraphStructure$ViewDestructorThread.run(GraphStructure.java:240) 50 | 51 | 但是对结果没有影响,run的结果还会是success。这些异常似乎catch不住,有待解决。 52 | 53 | ## 代码阅读指南(程序执行指南) 54 | 55 | 程序最主要的类是在renren.scala中,首先程序会读取在`userinfo.ini`中的用户名密码,然后取得用户的好友列表,最后根据好友列表,生成好友关系图。样例如下,原本是带有名字标签在图上的,但为了保护隐私,已和谐。 56 | 57 |
58 | 59 |
60 | 61 | 另外在画图的时候使用了gephi-toolkit,现行的情况是,根据[Modularity](https://en.wikipedia.org/wiki/Modularity_\(networks\))来决定节点的颜色,节点度来决定节点的大小。 62 | 63 | ## 未完成 64 | 65 | * 优化friendnetwork.scala中的marshal,不需要读入文件时都存入内存,边读入边遍历,可使用解释器模式 66 | * grapher.scala优化画图风格 67 | * actor使用到了外部mutable的变量,会有未定义的行为,以后改善。。。 68 | 69 | ### PS 70 | 71 | Code Style在现在看来极其差劲。。。大家将就着看看吧。。 72 | -------------------------------------------------------------------------------- /src/main/scala/config/parser.scala: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import scala.collection.{ mutable, immutable, generic } 4 | import scala.collection.mutable.ListBuffer 5 | import scala.io.Source 6 | import utils.Reporters._ 7 | 8 | class Parser(filename: String, reporter: Reporter) { 9 | var username: String = "" 10 | var pwd: String = "" 11 | 12 | class Context(filename: String) { 13 | var ctx: ListBuffer[String] = ListBuffer() 14 | 15 | for (line <- Source.fromFile(filename).getLines()) { 16 | ctx += line.toString() 17 | } 18 | } 19 | 20 | class AbstractExp() { 21 | def interpret(context: Context): Unit = { 22 | if (context.ctx.length == 0) { 23 | reporter.error("The has no content.") 24 | } else { 25 | var line = context.ctx.head 26 | if (line != "") { 27 | reporter.error("account needed in ") 28 | } else { 29 | context.ctx = context.ctx.tail 30 | var accountExp = new AccountExp().interpret(context) 31 | } 32 | } 33 | } 34 | } 35 | 36 | class AccountExp() extends AbstractExp { 37 | 38 | override def interpret(context: Context): Unit = { 39 | var userLine = context.ctx.head 40 | context.ctx = context.ctx.tail 41 | 42 | var pwdLine = context.ctx.head 43 | context.ctx = context.ctx.tail 44 | 45 | username = userLine.split("=")(1) 46 | pwd = pwdLine.split("=")(1) 47 | 48 | reporter.info("Get the info of <" + username + ">") 49 | } 50 | } 51 | 52 | var context: Context = new Context(filename) 53 | var exp: AbstractExp = new AbstractExp() 54 | def run() = { 55 | exp.interpret(context) 56 | } 57 | 58 | def getUsername(): String = { 59 | if (username != "") { 60 | return username 61 | } else { 62 | reporter.error("username unknown") 63 | return "-1" 64 | } 65 | } 66 | 67 | def getPwd(): String = { 68 | if (pwd != "") { 69 | return pwd 70 | } else { 71 | reporter.error("pwd unknown") 72 | return "-1" 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /src/main/scala/main.scala: -------------------------------------------------------------------------------- 1 | import config.Parser 2 | import renren.Renren 3 | import utils.Reporters._ 4 | import utils.Dumper 5 | 6 | object Main { 7 | val reporter: Reporter = new Reporter() 8 | reporter.attach(new ConsoleReporterHandler()) 9 | reporter.open() 10 | val dumper: Dumper = new Dumper("Output-Data.txt") 11 | 12 | def main(args: Array[String]): Unit = { 13 | val parser = new Parser("userinfo.ini", reporter) 14 | parser.run() 15 | val username = parser.getUsername() 16 | val pwd = parser.getPwd() 17 | 18 | val renren = new Renren(username, pwd, reporter, dumper) 19 | // renren.runSixDegreeVerifier 20 | renren.runFriendNetwork 21 | } 22 | } -------------------------------------------------------------------------------- /src/main/scala/renren/friend.scala: -------------------------------------------------------------------------------- 1 | package renren 2 | 3 | class Friend(var uid: String, var school: String, var name: String, var link: String) extends Serializable { 4 | override def toString(): String = { 5 | s"Friend($uid, $school, $name, $link)" 6 | } 7 | 8 | override def equals(o: Any) = o match { 9 | case that: Friend => that.uid.equalsIgnoreCase(this.uid) 10 | case _ => false 11 | } 12 | 13 | override def hashCode = uid.toUpperCase.hashCode 14 | 15 | // Friend($uid, $school, $name, $link) 16 | def parse(s: String) = { 17 | var content = s.substring(7, s.size - 2) 18 | var parameterList = content.split(", ") 19 | uid = parameterList(0) 20 | school = parameterList(1) 21 | name = parameterList(2) 22 | link = parameterList(3) 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /src/main/scala/renren/friendnetwork.scala: -------------------------------------------------------------------------------- 1 | package renren 2 | 3 | import scala.collection.mutable.{ HashMap } 4 | import scala.collection.mutable.ListBuffer 5 | import scala.Serializable 6 | import scala.io.Source 7 | import scala.actors.Actor 8 | import scala.actors.Actor._ 9 | 10 | // jsoup 11 | import org.jsoup.Jsoup; 12 | import org.jsoup.helper.Validate; 13 | import org.jsoup.nodes.Document; 14 | import org.jsoup.nodes.Element; 15 | import org.jsoup.select.Elements; 16 | 17 | // project utils 18 | import utils.Reporters._ 19 | import utils.Dumper 20 | import utils.Grapher 21 | 22 | import java.io._ 23 | import java.nio.file.{ Paths, Files } 24 | 25 | class FriendNetwork(reporter: Reporter) extends NetworkBase(reporter: Reporter) { 26 | val me = this 27 | class FriendActor(renren: Renren, superActor: SuperActor) extends Actor { 28 | def act() = { 29 | var new_renren = new Renren(renren.username, renren.pwd, renren.reporter, renren.dumper) 30 | var flag = true 31 | while (flag) { 32 | receive { 33 | case k: Friend => 34 | reporter.info("Begin parsing: " + k.toString) 35 | var workUid = k.uid 36 | // tricky 37 | if (new_renren.getFriendCount(workUid) < 1000) { 38 | var friList: List[Friend] = me.translate2Friend(new_renren.getFriendList(workUid)) 39 | for (friend <- friList) { 40 | if (me.network.exists(_._1 == friend)) { 41 | me.network(k) = friend :: me.network(k) 42 | } 43 | } 44 | } 45 | sender ! "end" 46 | case "recruit" => 47 | reporter.info("actor recruited") 48 | sender ! "end" 49 | case "Finish" => 50 | sender ! "ready" 51 | reporter.info("actor exit") 52 | // flag = false 53 | exit() 54 | } 55 | } 56 | } 57 | } 58 | 59 | class SuperActor(thread: Int, var list: List[Friend], renren: Renren) extends Actor { 60 | var actorList: List[FriendActor] = List() 61 | var flag = true 62 | var times = -1 63 | for (i <- 1 to thread) { 64 | reporter.info("create " + i.toString + "th actor") 65 | val actor = new FriendActor(renren, this) 66 | actorList = actor :: actorList 67 | } 68 | def act() = { 69 | var exitnum = thread 70 | while (flag) { 71 | receive { 72 | case "begin" => 73 | reporter.info("superActor begins to work") 74 | for (actor <- actorList) { 75 | actor.start ! "recruit" 76 | } 77 | case "end" => 78 | if (list.size != 0 && times != 0) { 79 | times = times - 1 80 | sender ! list.head 81 | list = list.tail 82 | } else { 83 | reporter.info("Finish The parsing") 84 | for (actor <- actorList) { 85 | actor ! "Finish" 86 | } 87 | } 88 | case "ready" => 89 | exitnum = exitnum - 1 90 | reporter.msg(thread - exitnum + " Acotr(s) ready to exit") 91 | if (exitnum == 0) { 92 | flag = false 93 | reporter.info("Dumping to file") 94 | dump 95 | exportGraph 96 | exit 97 | } 98 | } 99 | } 100 | } 101 | } 102 | 103 | // parse all the friend you have and get the network 104 | def parseFriend(renren: Renren, thread: Int): Unit = { 105 | if (hasFile == true) { 106 | exportGraph 107 | return 108 | } 109 | val me = this 110 | var list = network.keySet.toList 111 | 112 | val superActor: SuperActor = new SuperActor(thread, list, renren) 113 | superActor.start ! "begin" 114 | // list.par.map { 115 | // ele => mapFunc(renren, ele) 116 | // } 117 | return 118 | } 119 | 120 | def exportGraph() = { 121 | val grapher = new Grapher(network, reporter) 122 | grapher.script() 123 | } 124 | } -------------------------------------------------------------------------------- /src/main/scala/renren/networkbase.scala: -------------------------------------------------------------------------------- 1 | package renren 2 | 3 | import scala.collection.mutable.{ HashMap } 4 | import scala.collection.mutable.ListBuffer 5 | import scala.Serializable 6 | import scala.io.Source 7 | import scala.actors.Actor 8 | import scala.actors.Actor._ 9 | 10 | // jsoup 11 | import org.jsoup.Jsoup; 12 | import org.jsoup.helper.Validate; 13 | import org.jsoup.nodes.Document; 14 | import org.jsoup.nodes.Element; 15 | import org.jsoup.select.Elements; 16 | 17 | // project utils 18 | import utils.Reporters._ 19 | import utils.Dumper 20 | import utils.Grapher 21 | 22 | import java.io._ 23 | import java.nio.file.{ Paths, Files } 24 | 25 | class NetworkBase(reporter: Reporter) { 26 | var filename = "network.txt" 27 | var hasFile = false 28 | if (Files.exists(Paths.get(filename))) { 29 | reporter.warn("found the network data in local disk, if you don't want it, rm the ") 30 | hasFile = true 31 | } 32 | 33 | def manOf[T: Manifest](t: T): Manifest[T] = manifest[T] 34 | 35 | // marshal class: responsible for dump a object to file and read it from file 36 | class Marshal(filename: String) { 37 | var obj: HashMap[Friend, List[Friend]] = new HashMap() 38 | var ctx: ListBuffer[String] = new ListBuffer() 39 | def run(): HashMap[Friend, List[Friend]] = { 40 | while (ctx.size != 0) { 41 | parseString2Network() 42 | } 43 | return obj 44 | } 45 | 46 | def parseString2Network() = { 47 | val line = ctx.head 48 | var friend: Friend = new Friend("", "", "", "") 49 | var list: List[Friend] = List() 50 | friend.parse(line.substring(1)) 51 | ctx = ctx.tail 52 | if (ctx.size != 0) { 53 | list = parseString2List(friend, list) 54 | } 55 | obj(friend) = list 56 | } 57 | 58 | def parseString2List(friend: Friend, list: List[Friend]): List[Friend] = { 59 | val line = ctx.head 60 | if (line.charAt(0) == '-' && line.charAt(1) == '-') { 61 | var friendElement: Friend = new Friend("", "", "", "") 62 | friendElement.parse(line.substring(2)) 63 | ctx = ctx.tail 64 | if (ctx.size != 0) { 65 | parseString2List(friend, friendElement :: list) 66 | } else { 67 | friendElement :: list 68 | } 69 | } else { 70 | list 71 | } 72 | } 73 | 74 | def dump(message: HashMap[Friend, List[Friend]] = HashMap()) = { 75 | reporter.msg("dump to <" + filename + ">") 76 | val dumper = new Dumper(filename) 77 | for ((k, v) <- message) { 78 | dumper.write("-") 79 | dumper.write(k.toString) 80 | dumper.write("\n") 81 | for (ele <- v) { 82 | dumper.write("--") 83 | dumper.write(ele.toString) 84 | dumper.write("\n") 85 | } 86 | } 87 | dumper.close 88 | } 89 | 90 | def read(): HashMap[Friend, List[Friend]] = { 91 | reporter.msg("read from <" + filename + ">") 92 | ctx.clear 93 | for (line <- Source.fromFile(filename)("UTF-8").getLines()) { 94 | ctx += line.toString() 95 | } 96 | 97 | run() 98 | } 99 | } 100 | 101 | var marshaler: Marshal = new Marshal("network.txt") 102 | var network: HashMap[Friend, List[Friend]] = new HashMap[Friend, List[Friend]]() 103 | def dump() = { 104 | marshaler.dump(network) 105 | } 106 | def read() = { 107 | network = marshaler.read() 108 | } 109 | 110 | // initial the network 111 | def initialize(list: List[Element]): Unit = { 112 | if (hasFile == true) { 113 | network = marshaler.read() 114 | return 115 | } 116 | for (ele <- list) { 117 | var infos = ele.select("div[class=info]") 118 | var it: java.util.Iterator[Element] = infos.iterator() 119 | var info = it.next() 120 | var value = info.select("dd").iterator() 121 | value.next() 122 | val _name: String = info.select("a").iterator().next().html() 123 | val _school: String = value.next().html() 124 | val _link: String = info.select("a").iterator().next().attr("href") 125 | val _uid: String = _link.split("=")(1) 126 | var friend = new Friend(_uid, _school, _name, _link) 127 | 128 | network getOrElseUpdate (friend, Nil) 129 | } 130 | } 131 | 132 | // transfer the List[Element] to List[Friend] 133 | def translate2Friend(list: List[Element]): List[Friend] = { 134 | var res: List[Friend] = List() 135 | for (ele <- list) { 136 | var infos = ele.select("div[class=info]") 137 | var it: java.util.Iterator[Element] = infos.iterator() 138 | var info = it.next() 139 | var value = info.select("dd").iterator() 140 | value.next() 141 | val _name: String = info.select("a").iterator().next().html() 142 | val _school: String = value.next().html() 143 | val _link: String = info.select("a").iterator().next().attr("href") 144 | val _uid: String = _link.split("=")(1) 145 | var friend = new Friend(_uid, _school, _name, _link) 146 | 147 | res = friend :: res 148 | } 149 | res 150 | } 151 | } -------------------------------------------------------------------------------- /src/main/scala/renren/renren.scala: -------------------------------------------------------------------------------- 1 | package renren 2 | 3 | // project utils 4 | import utils.Reporters._ 5 | import utils.HttpParser 6 | import utils.Dumper 7 | 8 | import java.io._ 9 | 10 | // utilities 11 | import scala.collection.{ mutable, immutable, generic } 12 | import collection.JavaConversions._ 13 | 14 | // jsoup 15 | import org.jsoup.Jsoup 16 | import org.jsoup.helper.Validate 17 | import org.jsoup.nodes.Document 18 | import org.jsoup.nodes.Element 19 | import org.jsoup.select.Elements 20 | 21 | class Renren(val username: String, val pwd: String, val reporter: Reporter, val dumper: Dumper) { 22 | 23 | // info 24 | var uid = "" 25 | val httpParser: HttpParser = new HttpParser(username: String, reporter: Reporter, dumper: Dumper) 26 | var friends: List[Element] = Nil 27 | var network: FriendNetwork = new FriendNetwork(reporter) 28 | var renrenHttpClient: RenrenHttpClient = new RenrenHttpClient(reporter, username, pwd) 29 | 30 | private def divmod(x: Int, y: Int): (Int, Int) = { 31 | (x / y, x % y) 32 | } 33 | 34 | def login() = { 35 | val redirectURL = "http://www.renren.com/profile.do" 36 | 37 | // visit the profile page 38 | var content = renrenHttpClient.getContentByUrl(redirectURL) 39 | 40 | // parse profile for user 41 | uid = httpParser.getUid(content) 42 | 43 | } 44 | 45 | def getFriendList(_uid: String = ""): List[Element] = { 46 | var workUid: String = "" 47 | if (_uid == "") { 48 | workUid = uid 49 | } else { 50 | workUid = _uid 51 | } 52 | 53 | // get page num 54 | val elementsInOnePage = 20 55 | var page = 0 56 | var url = "http://friend.renren.com/GetFriendList.do?curpage=%s&id=" + workUid 57 | var content = renrenHttpClient.getContentByUrl(url format (page.toString())) 58 | var count = httpParser.getFriendCount(content) 59 | 60 | val internalRes = divmod(count, elementsInOnePage) 61 | val pageNum = internalRes._1 + (if (internalRes._2 == 0) 0 else 1) 62 | 63 | // get all friends 64 | var list: List[Element] = Nil 65 | for (pageBuf <- 0 to pageNum) { 66 | val pageUrl = url format pageBuf.toString 67 | val content = renrenHttpClient.getContentByUrl(pageUrl) 68 | list = list ::: httpParser.getFriendsInOnePage(content) 69 | reporter.info("getting the friend data in the page " + pageBuf) 70 | } 71 | list 72 | } 73 | 74 | def getFriendCount(_uid: String = ""): Int = { 75 | var workUid: String = "" 76 | if (_uid == "") { 77 | workUid = uid 78 | } else { 79 | workUid = _uid 80 | } 81 | 82 | var url = "http://friend.renren.com/GetFriendList.do?curpage=%s&id=" + workUid 83 | var content = renrenHttpClient.getContentByUrl(url format ("0")) 84 | var count = httpParser.getFriendCount(content) 85 | 86 | count 87 | } 88 | 89 | def getSchoolRank() = { 90 | if (friends.isEmpty) { 91 | reporter.error("you don't have a friend list now") 92 | } 93 | 94 | httpParser.getSchoolRank(friends) 95 | } 96 | 97 | def runFriendNetwork() = { 98 | login 99 | 100 | friends = getFriendList() 101 | getSchoolRank 102 | network.initialize(friends) 103 | // 4: The thread count 104 | network.parseFriend(this, 4) 105 | dumper.close 106 | } 107 | 108 | def runSixDegreeVerifier() = { 109 | login 110 | 111 | val sdv = new SixDegreeVerifier(reporter, this) 112 | sdv.findShortestPath(uid, "75511553") 113 | } 114 | } -------------------------------------------------------------------------------- /src/main/scala/renren/renrenhttpclient.scala: -------------------------------------------------------------------------------- 1 | package renren 2 | 3 | // project utils 4 | import utils.Reporters._ 5 | import utils.HttpParser 6 | import utils.Dumper 7 | 8 | // httpclient 9 | import java.io._ 10 | import org.apache.commons._ 11 | import org.apache.http._ 12 | import org.apache.http.client._ 13 | import org.apache.http.client.methods.{ HttpPost, HttpGet } 14 | import java.util.{ ArrayList } 15 | import org.apache.http.message.BasicNameValuePair 16 | import org.apache.http.client.entity.UrlEncodedFormEntity 17 | import org.apache.http.impl.client.{ BasicResponseHandler, DefaultHttpClient } 18 | import org.apache.http.client.params.{ CookiePolicy, HttpClientParams } 19 | import org.apache.http.util.EntityUtils 20 | import org.apache.http.params.CoreProtocolPNames 21 | 22 | class RenrenHttpClient(reporter: Reporter, username: String, pwd: String) { 23 | val client = new DefaultHttpClient 24 | client.getParams().setParameter("http.protocol.single-cookie-header", true) 25 | HttpClientParams.setCookiePolicy(client.getParams(), CookiePolicy.BROWSER_COMPATIBILITY) 26 | 27 | def neededOperation(new_client: DefaultHttpClient) = { 28 | val url = "http://www.renren.com/ajaxLogin/login" 29 | 30 | // login, first 31 | var httpPost = new HttpPost(url) 32 | var nvps = new ArrayList[NameValuePair]() 33 | nvps.add(new BasicNameValuePair("domain", "renren.com")) 34 | nvps.add(new BasicNameValuePair("isplogin", "true")) 35 | nvps.add(new BasicNameValuePair("formName", "")) 36 | nvps.add(new BasicNameValuePair("method", "")) 37 | nvps.add(new BasicNameValuePair("submit", "登录")) 38 | nvps.add(new BasicNameValuePair("email", username)) 39 | nvps.add(new BasicNameValuePair("password", pwd)) 40 | httpPost.setEntity(new UrlEncodedFormEntity(nvps)) 41 | var res = new_client.execute(httpPost) 42 | var entity = res.getEntity() 43 | EntityUtils.consume(entity) 44 | } 45 | 46 | neededOperation(client) 47 | 48 | def getContentByUrl(url: String): String = { 49 | val useragents = List( 50 | "Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.57 Safari/537.17", 51 | "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.11 (KHTML, like Gecko) Ubuntu Chromium/23.0.1271.97 Chrome/23.0.1271.97 Safari/537.11", 52 | "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.56 Safari/537.17", 53 | "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.34 (KHTML, like Gecko) rekonq/1.1 Safari/534.34", 54 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/536.26.17 (KHTML, like Gecko) Version/6.0.2 Safari/536.26.17") 55 | client.getParams().setParameter(CoreProtocolPNames.USER_AGENT, useragents(scala.util.Random.nextInt(5))); 56 | 57 | val httpGet = new HttpGet(url) 58 | val res_new = client.execute(httpGet) 59 | var rd = new BufferedReader(new InputStreamReader(res_new.getEntity().getContent())) 60 | var content = "" 61 | var line = rd.readLine() 62 | while (line != null) { 63 | content += line 64 | line = rd.readLine() 65 | } 66 | 67 | // close the connection 68 | val entity = res_new.getEntity() 69 | EntityUtils.consume(entity) 70 | 71 | // return 72 | content 73 | } 74 | } -------------------------------------------------------------------------------- /src/main/scala/renren/sixdegreeverifier.scala: -------------------------------------------------------------------------------- 1 | package renren 2 | 3 | import scala.collection.mutable.{ HashMap } 4 | import scala.collection.mutable.Queue 5 | import scala.Serializable 6 | import scala.io.Source 7 | import scala.actors.Actor 8 | import scala.actors.Actor._ 9 | 10 | // jsoup 11 | import org.jsoup.Jsoup; 12 | import org.jsoup.helper.Validate; 13 | import org.jsoup.nodes.Document; 14 | import org.jsoup.nodes.Element; 15 | import org.jsoup.select.Elements; 16 | 17 | // project utils 18 | import utils.Reporters._ 19 | import utils.Dumper 20 | import utils.Grapher 21 | 22 | import java.io._ 23 | import java.nio.file.{ Paths, Files } 24 | 25 | class SixDegreeVerifier(reporter: Reporter, renren: Renren) extends NetworkBase(reporter: Reporter) { 26 | // initialize() 27 | var visitedSet: Set[Friend] = Set() 28 | var queue: Queue[Friend] = new Queue() 29 | val shore: Friend = new Friend("-1", "-1", "-1", "-1") 30 | var dis = 0 31 | var fuid: String = "" 32 | class WorkAcotr() extends Actor { 33 | def act() { 34 | val new_renren = new Renren(renren.username, renren.pwd, renren.reporter, renren.dumper) 35 | while (true) { 36 | receive { 37 | case "recruit" => 38 | reporter.info("Ready to work") 39 | sender ! "end" 40 | case k: Friend => 41 | if (visitedSet contains k) { 42 | sender ! "end" 43 | } else { 44 | if (k == shore) { 45 | dis += 1 46 | queue.enqueue(shore) 47 | sender ! "end" 48 | } else { 49 | visitedSet += k 50 | if (k.uid == fuid) { 51 | reporter.msg("Has Found the frind in depth " + dis) 52 | sender ! "Found" 53 | } else { 54 | queue ++= translate2Friend(new_renren.getFriendList(k.uid)) 55 | sender ! "end" 56 | } 57 | } 58 | } 59 | } 60 | } 61 | } 62 | } 63 | 64 | class SuperActor(thread: Int) extends Actor { 65 | var actorList: List[WorkAcotr] = List() 66 | for (i <- 1 to thread) { 67 | reporter.info("create " + i.toString + "th actor") 68 | val actor = new WorkAcotr() 69 | actorList = actor :: actorList 70 | } 71 | def act() = { 72 | while (true) { 73 | receive { 74 | case "begin" => 75 | reporter.info("superActor begins to work") 76 | for (actor <- actorList) { 77 | actor.start ! "recruit" 78 | } 79 | case "end" => 80 | if (!queue.isEmpty) { 81 | val f = queue.dequeue 82 | reporter.info("Begin to analyze " + f.toString) 83 | sender ! f 84 | } else { 85 | reporter.info("Not found") 86 | exit() 87 | } 88 | case "Found" => 89 | exit() 90 | } 91 | } 92 | } 93 | } 94 | 95 | def findShortestPath(uid: String, fuid: String) = { 96 | this.fuid = fuid 97 | val new_renren = new Renren(renren.username, renren.pwd, renren.reporter, renren.dumper) 98 | queue ++= translate2Friend(new_renren.getFriendList(uid)) 99 | queue.enqueue(shore) 100 | var sa = new SuperActor(4) 101 | sa.start ! "begin" 102 | } 103 | } -------------------------------------------------------------------------------- /src/main/scala/utils/dumper.scala: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import java.io._ 4 | 5 | class Dumper(filename: String) { 6 | val writer = new PrintWriter(new File(filename)) 7 | 8 | def write(content: String) = { 9 | writer.write(content) 10 | } 11 | 12 | def writeLine(content: String) = { 13 | writer.write(content) 14 | writer.write("\n") 15 | } 16 | 17 | def close() = { 18 | writer.close() 19 | } 20 | 21 | def writeSchoolRank(list: List[(String, Int)]) = { 22 | writeLine("---Friend School Rank---") 23 | for ((k, v) <- list) { 24 | writeLine("School: " + k + ", Count: " + v.toString) 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/main/scala/utils/grapher.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2008-2010 Gephi 3 | Authors : Mathieu Bastian 4 | Website : http://www.gephi.org 5 | 6 | This file is part of Gephi. 7 | 8 | Gephi is free software: you can redistribute it and/or modify 9 | it under the terms of the GNU Affero General Public License as 10 | published by the Free Software Foundation, either version 3 of the 11 | License, or (at your option) any later version. 12 | 13 | Gephi is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU Affero General Public License for more details. 17 | 18 | You should have received a copy of the GNU Affero General Public License 19 | along with Gephi. If not, see . 20 | */ 21 | package utils 22 | 23 | import java.awt.Color 24 | import java.io.File 25 | import java.io.IOException 26 | import java.awt.BorderLayout 27 | import javax.swing.JFrame 28 | import org.gephi.data.attributes.api.AttributeColumn 29 | import org.gephi.data.attributes.api.AttributeController 30 | import org.gephi.data.attributes.api.AttributeModel 31 | import org.gephi.filters.api.FilterController 32 | import org.gephi.filters.api.Query 33 | import org.gephi.filters.api.Range 34 | import org.gephi.filters.plugin.graph.DegreeRangeBuilder.DegreeRangeFilter 35 | import org.gephi.graph.api._ 36 | import org.gephi.io.exporter.api.ExportController 37 | import org.gephi.io.importer.api.Container 38 | import org.gephi.io.importer.api.EdgeDefault 39 | import org.gephi.io.importer.api.ImportController 40 | import org.gephi.io.processor.plugin.DefaultProcessor 41 | import org.gephi.layout.plugin.force.StepDisplacement 42 | import org.gephi.layout.plugin.forceAtlas.ForceAtlasLayout 43 | import org.gephi.preview.types.EdgeColor 44 | import org.gephi.project.api.ProjectController 45 | import org.gephi.project.api.Workspace 46 | import org.gephi.ranking.api.Ranking 47 | import org.gephi.ranking.api.RankingController 48 | import org.gephi.ranking.api.Transformer 49 | import org.gephi.ranking.plugin.transformer.AbstractColorTransformer 50 | import org.gephi.ranking.plugin.transformer.AbstractSizeTransformer 51 | import org.gephi.statistics.plugin.GraphDistance 52 | import org.gephi.statistics.plugin.Modularity 53 | import org.openide.util.Lookup 54 | import org.gephi.io.exporter.preview._ 55 | import org.gephi.io.importer.api.Container 56 | import org.gephi.io.importer.api.ImportController 57 | import org.gephi.io.processor.plugin.DefaultProcessor 58 | import org.gephi.preview.api._ 59 | import org.gephi.preview.types.DependantOriginalColor 60 | import processing.core.PApplet 61 | 62 | import scala.collection.{ mutable, immutable, generic } 63 | import scala.collection.mutable.{ HashMap } 64 | import scala.util.Random 65 | 66 | import renren.Friend 67 | import utils.Reporters._ 68 | 69 | /** 70 | * This demo shows several actions done with the toolkit, aiming to do a complete chain, 71 | * from data import to results. 72 | *

73 | * This demo shows the following steps: 74 | *

  • Create a project and a workspace, it is mandatory.
  • 75 | *
  • Import the polblogs.gml graph file in an import container.
  • 76 | *
  • Append the container to the main graph structure.
  • 77 | *
  • Filter the graph, using DegreeFilter.
  • 78 | *
  • Run layout manually.
  • 79 | *
  • Compute graph distance metrics.
  • 80 | *
  • Rank color by degree values.
  • 81 | *
  • Rank size by centrality values.
  • 82 | *
  • Configure preview to display labels and mutual edges differently.
  • 83 | *
  • Export graph as PDF.
84 | * 85 | * @author Mathieu Bastian 86 | */ 87 | class Grapher(val network: HashMap[Friend, List[Friend]], reporter: Reporter) { 88 | val minSize = 10 89 | val maxSize = 50 90 | val height = 1080 91 | val width = 1920 92 | val colorMap: HashMap[Integer, Color] = new HashMap() 93 | 94 | def generateRandomColor(mix: Color): Color = { 95 | var random = new Random() 96 | var red = random.nextInt(256) 97 | var green = random.nextInt(256) 98 | var blue = random.nextInt(256) 99 | 100 | // mix the color 101 | red = (red + mix.getRed()) / 2 102 | green = (green + mix.getGreen()) / 2 103 | blue = (blue + mix.getBlue()) / 2 104 | 105 | var color = new Color(red, green, blue) 106 | return color 107 | } 108 | 109 | def fillMap() = { 110 | var mix = new Color(0, 0, 0) 111 | for (i <- 0 to 100) { 112 | colorMap(i) = generateRandomColor(mix) 113 | mix = colorMap(i) 114 | } 115 | } 116 | 117 | fillMap() 118 | 119 | def rgbInt2Float(a: Integer): Float = { 120 | return a.toFloat / 256f 121 | } 122 | 123 | def script(): Unit = { 124 | reporter.info("Begin to draw") 125 | //Init a project - and therefore a workspace 126 | var pc = Lookup.getDefault().lookup(classOf[ProjectController]) 127 | pc.newProject() 128 | var workspace = pc.getCurrentWorkspace() 129 | 130 | //Get models and controllers for this new workspace - will be useful later 131 | var attributeModel = Lookup.getDefault().lookup(classOf[AttributeController]).getModel() 132 | var graphModel = Lookup.getDefault().lookup(classOf[GraphController]).getModel() 133 | var previewController = Lookup.getDefault().lookup(classOf[PreviewController]) 134 | var model = previewController.getModel() 135 | var filterController = Lookup.getDefault().lookup(classOf[FilterController]) 136 | var rankingController = Lookup.getDefault().lookup(classOf[RankingController]) 137 | 138 | // Import the data 139 | var directedGraph = graphModel.getDirectedGraph() 140 | var nodes: HashMap[Friend, Node] = new HashMap() 141 | // println(network) 142 | for ((k, _) <- network) { 143 | var n0 = graphModel.factory().newNode(k.uid) 144 | n0.getNodeData().setLabel(k.name) 145 | nodes(k) = n0 146 | directedGraph.addNode(n0) 147 | } 148 | for ((k, value) <- network) { 149 | for (ele <- value) { 150 | var e0 = graphModel.factory().newEdge(nodes(k), nodes(ele), 1f, true) 151 | directedGraph.addEdge(e0) 152 | } 153 | } 154 | 155 | // System.out.println("Nodes: "+directedGraph.getNodeCount()+" Edges: "+directedGraph.getEdgeCount()) 156 | 157 | //Filter 158 | var degreeFilter = new DegreeRangeFilter() 159 | degreeFilter.init(directedGraph) 160 | degreeFilter.setRange(new Range(1, Integer.MAX_VALUE)) //Remove nodes with degree < 30 161 | var query = filterController.createQuery(degreeFilter) 162 | var view = filterController.filter(query) 163 | graphModel.setVisibleView(view) //Set the filter result as the visible view 164 | 165 | //See visible graph stats 166 | var graphVisible = graphModel.getUndirectedGraphVisible() 167 | reporter.info("---The Graph Info---") 168 | reporter.info("Nodes: " + graphVisible.getNodeCount()) 169 | reporter.info("Edges: " + graphVisible.getEdgeCount()) 170 | 171 | //Get Centrality 172 | var distance = new GraphDistance() 173 | distance.setDirected(true) 174 | distance.execute(graphModel, attributeModel) 175 | 176 | //Rank color by Degree 177 | // var degreeRanking = rankingController.getModel().getRanking(Ranking.NODE_ELEMENT, Ranking.DEGREE_RANKING) 178 | // var colorTransformer = rankingController.getModel().getTransformer(Ranking.NODE_ELEMENT, Transformer.RENDERABLE_COLOR).asInstanceOf[AbstractColorTransformer[Ranking[AttributeColumn]]] 179 | // colorTransformer.setColors(Array(new Color(0xFEF0D9), new Color(0xB30000))) 180 | // rankingController.transform(degreeRanking,colorTransformer) 181 | 182 | //Rank size by centrality 183 | var centralityColumn = attributeModel.getNodeTable().getColumn(GraphDistance.BETWEENNESS) 184 | var centralityRanking = rankingController.getModel().getRanking(Ranking.NODE_ELEMENT, centralityColumn.getId) 185 | var sizeTransformer = rankingController.getModel().getTransformer(Ranking.NODE_ELEMENT, Transformer.RENDERABLE_SIZE).asInstanceOf[AbstractSizeTransformer[Ranking[AttributeColumn]]] 186 | sizeTransformer.setMinSize(minSize) 187 | sizeTransformer.setMaxSize(maxSize) 188 | rankingController.transform(centralityRanking, sizeTransformer) 189 | 190 | // Rank color by modularity 191 | var modularity = new Modularity 192 | modularity.execute(graphModel, attributeModel) 193 | var modularityColumn = attributeModel.getNodeTable().getColumn(Modularity.MODULARITY_CLASS) 194 | for (node <- graphModel.getDirectedGraph.getNodes.toArray) { 195 | var rgb = node.getNodeData().getAttributes().getValue(modularityColumn.getIndex()).asInstanceOf[Int] 196 | var colorBuf = colorMap(rgb) 197 | node.getNodeData().setColor(rgbInt2Float(colorBuf.getRed), rgbInt2Float(colorBuf.getGreen), rgbInt2Float(colorBuf.getBlue)) 198 | } 199 | 200 | // use layout 201 | reporter.info("Run layout algorithm") 202 | val layout = new ForceAtlasLayout(null) 203 | layout.setGraphModel(graphModel) 204 | layout.resetPropertiesValues() 205 | layout.initAlgo() 206 | 207 | var i = 0 208 | while (i < 1000 && layout.canAlgo()) { 209 | i = i + 1 210 | layout.goAlgo() 211 | } 212 | layout.endAlgo() 213 | reporter.info("Layout end") 214 | 215 | // Test: get all the AttributeColumn 216 | // for (col <- attributeModel.getNodeTable().getColumns()) { 217 | // println(col) 218 | // } 219 | 220 | //Preview 221 | model.getProperties().putValue(PreviewProperty.SHOW_NODE_LABELS, true) 222 | model.getProperties().putValue(PreviewProperty.NODE_LABEL_FONT, model.getProperties().getFontValue(PreviewProperty.NODE_LABEL_FONT).deriveFont(8)) 223 | model.getProperties().putValue(PreviewProperty.EDGE_CURVED, false) 224 | previewController.refreshPreview() 225 | 226 | //New Processing target, get the PApplet 227 | var target = previewController.getRenderTarget(RenderTarget.PROCESSING_TARGET).asInstanceOf[ProcessingTarget] 228 | var applet = target.getApplet() 229 | applet.init() 230 | 231 | //Refresh the preview and reset the zoom 232 | previewController.render(target) 233 | target.refresh() 234 | target.resetZoom() 235 | 236 | //Add the applet to a JFrame and display 237 | reporter.info("Build JFrame") 238 | var frame = new JFrame("Renren Friend Relationship") 239 | frame.setLayout(new BorderLayout()) 240 | 241 | frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE) 242 | frame.add(applet, BorderLayout.CENTER) 243 | 244 | frame.pack() 245 | frame.setVisible(true) 246 | var ec = Lookup.getDefault().lookup(classOf[ExportController]) 247 | val pe = new PNGExporter 248 | ec.exportFile(new File("headless_simple.png"), pe) 249 | val ps = new SVGExporter 250 | ec.exportFile(new File("headless_simple.svg"), ps) 251 | 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /src/main/scala/utils/httpparser.scala: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | // jsoup 4 | import org.jsoup.Jsoup; 5 | import org.jsoup.helper.Validate; 6 | import org.jsoup.nodes.Document; 7 | import org.jsoup.nodes.Element; 8 | import org.jsoup.select.Elements; 9 | 10 | // project utils 11 | import utils.Reporters._ 12 | 13 | // utilities 14 | import scala.collection.{ mutable, immutable, generic } 15 | 16 | class HttpParser(username: String, reporter: Reporter, dumper: Dumper) { 17 | def getUid(content: String): String = { 18 | var document = Jsoup.parse(content) 19 | var linkList = document.select("a[href~=renren.com/[0-9]+/profile]") 20 | 21 | // can't use for or map to iterate the Elements 22 | var href: java.util.Iterator[Element] = linkList.iterator() 23 | var objectName = href.next() 24 | var hrefValue = objectName.attr("href"); 25 | val uid = hrefValue.split("/")(3) 26 | reporter.info(username + "'s uid is " + uid) 27 | uid 28 | } 29 | 30 | def getFriendCount(content: String): Int = { 31 | var document = Jsoup.parse(content) 32 | var linkList = document.select("span[class=count]") 33 | 34 | // can't use for or map to iterate the Elements 35 | val href: java.util.Iterator[Element] = linkList.iterator() 36 | var objectName = href.next() 37 | var count = objectName.html 38 | reporter.info(" has " + count.toString + " friends") 39 | count.toInt 40 | } 41 | 42 | def getFriendsInOnePage(content: String): List[Element] = { 43 | var document = Jsoup.parse(content) 44 | var linkList = document.select("ol[id=friendListCon]") 45 | if (linkList.size != 0) { 46 | var elements = linkList.iterator().next().children() 47 | var list: List[Element] = List() 48 | var it: java.util.Iterator[Element] = elements.iterator() 49 | while (it.hasNext()) { 50 | var obj = it.next() 51 | list = obj :: list 52 | } 53 | list 54 | } else { 55 | Nil 56 | } 57 | } 58 | 59 | def getSchoolRank(friends: List[Element]) = { 60 | var hmp = new scala.collection.mutable.ListMap[String, Int]() 61 | for (friend <- friends) { 62 | var infos = friend.select("dt") 63 | var it: java.util.Iterator[Element] = infos.iterator() 64 | 65 | // remove name 66 | it.next() 67 | 68 | var schoolInfo = it.next().html() 69 | if (schoolInfo.toString == "学校") { 70 | infos = friend.select("dd") 71 | it = infos.iterator() 72 | 73 | it.next() 74 | 75 | schoolInfo = it.next().html() 76 | hmp(schoolInfo) = (hmp getOrElse (schoolInfo, 0)) + 1 77 | } 78 | } 79 | 80 | val list = hmp.toList.sortWith(_._2 > _._2) 81 | dumper.writeSchoolRank(list) 82 | } 83 | } -------------------------------------------------------------------------------- /src/main/scala/utils/reporter.scala: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import scala.tools.nsc.Global 4 | import scala.reflect.internal.util.{ Position, NoPosition, FakePos } 5 | 6 | object Reporters { 7 | 8 | abstract class ReporterFormatter { 9 | def formatTypeTitle(typ: MsgType): String 10 | } 11 | 12 | class ConsoleFormatter extends ReporterFormatter { 13 | def formatTypeTitle(typ: MsgType) = { 14 | typ match { 15 | case FatalMsg => 16 | Console.RED + Console.BOLD + typ.title + Console.RESET 17 | case ErrorMsg => 18 | Console.RED + typ.title + Console.RESET 19 | case WarningMsg => 20 | Console.YELLOW + typ.title + Console.RESET 21 | case NormalMsg => 22 | Console.MAGENTA + typ.title + Console.RESET 23 | case TitleMsg => 24 | Console.BLUE + Console.BOLD + typ.title + Console.RESET 25 | case DebugMsg => 26 | typ.title 27 | } 28 | } 29 | } 30 | 31 | class PlainFormatter extends ReporterFormatter { 32 | def formatTypeTitle(typ: MsgType) = { 33 | typ.title 34 | } 35 | } 36 | 37 | sealed abstract class MsgType { 38 | val title: String 39 | } 40 | 41 | object MsgType { 42 | val maxTitleSize = WarningMsg.title.size 43 | } 44 | 45 | case object TitleMsg extends MsgType { 46 | val title = "info" 47 | } 48 | 49 | case object FatalMsg extends MsgType { 50 | val title = "fatal" 51 | } 52 | 53 | case object ErrorMsg extends MsgType { 54 | val title = "error" 55 | } 56 | 57 | case object NormalMsg extends MsgType { 58 | val title = "info" 59 | } 60 | 61 | case object WarningMsg extends MsgType { 62 | val title = "warning" 63 | } 64 | 65 | case object DebugMsg extends MsgType { 66 | val title = "debug" 67 | } 68 | 69 | final case class MsgLines(lines: Seq[String]); 70 | 71 | case class Msg(lines: Seq[String], typ: MsgType) { 72 | def content = lines.mkString("\n") 73 | 74 | val firstLine = lines.head 75 | val otherLines = lines.tail 76 | } 77 | 78 | import language.implicitConversions 79 | 80 | implicit def posToOptPos(p: Position): Option[Position] = Some(p) 81 | implicit def strToMsgLines(m: String): MsgLines = MsgLines(Seq(m)) 82 | implicit def seqStrToMsgLines(m: Seq[String]): MsgLines = MsgLines(m) 83 | 84 | trait ReporterHandler { 85 | def open() {} 86 | def incIndent() {} 87 | def decIndent() {} 88 | def close() {} 89 | 90 | def printMessage(msg: Msg, optPos: Option[Position]) 91 | def printText(content: String) 92 | } 93 | 94 | class ConsoleReporterHandler() extends ReporterHandler { 95 | var currentIndent: Int = 0; 96 | val indentStep = 8; 97 | 98 | val formatter = new ConsoleFormatter 99 | 100 | protected def posToString(optPos: Option[Position]): String = { 101 | optPos match { 102 | case Some(posIn) => 103 | val pos = if (posIn eq null) NoPosition 104 | else if (posIn.isDefined) posIn.inUltimateSource(posIn.source) 105 | else posIn 106 | 107 | pos match { 108 | case FakePos(fmsg) => 109 | "?:? (" + fmsg + "): " 110 | case NoPosition => 111 | "" 112 | 113 | case _ => 114 | val file = pos.source.file 115 | 116 | file.path + ":" + pos.line + ": " 117 | } 118 | 119 | case None => 120 | "" 121 | } 122 | } 123 | 124 | override def incIndent() { 125 | currentIndent += indentStep 126 | } 127 | override def decIndent() { 128 | currentIndent -= indentStep 129 | } 130 | 131 | override def printMessage(msg: Msg, optPos: Option[Position]) { 132 | val strPos = posToString(optPos) 133 | 134 | val indent = " " * currentIndent 135 | val padding = " " * (MsgType.maxTitleSize - msg.typ.title.size) 136 | 137 | printText(formatter.formatTypeTitle(msg.typ) + padding + ": " + indent + msg.firstLine + "\n") 138 | for (line <- msg.otherLines) { 139 | printText(" " * (MsgType.maxTitleSize + (": " + indent).length) + line + "\n") 140 | } 141 | 142 | optPos match { 143 | case Some(posIn) if posIn ne null => 144 | val pos = if (posIn.isDefined) posIn.inUltimateSource(posIn.source) 145 | else posIn 146 | 147 | pos match { 148 | case FakePos(fmsg) => 149 | case NoPosition => 150 | case _ => 151 | printSourceLine(strPos, pos) 152 | } 153 | case _ => 154 | } 155 | } 156 | 157 | def printSourceLine(prefix: String, pos: Position) = { 158 | printText(prefix + pos.lineContent.stripLineEnd + "\n") 159 | if (pos.isDefined) { 160 | printText((" " * (pos.column - 1 + prefix.length) + "^\n")) 161 | } 162 | } 163 | 164 | def printText(content: String) { 165 | print(content) 166 | } 167 | 168 | } 169 | 170 | class Reporter() { 171 | var handlers = Set[ReporterHandler]() 172 | 173 | def dispatch(cb: ReporterHandler => Unit) { 174 | handlers.foreach(cb) 175 | } 176 | 177 | def attach(rh: ReporterHandler) { 178 | handlers += rh 179 | } 180 | 181 | def detach(rh: ReporterHandler) { 182 | handlers -= rh 183 | } 184 | 185 | def open() { 186 | dispatch(_.open) 187 | } 188 | 189 | def close() { 190 | dispatch(_.close) 191 | } 192 | 193 | def printMessage(m: Msg, optPos: Option[Position]) { 194 | dispatch { rh => rh.printMessage(m, optPos) } 195 | } 196 | 197 | def printText(s: String) { 198 | dispatch { rh => rh.printText(s) } 199 | } 200 | 201 | def incIndent() { 202 | dispatch(_.incIndent) 203 | } 204 | 205 | def decIndent() { 206 | dispatch(_.decIndent) 207 | } 208 | 209 | def msg(m: MsgLines, optPos: Option[Position] = None) = 210 | printMessage(Msg(m.lines, NormalMsg), optPos) 211 | 212 | def info(m: MsgLines, optPos: Option[Position] = None) = 213 | printMessage(Msg(m.lines, NormalMsg), optPos) 214 | 215 | def error(m: MsgLines, optPos: Option[Position] = None) = 216 | printMessage(Msg(m.lines, ErrorMsg), optPos) 217 | 218 | def fatal(m: MsgLines, optPos: Option[Position] = None) = { 219 | printMessage(Msg(m.lines, FatalMsg), optPos) 220 | sys.error("Panic! Evacuate Ship!") 221 | } 222 | 223 | def debug(m: MsgLines, optPos: Option[Position] = None) = 224 | printMessage(Msg(m.lines, DebugMsg), optPos) 225 | 226 | def warn(m: MsgLines, optPos: Option[Position] = None) = 227 | printMessage(Msg(m.lines, WarningMsg), optPos) 228 | 229 | def title(m: String) { 230 | printMessage(Msg(Seq(m), TitleMsg), None) 231 | } 232 | } 233 | 234 | case class CompilerReporterPassThrough(as: (String, Position) => Unit) extends scala.tools.nsc.reporters.Reporter { 235 | protected def info0(pos: Position, msg: String, severity: Severity, force: Boolean) { 236 | as(msg, pos) 237 | } 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /userinfo.ini.example: -------------------------------------------------------------------------------- 1 | 2 | user= 3 | passwd= --------------------------------------------------------------------------------