write-tweet.ts 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. import { Component } from "@angular/core";
  2. import {
  3. IonicPage,
  4. NavController,
  5. NavParams,
  6. LoadingController,
  7. AlertController
  8. } from "ionic-angular";
  9. import {
  10. FormBuilder,
  11. Validators,
  12. FormGroup,
  13. ValidatorFn,
  14. AbstractControl
  15. } from "@angular/forms";
  16. import { TwitterApiProvider } from "../../providers/twitter-api/twitter-api";
  17. import { Storage } from "@ionic/storage";
  18. import { P2pStorageIpfsProvider } from "../../providers/p2p-storage-ipfs/p2p-storage-ipfs";
  19. import { P2pDatabaseGunProvider } from "../../providers/p2p-database-gun/p2p-database-gun";
  20. import twittertext from "twitter-text";
  21. import { CryptoProvider } from "../../providers/crypto/crypto";
  22. @IonicPage()
  23. @Component({
  24. selector: "page-write-tweet",
  25. templateUrl: "write-tweet.html"
  26. })
  27. export class WriteTweetPage {
  28. tweet: FormGroup;
  29. retweetId: string;
  30. replyToStatusId: string;
  31. retweet;
  32. replyTweet;
  33. constructor(
  34. public navCtrl: NavController,
  35. public navParams: NavParams,
  36. private formBuilder: FormBuilder,
  37. private twitter: TwitterApiProvider,
  38. private loadingCtrl: LoadingController,
  39. private storage: Storage,
  40. private ipfs: P2pStorageIpfsProvider,
  41. private gun: P2pDatabaseGunProvider,
  42. private cryptoUtils: CryptoProvider,
  43. private alertCtrl: AlertController
  44. ) {
  45. this.retweetId = this.navParams.get("tweetId");
  46. this.replyToStatusId = this.navParams.get("replyToStatus");
  47. this.tweet = this.formBuilder.group({
  48. text: [""],
  49. p2p: [false]
  50. });
  51. this.addValidators();
  52. }
  53. private async addValidators() {
  54. const triggerWords = await this.storage.get("keywords");
  55. const validators = [
  56. Validators.maxLength(140),
  57. this.containsTriggerWord(triggerWords)
  58. ];
  59. this.tweet.controls["text"].setValidators(validators);
  60. }
  61. private containsTriggerWord(triggerWords: string): ValidatorFn {
  62. return (control: AbstractControl): { [key: string]: any } | null => {
  63. const regexList = triggerWords
  64. .toLowerCase()
  65. .split(", ")
  66. .join("|");
  67. const regex = new RegExp(regexList);
  68. const containsTriggerWord = regex.test(control.value.toLowerCase());
  69. return containsTriggerWord
  70. ? { containsTriggerWord: { value: control.value } }
  71. : null;
  72. };
  73. }
  74. async ionViewDidLoad() {
  75. if (this.retweetId) {
  76. this.retweet = await this.twitter.fetchTweet(this.retweetId);
  77. }
  78. if (this.replyToStatusId) {
  79. this.replyTweet = await this.twitter.fetchTweet(this.replyToStatusId);
  80. }
  81. }
  82. get tweetCharProgress() {
  83. const progress = 1 - this.tweet.value["text"].length / 140;
  84. const radius = 8;
  85. const circumference = Math.PI * radius * 2;
  86. return progress * circumference;
  87. }
  88. get showTrigger(): boolean {
  89. return (
  90. this.tweet &&
  91. this.tweet.controls &&
  92. this.tweet.controls.text &&
  93. this.tweet.controls.text.errors &&
  94. this.tweet.controls.text.errors["containsTriggerWord"] &&
  95. !this.tweet.controls.p2p.value
  96. );
  97. }
  98. showTriggerInfo() {
  99. this.alertCtrl
  100. .create({
  101. title: "Watch Out!",
  102. message:
  103. "Your tweet contains words you have previously defined to only share securely via P2P. Currently P2P mode is not selected.",
  104. buttons: ["OK"]
  105. })
  106. .present();
  107. }
  108. async submitTweet() {
  109. const loading = this.loadingCtrl.create();
  110. loading.present();
  111. if (this.tweet.value.p2p) {
  112. loading.setContent("Validate keys...");
  113. if (
  114. (await this.cryptoUtils.isPrivateKeySet()) &&
  115. (await this.cryptoUtils.isPublicKeyPublished())
  116. ) {
  117. loading.setContent("Publish private tweet...");
  118. await this.tweetPrivate();
  119. } else {
  120. loading.dismiss();
  121. const alert = this.alertCtrl.create({
  122. title: "Oooops...",
  123. message:
  124. "Please verify that you have set a private and public key in the settings and that your latest public key was published."
  125. });
  126. alert.present();
  127. return;
  128. }
  129. } else {
  130. loading.setContent("Publish on Twitter...");
  131. await this.twitter.tweet(
  132. this.tweet.value["text"],
  133. this.retweet,
  134. this.replyToStatusId
  135. );
  136. }
  137. loading.dismiss();
  138. this.navCtrl.pop();
  139. }
  140. private async tweetPrivate() {
  141. const tweet = await this.buildPrivateTweet();
  142. const privateKey = await this.storage.get("privateKey");
  143. const encryptedTweet = this.cryptoUtils.encrypt(
  144. JSON.stringify(tweet),
  145. privateKey
  146. );
  147. const res = await this.ipfs.storeTweet(encryptedTweet);
  148. this.gun.storeLastTweetHashForUser(
  149. tweet.user_id,
  150. res["Hash"],
  151. tweet.created_at
  152. );
  153. this.gun.publishHashtags(tweet.entities.hashtags);
  154. }
  155. private async buildPrivateTweet() {
  156. const status = this.tweet.value["text"].trim();
  157. const entities = await this.getEntities(status);
  158. return {
  159. full_text: status,
  160. user_id: await this.storage.get("userId"),
  161. created_at: Date.now(),
  162. private_tweet: true,
  163. in_reply_to_status_id: this.replyToStatusId,
  164. quoted_status_id: this.retweetId,
  165. display_text_range: [0, status.length],
  166. entities: entities
  167. };
  168. }
  169. private async getEntities(status: string) {
  170. return {
  171. hashtags: twittertext.extractHashtagsWithIndices(status),
  172. urls: twittertext.extractUrlsWithIndices(status),
  173. user_mentions: await this.getMentions(status)
  174. };
  175. }
  176. private async getMentions(status: string) {
  177. // extract mentions
  178. const entities = twittertext.extractMentionsWithIndices(status);
  179. // add user_id
  180. const entitiesWithPromises = entities.map(async mention => {
  181. try {
  182. const user = await this.twitter.fetchUserFromScreenName(
  183. mention.screenName
  184. );
  185. mention["id_str"] = user[0]["id_str"];
  186. mention["screen_name"] = mention.screenName;
  187. delete mention.screenName;
  188. } catch (err) {
  189. console.error(
  190. "There is no user signed up to twitter with username: " +
  191. mention.screenName
  192. );
  193. }
  194. return mention;
  195. });
  196. // filter for valid users and return
  197. return (await Promise.all(entitiesWithPromises)).filter(el =>
  198. el.hasOwnProperty("id_str")
  199. );
  200. }
  201. }