PageRenderTime 55ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/src/main/scala/megane.scala

https://code.google.com/
Scala | 1052 lines | 949 code | 72 blank | 31 comment | 121 complexity | c60b92b1d828282ffd4376d5206f418b MD5 | raw file
  1. package net.centrevillage.mgtw
  2. import java.awt.AlphaComposite
  3. import java.awt.Canvas
  4. import java.awt.Color
  5. import java.awt.Cursor
  6. import java.awt.FlowLayout
  7. import java.awt.Font
  8. import java.awt.FontMetrics
  9. import java.awt.Graphics
  10. import java.awt.Graphics2D
  11. import java.awt.GraphicsEnvironment
  12. import java.awt.Point
  13. import java.awt.color.ColorSpace
  14. import java.awt.event.KeyEvent
  15. import java.awt.event.MouseEvent
  16. import java.awt.geom.Ellipse2D
  17. import java.awt.geom._
  18. import java.awt.image._
  19. import java.awt.PopupMenu
  20. import java.awt.MenuItem
  21. import java.awt.SystemTray
  22. import java.awt.TrayIcon
  23. import java.awt.TrayIcon.MessageType
  24. import java.awt.event._
  25. import java.io.IOException
  26. import java.io.BufferedReader
  27. import java.io.File
  28. import java.io.FileInputStream
  29. import java.io.InputStream
  30. import java.io.OutputStream
  31. import java.io.InputStreamReader
  32. import java.io.FileReader
  33. import java.io.FileWriter
  34. import java.util.Timer
  35. import java.util.TimerTask
  36. import java.util.Date
  37. import java.net.URL
  38. import javax.imageio.ImageIO
  39. import twitter4j._
  40. import twitter4j.api._
  41. import twitter4j.auth._
  42. import twitter4j.conf._
  43. import twitter4j.json._
  44. import twitter4j.management._
  45. import twitter4j.media._
  46. import twitter4j.util._
  47. import org.yaml.snakeyaml.Yaml
  48. import scala.collection.JavaConversions._
  49. import scala.collection.immutable.SortedMap
  50. import scala.collection.immutable.TreeMap
  51. import scala.collection.mutable.ArrayBuffer
  52. import scala.collection.mutable.HashSet
  53. import scala.collection.mutable.Queue
  54. import scala.collection.mutable.SynchronizedQueue
  55. import scala.collection.mutable.{LinkedHashMap => LHMap}
  56. import scala.collection.mutable.{ListBuffer => MList}
  57. import scala.collection.mutable.{Map => MMap}
  58. import scala.io._
  59. import scala.util.matching.Regex
  60. import scala.util.parsing.combinator._
  61. import scala.swing._
  62. import scala.swing.event.{Event => ScEvent, ActionEvent => ScActionEvent, _}
  63. import scala.swing.Dialog
  64. import javax.swing.JTable
  65. import javax.swing.table.DefaultTableModel
  66. import javax.swing.SwingUtilities
  67. import javax.swing.UIManager
  68. import javax.swing.ImageIcon
  69. import javax.swing.event.HyperlinkEvent
  70. import javax.swing.event.HyperlinkEvent.EventType
  71. import javax.swing.event.HyperlinkListener
  72. import java.awt.Desktop
  73. case class ResourceNotFoundException(msg: String = "Unknown", cause: Throwable = null) extends Exception(msg, cause)
  74. object Consts {
  75. val MAX_ROW = 10000
  76. }
  77. class ConfigLoader {
  78. private def getResourceStream(path: String): java.io.InputStream = {
  79. val file = new File(path)
  80. val resourcePath = path.replaceAll("""^\./""", "/")
  81. var url = getClass.getClassLoader.getResource(resourcePath)
  82. if (file.exists) {
  83. new FileInputStream(path)
  84. } else if (url != null) {
  85. url.openStream()
  86. } else {
  87. throw ResourceNotFoundException("????[" + path + "]???????")
  88. }
  89. }
  90. protected var cache = MMap[String, Map[Any, Any]]()
  91. private def loadReal(path: String): Map[Any, Any] = loadReal(path, Map[Symbol, Any]())
  92. private def loadReal(path: String, opt: Map[Symbol, Any]): Map[Any, Any] = {
  93. var is: InputStream = null
  94. var reader: InputStreamReader = null
  95. try {
  96. is = getResourceStream(path)
  97. reader = new InputStreamReader(is)
  98. convertConfigMap((new Yaml).load(reader).asInstanceOf[java.util.HashMap[Any, Any]])
  99. } finally {
  100. reader.close()
  101. is.close()
  102. }
  103. }
  104. def clearCache() {
  105. cache.clear()
  106. }
  107. def load(path: String): Map[Any, Any] = {
  108. load(path, Map[Symbol, Any]())
  109. }
  110. def load(path: String, opt: Map[Symbol, Any]): Map[Any, Any] = {
  111. try {
  112. opt.get('cache) match {
  113. case Some(true) =>
  114. cache.get(path) match {
  115. case Some(config) => config
  116. case None =>
  117. val config = loadReal(path)
  118. cache += (path -> config)
  119. config
  120. }
  121. case _ => loadReal(path)
  122. }
  123. } catch {
  124. case ex: Exception =>
  125. Dialog.showMessage(title="ERROR", message="["+ path +"]???????????")
  126. throw ex
  127. }
  128. }
  129. def save(path: String, values: Map[Any, Any]) {
  130. var writer = new FileWriter(path)
  131. try {
  132. (new Yaml).dump(unconvertConfigMap(values), writer)
  133. } finally {
  134. writer.close()
  135. }
  136. }
  137. private def unconvertConfigMap(v: Map[Any, Any]): java.util.Map[Any, Any] = {
  138. import java.util.HashMap
  139. val map = new HashMap[Any, Any]
  140. v.foreach {
  141. case (k, value: Map[Any, Any]) => map += (k -> unconvertConfigMap(value))
  142. case (k, value: List[Any]) => map += (k -> unconvertConfigList(value))
  143. case (k, value) => map += (k -> value)
  144. }
  145. map
  146. }
  147. private def unconvertConfigList(v: List[Any]): java.util.List[Any] = {
  148. import java.util.ArrayList
  149. val list = new ArrayList[Any]
  150. v.foreach {
  151. case value: Map[Any, Any] => list += unconvertConfigMap(value)
  152. case value: List[Any] => list += unconvertConfigList(value)
  153. case value => list += value
  154. }
  155. list
  156. }
  157. private def convertConfigMap(v: java.util.Map[Any, Any]): Map[Any, Any] = {
  158. val map = v
  159. var immutableMap = Map[Any, Any]()
  160. map.foreach {
  161. case (k, vv: java.util.Map[Any, Any]) => immutableMap += (k -> convertConfigMap(vv))
  162. case (k, vv: java.util.List[Any]) => immutableMap += (k -> convertConfigList(vv))
  163. case (k, vv) => immutableMap += (k -> vv)
  164. }
  165. immutableMap
  166. }
  167. private def convertConfigList(v: java.util.List[Any]): List[Any] = {
  168. v.toList.map {
  169. case vv: java.util.Map[Any, Any] => convertConfigMap(vv)
  170. case vv: java.util.List[Any] => convertConfigList(vv)
  171. case vv => vv
  172. }
  173. }
  174. }
  175. class MGLayout extends java.awt.LayoutManager {
  176. import java.awt._
  177. def addLayoutComponent(name: String, comp: Component): Unit = {}
  178. def removeLayoutComponent(comp: Component): Unit = {}
  179. def preferredLayoutSize(target: Container): Dimension =
  180. target.getComponents.map(_.getPreferredSize).foldLeft(new Dimension) { (d, sz) =>
  181. d.width = math.max(d.width, sz.width)
  182. d.height += sz.height
  183. d
  184. }
  185. def minimumLayoutSize(target: Container): Dimension =
  186. target.getComponents.map(_.getMinimumSize).foldLeft(new Dimension) { (d, sz) =>
  187. d.width = math.max(d.width, sz.width)
  188. d.height += sz.height
  189. d
  190. }
  191. def layoutContainer(target: Container): Unit = {
  192. var y = 0
  193. target.getComponents.foreach { c =>
  194. val sz = c.getPreferredSize
  195. c.setBounds(0, y, sz.width, sz.height)
  196. y += sz.height
  197. }
  198. }
  199. }
  200. class ListPane extends Panel with SequentialContainer.Wrapper {
  201. override lazy val peer = new javax.swing.JPanel(new MGLayout) with SuperMixin
  202. }
  203. object ConfigLoader extends ConfigLoader
  204. class HyperlinkHandler extends HyperlinkListener {
  205. def hyperlinkUpdate(e: HyperlinkEvent) {
  206. if (e.getEventType() == EventType.ACTIVATED) {
  207. val url = e.getURL()
  208. val dp = Desktop.getDesktop()
  209. try {
  210. dp.browse(url.toURI())
  211. } catch {
  212. case ex: Exception => ex.printStackTrace()
  213. }
  214. }
  215. }
  216. }
  217. class MGTimeLine extends scala.swing.Table {
  218. val columnNames = Array[java.lang.Object](
  219. "????", "ID", "????", "M", "??"
  220. )
  221. override protected def editor(row: Int, column: Int) = {
  222. null
  223. }
  224. override lazy val model = new MyTableModel(super.model.asInstanceOf[javax.swing.table.DefaultTableModel])
  225. columnNames.foreach((model.asInstanceOf[MyTableModel]).addColumn(_))
  226. peer.getColumnModel().getColumn(0).setPreferredWidth(60)
  227. peer.getColumnModel().getColumn(0).setMaxWidth(60)
  228. peer.getColumnModel().getColumn(0).setMinWidth(20)
  229. peer.getColumnModel().getColumn(1).setPreferredWidth(64)
  230. peer.getColumnModel().getColumn(1).setMaxWidth(80)
  231. peer.getColumnModel().getColumn(1).setMinWidth(50)
  232. peer.getColumnModel().getColumn(2).setPreferredWidth(400)
  233. peer.getColumnModel().getColumn(2).setMaxWidth(2048)
  234. peer.getColumnModel().getColumn(2).setMinWidth(128)
  235. peer.getColumnModel().getColumn(3).setPreferredWidth(20)
  236. peer.getColumnModel().getColumn(3).setMaxWidth(20)
  237. peer.getColumnModel().getColumn(3).setMinWidth(20)
  238. peer.getColumnModel().getColumn(4).setPreferredWidth(50)
  239. peer.getColumnModel().getColumn(4).setMaxWidth(128)
  240. peer.getColumnModel().getColumn(4).setMinWidth(50)
  241. }
  242. class MGTWindow {
  243. val frameWidth = 800
  244. val frameHeight = 600
  245. val rowHeight = 30
  246. lazy val timeLine = new MGTimeLine
  247. lazy val replyLine = new MGTimeLine
  248. lazy val dmLine = new MGTimeLine
  249. lazy val myText = new scala.swing.TextArea {
  250. yLayoutAlignment = 1.0
  251. charWrap = true
  252. lineWrap = true
  253. background = new Color(208, 208, 208)
  254. border = javax.swing.plaf.basic.BasicBorders.getTextFieldBorder
  255. maximumSize = new Dimension(9999, 60)
  256. yLayoutAlignment = java.awt.Component.BOTTOM_ALIGNMENT
  257. }
  258. lazy val selImg = new Label
  259. lazy val selMsg = new EditorPane("text/html", "") {
  260. import scala.swing.Alignment._
  261. editable = false
  262. minimumSize = new Dimension(100, 20)
  263. peer.putClientProperty(javax.swing.JEditorPane.HONOR_DISPLAY_PROPERTIES, java.lang.Boolean.TRUE)
  264. peer.setFont((new Label).font)
  265. peer.addHyperlinkListener(new HyperlinkHandler)
  266. override def text_= (txt: String) {
  267. super.text_=(txt.replaceAll("https?(:\\/\\/[-_.!~*\\'a-zA-Z0-9\\/?\\@&=+\\$%#]+)", "<a href='$0'>$0</a>"))
  268. }
  269. }
  270. lazy val top = new Frame {
  271. import FlowPanel.Alignment._
  272. override def closeOperation() {
  273. iconified()
  274. }
  275. // iconImage = ImageIO.read(classOf[Megane].getResourceAsStream("icon.png"))
  276. iconImage = ImageIO.read(classOf[Megane].getResourceAsStream("icon64.png"))
  277. size = new Dimension(frameWidth, frameHeight)
  278. title = "megane-twit"
  279. contents = new BoxPanel(Orientation.Vertical) {
  280. contents += new BoxPanel(Orientation.Vertical) {
  281. maximumSize = new Dimension(9999, 9999)
  282. contents += new TabbedPane {
  283. pages += new TabbedPane.Page("Home", new ScrollPane {
  284. yLayoutAlignment = 0.0
  285. contents = timeLine
  286. })
  287. pages += new TabbedPane.Page(" ? ", new ScrollPane {
  288. yLayoutAlignment = 0.0
  289. contents = replyLine
  290. })
  291. pages += new TabbedPane.Page(" DM ", new ScrollPane {
  292. yLayoutAlignment = 0.0
  293. contents = dmLine
  294. })
  295. }
  296. }
  297. contents += new BoxPanel(Orientation.Vertical) {
  298. maximumSize = new Dimension(9999, 100)
  299. contents += new BoxPanel(Orientation.Horizontal) {
  300. yLayoutAlignment = 1.0
  301. size = new Dimension(frameWidth, 30)
  302. maximumSize = new Dimension(9999, 80)
  303. contents += new FlowPanel(Left)(selImg)
  304. contents += selMsg
  305. }
  306. contents += myText
  307. contents += new BoxPanel(Orientation.Horizontal) {
  308. yLayoutAlignment = 1.0
  309. size = new Dimension(frameWidth, 26)
  310. contents += new FlowPanel(Left)(new BoxPanel(Orientation.Horizontal) {
  311. contents += new scala.swing.Button {
  312. name = "??????????"
  313. text = "DM"
  314. reactions += {
  315. case ButtonClicked(b) =>
  316. try {
  317. val text = myText.text.trim
  318. if (selectedTL != null && !text.isEmpty) {
  319. if (text.length > 140) {
  320. Dialog.showMessage(
  321. title="???????",
  322. message=(text.length-140) + "??????????"
  323. )
  324. } else {
  325. Dialog.showConfirmation(message="@" + selectedTL.screenName + "?DM???????") match {
  326. case Dialog.Result.Yes =>
  327. Megane.mgTwitter.sendDirectMessage(selectedTL.userID, text)
  328. myText.text = ""
  329. case _ => ()
  330. }
  331. }
  332. }
  333. } catch {
  334. case e: Exception => Dialog.showMessage(title="ERROR", message=e.getMessage)
  335. }
  336. ()
  337. }
  338. }
  339. contents += new scala.swing.Button {
  340. name = "??"
  341. text = "QT"
  342. reactions += {
  343. case ButtonClicked(b) =>
  344. try {
  345. if (selectedTL != null) {
  346. if (tabIdx == 3) {
  347. Dialog.showMessage(title="ERROR", message="??????????????????")
  348. } else {
  349. myText.text = "QT " + "@" + selectedTL.screenName + " " + selectedTL.msg
  350. }
  351. }
  352. } catch {
  353. case e: Exception => Dialog.showMessage(title="ERROR", message=e.getMessage)
  354. }
  355. ()
  356. }
  357. }
  358. contents += new scala.swing.Button {
  359. name = "?????"
  360. text = "RT"
  361. reactions += {
  362. case ButtonClicked(b) =>
  363. try {
  364. if (selectedTL != null) {
  365. if (tabIdx == 1) {
  366. Megane.mgTwitter.retweet(selectedTL.id)
  367. updateTLInfo(selectedTL.id, selectedTL.copy(relation="RT"))
  368. } else if (tabIdx == 2) {
  369. Megane.mgTwitter.retweet(selectedTL.id)
  370. updateReplyInfo(selectedTL.id, selectedTL.copy(relation="RT"))
  371. } else if (tabIdx == 3) {
  372. Dialog.showMessage(title="ERROR", message="?????????????????????")
  373. }
  374. }
  375. } catch {
  376. case e: Exception => Dialog.showMessage(title="ERROR", message=e.getMessage)
  377. }
  378. ()
  379. }
  380. }
  381. contents += new scala.swing.Button {
  382. name = "??"
  383. text = "?"
  384. reactions += {
  385. case ButtonClicked(b) =>
  386. try {
  387. if (selectedTL != null) {
  388. if (tabIdx == 3) {
  389. Dialog.showMessage(title="ERROR", message="??????????????????")
  390. } else {
  391. myText.text = "@" + selectedTL.screenName + " " + myText.text
  392. replyStatus = selectedTL.id
  393. replyScreenName = selectedTL.screenName
  394. }
  395. }
  396. } catch {
  397. case e: Exception => Dialog.showMessage(title="ERROR", message=e.getMessage)
  398. }
  399. ()
  400. }
  401. }
  402. contents += new scala.swing.Button {
  403. name = "?????"
  404. text = "?"
  405. reactions += {
  406. case ButtonClicked(b) =>
  407. try {
  408. if (selectedTL != null) {
  409. if (tabIdx == 1) {
  410. Megane.mgTwitter.fav(selectedTL.id)
  411. updateTLInfo(selectedTL.id, selectedTL.copy(relation="?"))
  412. } else if (tabIdx == 2) {
  413. Megane.mgTwitter.fav(selectedTL.id)
  414. updateReplyInfo(selectedTL.id, selectedTL.copy(relation="?"))
  415. } else if (tabIdx == 3) {
  416. Dialog.showMessage(title="ERROR", message="???????????????????????")
  417. }
  418. }
  419. } catch {
  420. case e: Exception => Dialog.showMessage(title="ERROR", message=e.getMessage)
  421. }
  422. ()
  423. }
  424. }
  425. })
  426. contents += new FlowPanel(Right)(new scala.swing.Button {
  427. name = "Submit"
  428. text = "??"
  429. reactions += {
  430. case ButtonClicked(b) =>
  431. try {
  432. val mgtw = Megane.mgTwitter
  433. val text = myText.text.trim
  434. if (!text.isEmpty) {
  435. if (text.length > 140) {
  436. Dialog.showMessage(
  437. title="???????",
  438. message=(text.length-140) + "??????????"
  439. )
  440. } else {
  441. if (replyScreenName != null && text.startsWith("@" + replyScreenName) && replyStatus > 0) {
  442. val reply = new StatusUpdate(text)
  443. reply.setInReplyToStatusId(replyStatus)
  444. mgtw.twit(reply)
  445. } else {
  446. mgtw.twit(text)
  447. }
  448. myText.text = ""
  449. }
  450. }
  451. Thread.sleep(1000)
  452. setTimeLine(mgtw.getHomeTimeLine())
  453. } catch {
  454. case e: Exception => Dialog.showMessage(title="ERROR", message=e.getMessage)
  455. }
  456. replyScreenName = null
  457. replyStatus = -1L
  458. ()
  459. }
  460. })
  461. }
  462. }
  463. listenTo(timeLine.selection)
  464. listenTo(dmLine.selection)
  465. listenTo(replyLine.selection)
  466. reactions += {
  467. case TableRowsSelected(source, range, adjusting) =>
  468. val selectedIdx = source.selection.rows.leadIndex
  469. if (source eq timeLine) {
  470. selectTwit(selectedIdx)
  471. } else if (source eq dmLine) {
  472. selectDM(selectedIdx)
  473. } else if (source eq replyLine) {
  474. selectReply(selectedIdx)
  475. }
  476. }
  477. }
  478. peer.setResizable(true)
  479. peer.setVisible(true)
  480. open()
  481. }
  482. var selectedTL: TimeLineInfo = null
  483. var replyScreenName: String = null
  484. var replyStatus: Long = -1L
  485. var tabIdx: Long = 1
  486. def selectTwit(idx: Int) {
  487. selectedTL = TwitterInfo.timeline(idx)
  488. tabIdx = 1
  489. selImg.icon = selectedTL.image
  490. selMsg.text = selectedTL.msg + " ["+ selectedTL.screenName + "]"
  491. selImg.repaint()
  492. }
  493. def selectReply(idx: Int) {
  494. selectedTL = TwitterInfo.replyline(idx)
  495. tabIdx = 2
  496. selImg.icon = selectedTL.image
  497. selMsg.text = selectedTL.msg + " ["+ selectedTL.screenName + "]"
  498. selImg.repaint()
  499. }
  500. def selectDM(idx: Int) {
  501. selectedTL = TwitterInfo.dmline(idx)
  502. tabIdx = 3
  503. selImg.icon = selectedTL.image
  504. selMsg.text = selectedTL.msg + " ["+ selectedTL.screenName + "]"
  505. selImg.repaint()
  506. }
  507. def open() {
  508. Swing.onEDT {
  509. val t = top
  510. SwingUtilities.updateComponentTreeUI(t.peer)
  511. if (t.size == new Dimension(0,0)) t.pack()
  512. t.visible = true
  513. }
  514. }
  515. def setVisible(flag: Boolean) {
  516. val t = top
  517. t.visible = flag
  518. }
  519. def setTimeLine(timeline: List[TimeLineInfo]) {
  520. val addedLines = TwitterInfo.addTimeLine(timeline)
  521. // addedLines.foreach(println(_))
  522. for (tl <- addedLines) {
  523. this.timeLine.model.insertRow(0, timelineToRow(tl))
  524. }
  525. while (this.timeLine.model.getRowCount > Consts.MAX_ROW) {
  526. this.timeLine.model.removeRow(this.timeLine.model.getRowCount-1)
  527. }
  528. TwitterInfo.timeline = TwitterInfo.timeline.take(Consts.MAX_ROW)
  529. }
  530. def setReplyLine(timeline: List[TimeLineInfo]) {
  531. val addedLines = TwitterInfo.addReplyLine(timeline)
  532. // addedLines.foreach(println(_))
  533. for (tl <- addedLines) {
  534. this.replyLine.model.insertRow(0, timelineToRow(tl))
  535. }
  536. while (this.replyLine.model.getRowCount > Consts.MAX_ROW) {
  537. this.replyLine.model.removeRow(this.replyLine.model.getRowCount-1)
  538. }
  539. TwitterInfo.replyline = TwitterInfo.replyline.take(Consts.MAX_ROW)
  540. }
  541. def setDMLine(timeline: List[TimeLineInfo]) {
  542. val addedLines = TwitterInfo.addDMLine(timeline)
  543. // addedLines.foreach(println(_))
  544. for (tl <- addedLines) {
  545. this.dmLine.model.insertRow(0, timelineToRow(tl))
  546. }
  547. while (this.dmLine.model.getRowCount > Consts.MAX_ROW) {
  548. this.dmLine.model.removeRow(this.dmLine.model.getRowCount-1)
  549. }
  550. TwitterInfo.dmline = TwitterInfo.dmline.take(Consts.MAX_ROW)
  551. }
  552. def updateTLInfo(id: Long, tl: TimeLineInfo) {
  553. val idx = TwitterInfo.timeline.indexWhere(_.id == id)
  554. if (idx >= 0) {
  555. TwitterInfo.timeline = TwitterInfo.timeline.updated(idx, tl)
  556. this.timeLine.model.removeRow(idx)
  557. this.timeLine.model.insertRow(idx, timelineToRow(tl))
  558. }
  559. }
  560. def updateDMInfo(id: Long, tl: TimeLineInfo) {
  561. val idx = TwitterInfo.dmline.indexWhere(_.id == id)
  562. if (idx >= 0) {
  563. TwitterInfo.dmline = TwitterInfo.dmline.updated(idx, tl)
  564. this.dmLine.model.removeRow(idx)
  565. this.dmLine.model.insertRow(idx, timelineToRow(tl))
  566. }
  567. }
  568. def updateReplyInfo(id: Long, tl: TimeLineInfo) {
  569. val idx = TwitterInfo.replyline.indexWhere(_.id == id)
  570. if (idx >= 0) {
  571. TwitterInfo.replyline = TwitterInfo.replyline.updated(idx, tl)
  572. this.replyLine.model.removeRow(idx)
  573. this.replyLine.model.insertRow(idx, timelineToRow(tl))
  574. }
  575. }
  576. val formatter = new java.text.SimpleDateFormat("HH:mm:ss yy/MM/dd")
  577. def timelineToRow(tl: TimeLineInfo): Array[AnyRef] = {
  578. Array[AnyRef](tl.image, tl.screenName, tl.msg, tl.relation, formatter.format(tl.time))
  579. }
  580. }
  581. class ImageIconLoader {
  582. var cache = MMap[URL, ImageIcon]()
  583. def load(url: URL): ImageIcon = {
  584. cache.get(url) match {
  585. case Some(imageIcon) => imageIcon
  586. case None =>
  587. var imageIcon = new ImageIcon(url)
  588. cache += (url -> imageIcon)
  589. cache(url)
  590. }
  591. }
  592. def clearCache() {
  593. cache.clear()
  594. }
  595. }
  596. object ImageIconLoader extends ImageIconLoader
  597. object TwitterInfo {
  598. var followIds = List[Long]()
  599. var followersIds = List[Long]()
  600. var lists = MMap[Int, List[Long]]()
  601. var userLists = MMap[Long, List[String]]()
  602. var user: User = null
  603. var timeline = List[TimeLineInfo]()
  604. var dmline = List[TimeLineInfo]()
  605. var replyline = List[TimeLineInfo]()
  606. def initialize(twitter: Twitter) {
  607. user = twitter.verifyCredentials()
  608. var ids1 = twitter.getFriendsIDs(-1)
  609. followIds :::= ids1.getIDs().toList
  610. var c1 = ids1.getNextCursor
  611. while (c1 != 0) {
  612. ids1 = twitter.getFriendsIDs(c1)
  613. followIds :::= ids1.getIDs().toList
  614. c1 = ids1.getNextCursor
  615. }
  616. var ids2 = twitter.getFollowersIDs(-1)
  617. followersIds :::= ids2.getIDs().toList
  618. var c2 = ids2.getNextCursor()
  619. while (c2 != 0) {
  620. ids2 = twitter.getFriendsIDs(c2)
  621. followersIds :::= ids2.getIDs().toList
  622. c2 = ids2.getNextCursor
  623. }
  624. def addUserList(userList: PagableResponseList[UserList]) {
  625. for (ul <- userList) {
  626. if (lists.contains(ul.getId)) {
  627. lists += (ul.getId -> (ul.getUser.getId :: lists(ul.getId)))
  628. } else {
  629. lists += (ul.getId -> List(ul.getUser.getId))
  630. }
  631. if (userLists.contains(ul.getUser.getId)) {
  632. userLists += (ul.getUser.getId -> (ul.getName :: userLists(ul.getUser.getId)))
  633. } else {
  634. userLists += (ul.getUser.getId -> List(ul.getName))
  635. }
  636. }
  637. }
  638. var ulCursor = -1L
  639. var userList = twitter.getUserLists(user.getId, ulCursor)
  640. addUserList(userList)
  641. while (userList.hasNext) {
  642. ulCursor = userList.getNextCursor
  643. userList = twitter.getUserLists(user.getId, ulCursor)
  644. addUserList(userList)
  645. }
  646. // -- debug
  647. // println("follow: " + followIds.mkString(","))
  648. // println("follower: " + followersIds.mkString(","))
  649. // println("list: " + lists)
  650. }
  651. def addTimeLine(newLines: List[TimeLineInfo]): List[TimeLineInfo] = {
  652. var returns = newLines.filter(tl => !this.timeline.exists(v => v.id == tl.id))
  653. for (tl <- returns) {
  654. this.timeline = tl :: this.timeline
  655. }
  656. returns
  657. }
  658. def addDMLine(newLines: List[TimeLineInfo]): List[TimeLineInfo] = {
  659. var returns = newLines.filter(tl => !this.dmline.exists(v => v.id == tl.id))
  660. for (tl <- returns) {
  661. this.dmline = tl :: this.dmline
  662. }
  663. returns
  664. }
  665. def addReplyLine(newLines: List[TimeLineInfo]): List[TimeLineInfo] = {
  666. var returns = newLines.filter(tl => !this.replyline.exists(v => v.id == tl.id))
  667. for (tl <- returns) {
  668. this.replyline = tl :: this.replyline
  669. }
  670. returns
  671. }
  672. def displayRelation(status: Status): String = {
  673. val user = status.getUser
  674. val isFollow = followIds.contains(user.getId)
  675. val isFollowed = followersIds.contains(user.getId)
  676. if (status.isRetweet) {
  677. "RT"
  678. } else if (status.isFavorited) {
  679. "?"
  680. } else if (user.isProtected) {
  681. "?"
  682. } else {
  683. if (isFollow && isFollowed) {
  684. "?"
  685. } else if (isFollow) {
  686. "?"
  687. } else if (isFollowed) {
  688. "?"
  689. } else {
  690. "?"
  691. }
  692. }
  693. }
  694. def displayListName(user: User): String = {
  695. if (userLists.contains(user.getId)) {
  696. userLists(user.getId).mkString("/")
  697. } else {
  698. "?"
  699. }
  700. }
  701. }
  702. class AuthDialog(_title: String, _message: String) extends Dialog {
  703. this.title = _title
  704. this.modal = true
  705. var result: Option[String] = None
  706. val pinField = new TextField
  707. contents = new BoxPanel(Orientation.Vertical) {
  708. contents += new EditorPane("text/html", _message.replaceAll("https?(:\\/\\/[-_.!~*\\'a-zA-Z0-9\\/?\\@&=+\\$%#]+)", "<a href='$0'>$0</a>")) {
  709. editable = false
  710. peer.putClientProperty(javax.swing.JEditorPane.HONOR_DISPLAY_PROPERTIES, java.lang.Boolean.TRUE)
  711. peer.setFont((new Label).font)
  712. peer.addHyperlinkListener(new HyperlinkHandler)
  713. }
  714. contents += pinField
  715. contents += new BoxPanel(Orientation.Horizontal) {
  716. contents += new scala.swing.Button {
  717. name = "Authorize"
  718. text = "??"
  719. reactions += {
  720. case ButtonClicked(b) =>
  721. result = Some(pinField.text)
  722. close()
  723. }
  724. }
  725. contents += new scala.swing.Button {
  726. name = "Cancel"
  727. text = "?????"
  728. reactions += {
  729. case ButtonClicked(b) =>
  730. result = None
  731. close()
  732. }
  733. }
  734. }
  735. }
  736. def getResult() = result
  737. }
  738. case class TimeLineInfo(id: Long, userID: Long, userName: String, screenName: String, msg: String, image: ImageIcon, time: Date, isRetweet: Boolean = false, isFavorited: Boolean = false, localtion: String = null, isProtected: Boolean = false, relation: String = null, listName: String = null, status: Status = null) {
  739. override def toString(): String = "@" + screenName + " - " + msg
  740. }
  741. class MGTwitter {
  742. private def statusToTimeLineInfo(status: Status): TimeLineInfo = {
  743. TimeLineInfo(
  744. id = status.getId,
  745. userID = status.getUser.getId,
  746. userName = status.getUser.getName,
  747. screenName = status.getUser.getScreenName,
  748. msg = status.getText,
  749. image = ImageIconLoader.load(status.getUser.getProfileImageUrlHttps),
  750. time = status.getCreatedAt,
  751. relation = TwitterInfo.displayRelation(status),
  752. listName = TwitterInfo.displayListName(status.getUser),
  753. isProtected = status.getUser.isProtected,
  754. status = status
  755. )
  756. }
  757. private def messageToTimeLineInfo(status: DirectMessage): TimeLineInfo = {
  758. TimeLineInfo(
  759. id = status.getId,
  760. userID = status.getSender.getId,
  761. userName = status.getSender.getName,
  762. screenName = status.getSender.getScreenName,
  763. msg = status.getText,
  764. image = ImageIconLoader.load(status.getSender.getProfileImageUrlHttps),
  765. time = status.getCreatedAt,
  766. relation = "?",
  767. listName = TwitterInfo.displayListName(status.getSender),
  768. isProtected = status.getSender.isProtected
  769. )
  770. }
  771. val twitter = login()
  772. TwitterInfo.initialize(twitter)
  773. def twit(text: String) {
  774. twitter.updateStatus(text)
  775. }
  776. def twit(status: StatusUpdate) {
  777. twitter.updateStatus(status)
  778. }
  779. def retweet(statusID: Long) {
  780. twitter.retweetStatus(statusID)
  781. }
  782. def fav(statusID: Long) {
  783. twitter.createFavorite(statusID)
  784. }
  785. def sendDirectMessage(userID: Long, text: String) {
  786. twitter.sendDirectMessage(userID, text)
  787. }
  788. def getReplyLine(): List[TimeLineInfo] = {
  789. var timelines = List[TimeLineInfo]()
  790. try {
  791. val statuses = twitter.getMentions(new Paging(1, 20))
  792. for (status <- statuses) {
  793. timelines ::= statusToTimeLineInfo(status)
  794. }
  795. } catch {
  796. case te: Exception =>
  797. te.printStackTrace()
  798. //Dialog.showMessage(title="ERROR", message="???????????????: " + te.getMessage)
  799. }
  800. timelines
  801. }
  802. def getDMLine(): List[TimeLineInfo] = {
  803. var timelines = List[TimeLineInfo]()
  804. try {
  805. val statuses = twitter.getDirectMessages(new Paging(1, 20))
  806. for (status <- statuses) {
  807. timelines ::= messageToTimeLineInfo(status)
  808. }
  809. } catch {
  810. case te: Exception =>
  811. te.printStackTrace()
  812. //Dialog.showMessage(title="ERROR", message="DM???????????: " + te.getMessage)
  813. }
  814. timelines
  815. }
  816. def getHomeTimeLine(): List[TimeLineInfo] = {
  817. var timelines = List[TimeLineInfo]()
  818. try {
  819. val statuses = twitter.getHomeTimeline(new Paging(1, 100))
  820. for (status <- statuses) {
  821. timelines ::= statusToTimeLineInfo(status)
  822. }
  823. } catch {
  824. case te: Exception =>
  825. te.printStackTrace()
  826. //Dialog.showMessage(title="ERROR", message="?????????????????: " + te.getMessage)
  827. }
  828. timelines
  829. }
  830. def login(): Twitter = {
  831. val tokenFile = new File("token")
  832. if (tokenFile.exists) {
  833. val config = ConfigLoader.load("token")
  834. val accessToken = new AccessToken(config("token").toString, config("tokenSecret").toString)
  835. new TwitterFactory().getInstance(accessToken)
  836. } else {
  837. val tw = new TwitterFactory().getInstance()
  838. // ?????????????????????????????
  839. val requestToken = tw.getOAuthRequestToken()
  840. var accessToken: AccessToken = null
  841. val br = new BufferedReader(new InputStreamReader(System.in))
  842. while (null == accessToken) {
  843. // val optPin = Dialog.showInput(
  844. // message="??URL???????????PIN????????????: \n"+requestToken.getAuthorizationURL(),
  845. // initial="")
  846. val dialog = new AuthDialog(
  847. "??",
  848. "??URL???????????PIN????????????: <br>"+requestToken.getAuthorizationURL())
  849. dialog.open()
  850. val optPin = dialog.getResult
  851. optPin match {
  852. case Some(pin) =>
  853. try{
  854. if(pin.length() > 0){
  855. accessToken = tw.getOAuthAccessToken(requestToken, pin)
  856. }else{
  857. accessToken = tw.getOAuthAccessToken()
  858. }
  859. } catch {
  860. case te: TwitterException =>
  861. if(401 == te.getStatusCode()){
  862. Dialog.showMessage(
  863. title="ERROR",
  864. message="??????????")
  865. }else{
  866. te.printStackTrace()
  867. }
  868. case e: Exception => e.printStackTrace()
  869. }
  870. case _ =>
  871. Dialog.showMessage(
  872. title="?????",
  873. message="??????????????")
  874. System.exit(0)
  875. }
  876. }
  877. //??????? accessToken ??????
  878. ConfigLoader.save("token", Map("token" -> accessToken.getToken, "tokenSecret" -> accessToken.getTokenSecret))
  879. tw
  880. }
  881. }
  882. }
  883. object Megane {
  884. lazy val mgtw = new MGTwitter
  885. def mgTwitter: MGTwitter = mgtw
  886. }
  887. class Megane {
  888. var window: MGTWindow = new MGTWindow
  889. def execute(args: Array[String]) {
  890. val mgtw = Megane.mgTwitter
  891. // ??????????
  892. val image = ImageIO.read(classOf[Megane].getResourceAsStream("icon.png"))
  893. val icon = new TrayIcon(image)
  894. icon.addActionListener(new ActionListener {
  895. def actionPerformed(e: ActionEvent) {
  896. window.setTimeLine(mgtw.getHomeTimeLine())
  897. window.setDMLine(mgtw.getDMLine())
  898. window.setReplyLine(mgtw.getReplyLine())
  899. window.setVisible(true)
  900. }
  901. })
  902. window.open()
  903. window.setTimeLine(mgtw.getHomeTimeLine())
  904. window.setDMLine(mgtw.getDMLine())
  905. window.setReplyLine(mgtw.getReplyLine())
  906. window.setVisible(true)
  907. // ??????????
  908. val menu = new PopupMenu
  909. // ??????
  910. val exitItem = new MenuItem("??")
  911. exitItem.addActionListener(new ActionListener {
  912. def actionPerformed(e: ActionEvent) {
  913. System.exit(0)
  914. }
  915. })
  916. // ????????????????
  917. menu.add(exitItem)
  918. icon.setPopupMenu(menu)
  919. // ?????????
  920. SystemTray.getSystemTray().add(icon)
  921. var count = 0
  922. val task = new TimerTask {
  923. def run() {
  924. window.setTimeLine(mgtw.getHomeTimeLine())
  925. if (count % 10 == 0) {
  926. //???DM??
  927. window.setDMLine(mgtw.getDMLine())
  928. window.setReplyLine(mgtw.getReplyLine())
  929. }
  930. count += 1
  931. }
  932. }
  933. val timer = new Timer
  934. timer.scheduleAtFixedRate(task, 0, 1 * 60 * 1000)
  935. }
  936. }
  937. //class MMLPlayer {
  938. // def play() {}
  939. //}
  940. //class MMLParser {
  941. // def parse(text: String) : MMLPlayer = {
  942. // }
  943. //}
  944. object Main {
  945. private var OS = 'Windows
  946. private var JVM_VERSION = "1.6"
  947. def checkOSAndJVMVersion(): Boolean = {
  948. var osname = System.getProperty("os.name")
  949. var jvmVersion = System.getProperty("java.version")
  950. // println("OS Name = " + osname)
  951. osname = osname.toLowerCase
  952. // println("Java Version = " + jvmVersion)
  953. var appendMessages = List("???JDK?JRE?????????????", "http://www.oracle.com/technetwork/java/javase/downloads")
  954. if (osname.startsWith("mac os x")) {
  955. OS = 'Mac
  956. appendMessages = List("Mac OS X 10.5 ? Intel Core2Duo ???????", "?Java Preferences????? Java SE 6 ??????????")
  957. } else if (osname.startsWith("windows")) {
  958. OS = 'Windows
  959. } else if (osname.startsWith("linux")) {
  960. OS = 'Linux
  961. } else {
  962. println("unknown os! name = " + osname)
  963. }
  964. if (jvmVersion < "1.6") {
  965. println("Java??????????????????????????\n"
  966. +"java version = " + jvmVersion + ", (" +appendMessages.mkString(",")+")")
  967. return false
  968. }
  969. JVM_VERSION = jvmVersion
  970. true
  971. }
  972. val megane = new Megane
  973. def main(args: Array[String]) {
  974. checkOSAndJVMVersion
  975. if (OS == 'Windows) {
  976. UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel")
  977. }
  978. megane.execute(args)
  979. }
  980. }