write-tweet.ts 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  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. if (triggerWords) {
  64. const regexList = triggerWords
  65. .toLowerCase()
  66. .split(", ")
  67. .join("|");
  68. const regex = new RegExp(regexList);
  69. const containsTriggerWord = regex.test(control.value.toLowerCase());
  70. return containsTriggerWord
  71. ? { containsTriggerWord: { value: control.value } }
  72. : null;
  73. } else {
  74. return null;
  75. }
  76. };
  77. }
  78. async ionViewDidLoad() {
  79. if (this.retweetId) {
  80. this.retweet = await this.twitter.fetchTweet(this.retweetId);
  81. }
  82. if (this.replyToStatusId) {
  83. this.replyTweet = await this.twitter.fetchTweet(this.replyToStatusId);
  84. }
  85. }
  86. get tweetCharProgress() {
  87. const progress = 1 - this.tweet.value["text"].length / 140;
  88. const radius = 8;
  89. const circumference = Math.PI * radius * 2;
  90. return progress * circumference;
  91. }
  92. get showTrigger(): boolean {
  93. return (
  94. this.tweet &&
  95. this.tweet.controls &&
  96. this.tweet.controls.text &&
  97. this.tweet.controls.text.errors &&
  98. this.tweet.controls.text.errors["containsTriggerWord"] &&
  99. !this.tweet.controls.p2p.value
  100. );
  101. }
  102. showTriggerInfo() {
  103. this.alertCtrl
  104. .create({
  105. title: "Watch Out!",
  106. message:
  107. "Your tweet contains words you have previously defined to only share securely via P2P. Currently P2P mode is not selected.",
  108. buttons: ["OK"]
  109. })
  110. .present();
  111. }
  112. async submitTweet() {
  113. const loading = this.loadingCtrl.create();
  114. loading.present();
  115. if (this.tweet.value.p2p) {
  116. loading.setContent("Validate keys...");
  117. if (
  118. (await this.cryptoUtils.isPrivateKeySet()) &&
  119. (await this.cryptoUtils.isPublicKeyPublished())
  120. ) {
  121. loading.setContent("Publish private tweet...");
  122. await this.tweetPrivate();
  123. } else {
  124. loading.dismiss();
  125. const alert = this.alertCtrl.create({
  126. title: "Oooops...",
  127. message:
  128. "Please verify that you have set a private and public key in the settings and that your latest public key was published."
  129. });
  130. alert.present();
  131. return;
  132. }
  133. } else {
  134. loading.setContent("Publish on Twitter...");
  135. await this.twitter.tweet(
  136. this.tweet.value["text"],
  137. this.retweet,
  138. this.replyToStatusId
  139. );
  140. }
  141. loading.dismiss();
  142. this.navCtrl.pop();
  143. }
  144. private async tweetPrivate() {
  145. const tweet = await this.buildPrivateTweet();
  146. const privateKey = await this.storage.get("privateKey");
  147. const encryptedTweet = this.cryptoUtils.encrypt(
  148. JSON.stringify(tweet),
  149. privateKey
  150. );
  151. const res = await this.ipfs.storeTweet(encryptedTweet);
  152. this.gun.storeLastTweetHashForUser(
  153. tweet.user_id,
  154. res["Hash"],
  155. tweet.created_at
  156. );
  157. this.gun.publishHashtags(tweet.entities.hashtags);
  158. }
  159. private async buildPrivateTweet() {
  160. const status = this.tweet.value["text"].trim();
  161. const entities = await this.getEntities(status);
  162. return {
  163. full_text: status,
  164. user_id: await this.storage.get("userId"),
  165. created_at: Date.now(),
  166. private_tweet: true,
  167. in_reply_to_status_id: this.replyToStatusId,
  168. quoted_status_id: this.retweetId,
  169. display_text_range: [0, status.length],
  170. entities: entities
  171. };
  172. }
  173. private async getEntities(status: string) {
  174. return {
  175. hashtags: twittertext.extractHashtagsWithIndices(status),
  176. urls: twittertext.extractUrlsWithIndices(status),
  177. user_mentions: await this.getMentions(status)
  178. };
  179. }
  180. private async getMentions(status: string) {
  181. // extract mentions
  182. const entities = twittertext.extractMentionsWithIndices(status);
  183. // add user_id
  184. const entitiesWithPromises = entities.map(async mention => {
  185. try {
  186. const user = await this.twitter.fetchUserFromScreenName(
  187. mention.screenName
  188. );
  189. mention["id_str"] = user[0]["id_str"];
  190. mention["screen_name"] = mention.screenName;
  191. delete mention.screenName;
  192. } catch (err) {
  193. console.error(
  194. "There is no user signed up to twitter with username: " +
  195. mention.screenName
  196. );
  197. }
  198. return mention;
  199. });
  200. // filter for valid users and return
  201. return (await Promise.all(entitiesWithPromises)).filter(el =>
  202. el.hasOwnProperty("id_str")
  203. );
  204. }
  205. }