write-tweet.ts 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  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. import * as openpgp from 'openpgp';
  23. @IonicPage()
  24. @Component({
  25. selector: "page-write-tweet",
  26. templateUrl: "write-tweet.html"
  27. })
  28. export class WriteTweetPage {
  29. tweet: FormGroup;
  30. retweetId: string;
  31. replyToStatusId: string;
  32. retweet;
  33. replyTweet;
  34. openpgp;
  35. privateKey;
  36. publicKey;
  37. passp = 'super long and hard to guess secret' ;
  38. constructor(
  39. public navCtrl: NavController,
  40. public navParams: NavParams,
  41. private formBuilder: FormBuilder,
  42. private twitter: TwitterApiProvider,
  43. private loadingCtrl: LoadingController,
  44. private storage: Storage,
  45. private ipfs: P2pStorageIpfsProvider,
  46. private gun: P2pDatabaseGunProvider,
  47. private cryptoUtils: CryptoProvider,
  48. private alertCtrl: AlertController
  49. ) {
  50. this.retweetId = this.navParams.get("tweetId");
  51. this.replyToStatusId = this.navParams.get("replyToStatus");
  52. this.tweet = this.formBuilder.group({
  53. text: [""],
  54. p2p: [false]
  55. });
  56. this.addValidators();
  57. this.generateKeys();
  58. }
  59. public async encryptDecryptFunction () {
  60. // await openpgp.initWorker({});
  61. // await this.generateKeys();
  62. console.log('priv key: ',this.privateKey,'this . pubkey',this.publicKey);
  63. let encrypted;
  64. const privKeyObj = (await openpgp.key.readArmored(this.privateKey)).keys[0];
  65. console.log('privKeyObj',privKeyObj);
  66. await privKeyObj.decrypt(this.passp)
  67. const options = {
  68. message: openpgp.message.fromText('Hello, World!'), // input as Message object
  69. publicKeys: (await openpgp.key.readArmored(this.publicKey)).keys, // for encryption
  70. privateKeys: [privKeyObj] // for signing (optional)
  71. }
  72. openpgp.encrypt(options).then(ciphertext => {
  73. encrypted = ciphertext.data // '-----BEGIN PGP MESSAGE ... END PGP MESSAGE-----'
  74. console.log('encrypted text is:',encrypted);
  75. return encrypted
  76. })
  77. .then(async encrypted => {
  78. const options = {
  79. message: await openpgp.message.readArmored(encrypted), // parse armored message
  80. publicKeys: (await openpgp.key.readArmored(this.publicKey)).keys, // for verification (optional)
  81. privateKeys: [privKeyObj] // for decryption
  82. }
  83. openpgp.decrypt(options).then(plaintext => {
  84. console.log('decrypted text is:',plaintext,plaintext.data);
  85. return plaintext.data // 'Hello, World!'
  86. })
  87. })
  88. }
  89. public async generateKeys(){
  90. let options = {
  91. userIds: [{ name:'Jon Smith', email:'jon@example.com' }], // multiple user IDs
  92. curve: "ed25519", // ECC curve name
  93. passphrase: this.passp // protects the private key
  94. };
  95. // var vm = this;
  96. // openpgp.generateKey(options)
  97. // .then(function(vm,key) {
  98. // if(key){
  99. // var privkey = key.privateKeyArmored; // '-----BEGIN PGP PRIVATE KEY BLOCK ... '
  100. // var pubkey = key.publicKeyArmored; // '-----BEGIN PGP PUBLIC KEY BLOCK ... '
  101. // var revocationCertificate = key.revocationCertificate; // '-----BEGIN PGP PUBLIC KEY BLOCK ... '
  102. // vm.privateKey =privkey;
  103. // vm.publicKey = pubkey;
  104. // }
  105. // })
  106. const a = await openpgp.generateKey(options);
  107. console.log('resolved a = ',a);
  108. this.privateKey =a.privateKeyArmored;
  109. this.publicKey = a.publicKeyArmored;
  110. this.encryptDecryptFunction();
  111. }
  112. private async addValidators() {
  113. const triggerWords = await this.storage.get("keywords");
  114. const validators = [
  115. Validators.maxLength(140),
  116. this.containsTriggerWord(triggerWords)
  117. ];
  118. this.tweet.controls["text"].setValidators(validators);
  119. }
  120. private containsTriggerWord(triggerWords: string): ValidatorFn {
  121. return (control: AbstractControl): { [key: string]: any } | null => {
  122. if (triggerWords) {
  123. const regexList = triggerWords
  124. .toLowerCase()
  125. .split(", ")
  126. .join("|");
  127. const regex = new RegExp(regexList);
  128. const containsTriggerWord = regex.test(control.value.toLowerCase());
  129. return containsTriggerWord
  130. ? { containsTriggerWord: { value: control.value } }
  131. : null;
  132. } else {
  133. return null;
  134. }
  135. };
  136. }
  137. async ionViewDidLoad() {
  138. if (this.retweetId) {
  139. this.retweet = await this.twitter.fetchTweet(this.retweetId);
  140. }
  141. if (this.replyToStatusId) {
  142. this.replyTweet = await this.twitter.fetchTweet(this.replyToStatusId);
  143. }
  144. }
  145. get tweetCharProgress() {
  146. const progress = 1 - this.tweet.value["text"].length / 140;
  147. const radius = 8;
  148. const circumference = Math.PI * radius * 2;
  149. return progress * circumference;
  150. }
  151. get showTrigger(): boolean {
  152. return (
  153. this.tweet &&
  154. this.tweet.controls &&
  155. this.tweet.controls.text &&
  156. this.tweet.controls.text.errors &&
  157. this.tweet.controls.text.errors["containsTriggerWord"] &&
  158. !this.tweet.controls.p2p.value
  159. );
  160. }
  161. showTriggerInfo() {
  162. this.alertCtrl
  163. .create({
  164. title: "Watch Out!",
  165. message:
  166. "Your tweet contains words you have previously defined to only share securely via P2P. Currently P2P mode is not selected.",
  167. buttons: ["OK"]
  168. })
  169. .present();
  170. }
  171. async submitTweet() {
  172. const loading = this.loadingCtrl.create();
  173. loading.present();
  174. if (this.tweet.value.p2p) {
  175. loading.setContent("Validate keys...");
  176. if (
  177. (await this.cryptoUtils.isPrivateKeySet()) &&
  178. (await this.cryptoUtils.isPublicKeyPublished())
  179. ) {
  180. loading.setContent("Publish private tweet...");
  181. await this.tweetPrivate();
  182. } else {
  183. loading.dismiss();
  184. const alert = this.alertCtrl.create({
  185. title: "Oooops...",
  186. message:
  187. "Please verify that you have set a private and public key in the settings and that your latest public key was published."
  188. });
  189. alert.present();
  190. return;
  191. }
  192. } else {
  193. loading.setContent("Publish on Twitter...");
  194. await this.twitter.tweet(
  195. this.tweet.value["text"],
  196. this.retweet,
  197. this.replyToStatusId
  198. );
  199. }
  200. loading.dismiss();
  201. this.navCtrl.pop();
  202. }
  203. private async tweetPrivate() {
  204. const tweet = await this.buildPrivateTweet();
  205. const privateKey = await this.storage.get("privateKey");
  206. const encryptedTweet = this.cryptoUtils.encrypt(
  207. JSON.stringify(tweet),
  208. privateKey
  209. );
  210. const res = await this.ipfs.storeTweet(encryptedTweet);
  211. this.gun.storeLastTweetHashForUser(
  212. tweet.user_id,
  213. res["Hash"],
  214. tweet.created_at
  215. );
  216. this.gun.publishHashtags(tweet.entities.hashtags);
  217. }
  218. private async buildPrivateTweet() {
  219. const status = this.tweet.value["text"].trim();
  220. const entities = await this.getEntities(status);
  221. return {
  222. full_text: status,
  223. user_id: await this.storage.get("userId"),
  224. created_at: Date.now(),
  225. private_tweet: true,
  226. in_reply_to_status_id: this.replyToStatusId,
  227. quoted_status_id: this.retweetId,
  228. display_text_range: [0, status.length],
  229. entities: entities
  230. };
  231. }
  232. private async getEntities(status: string) {
  233. return {
  234. hashtags: twittertext.extractHashtagsWithIndices(status),
  235. urls: twittertext.extractUrlsWithIndices(status),
  236. user_mentions: await this.getMentions(status)
  237. };
  238. }
  239. private async getMentions(status: string) {
  240. // extract mentions
  241. const entities = twittertext.extractMentionsWithIndices(status);
  242. // add user_id
  243. const entitiesWithPromises = entities.map(async mention => {
  244. try {
  245. const user = await this.twitter.fetchUserFromScreenName(
  246. mention.screenName
  247. );
  248. mention["id_str"] = user[0]["id_str"];
  249. mention["screen_name"] = mention.screenName;
  250. delete mention.screenName;
  251. } catch (err) {
  252. console.error(
  253. "There is no user signed up to twitter with username: " +
  254. mention.screenName
  255. );
  256. }
  257. return mention;
  258. });
  259. // filter for valid users and return
  260. return (await Promise.all(entitiesWithPromises)).filter(el =>
  261. el.hasOwnProperty("id_str")
  262. );
  263. }
  264. }