bb-fca 2.0.2 → 2.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/deltas/apis/posting/friend.js +84 -0
  2. package/dist/deltas/apis/posting/friend.js.map +1 -1
  3. package/dist/deltas/apis/users/changeBio.js +61 -0
  4. package/dist/deltas/apis/users/changeBio.js.map +1 -0
  5. package/dist/deltas/apis/users/getUserFriends.js +285 -0
  6. package/dist/deltas/apis/users/getUserFriends.js.map +1 -0
  7. package/dist/deltas/apis/users/searchCity.js +91 -0
  8. package/dist/deltas/apis/users/searchCity.js.map +1 -0
  9. package/dist/deltas/apis/users/searchCompany.js +86 -0
  10. package/dist/deltas/apis/users/searchCompany.js.map +1 -0
  11. package/dist/deltas/apis/users/searchJobTitle.js +85 -0
  12. package/dist/deltas/apis/users/searchJobTitle.js.map +1 -0
  13. package/dist/deltas/apis/users/updateCity.js +98 -0
  14. package/dist/deltas/apis/users/updateCity.js.map +1 -0
  15. package/dist/deltas/apis/users/updateHometown.js +97 -0
  16. package/dist/deltas/apis/users/updateHometown.js.map +1 -0
  17. package/dist/deltas/apis/users/updateWorkExperience.js +131 -0
  18. package/dist/deltas/apis/users/updateWorkExperience.js.map +1 -0
  19. package/dist/deltas/apis/users/uploadAvatar.js +174 -0
  20. package/dist/deltas/apis/users/uploadAvatar.js.map +1 -0
  21. package/dist/deltas/apis/users/uploadCover.js +165 -0
  22. package/dist/deltas/apis/users/uploadCover.js.map +1 -0
  23. package/dist/index.d.ts +115 -0
  24. package/dist/types/deltas/apis/posting/friend.d.ts +16 -0
  25. package/dist/types/deltas/apis/users/changeBio.d.ts +10 -0
  26. package/dist/types/deltas/apis/users/getUserFriends.d.ts +30 -0
  27. package/dist/types/deltas/apis/users/searchCity.d.ts +10 -0
  28. package/dist/types/deltas/apis/users/searchCompany.d.ts +10 -0
  29. package/dist/types/deltas/apis/users/searchJobTitle.d.ts +10 -0
  30. package/dist/types/deltas/apis/users/updateCity.d.ts +10 -0
  31. package/dist/types/deltas/apis/users/updateHometown.d.ts +10 -0
  32. package/dist/types/deltas/apis/users/updateWorkExperience.d.ts +30 -0
  33. package/dist/types/deltas/apis/users/uploadAvatar.d.ts +11 -0
  34. package/dist/types/deltas/apis/users/uploadCover.d.ts +15 -0
  35. package/friend.html +534 -0
  36. package/package.json +1 -1
  37. package/proflie.html +527 -0
  38. package/src/deltas/apis/posting/friend.ts +90 -0
  39. package/src/deltas/apis/users/changeBio.ts +68 -0
  40. package/src/deltas/apis/users/getUserFriends.ts +373 -0
  41. package/src/deltas/apis/users/searchCity.ts +106 -0
  42. package/src/deltas/apis/users/searchCompany.ts +100 -0
  43. package/src/deltas/apis/users/searchJobTitle.ts +99 -0
  44. package/src/deltas/apis/users/updateCity.ts +113 -0
  45. package/src/deltas/apis/users/updateHometown.ts +112 -0
  46. package/src/deltas/apis/users/updateWorkExperience.ts +160 -0
  47. package/src/deltas/apis/users/uploadAvatar.ts +172 -0
  48. package/src/deltas/apis/users/uploadCover.ts +167 -0
  49. package/src/types/index.d.ts +115 -0
@@ -0,0 +1,172 @@
1
+ import * as fs from 'fs';
2
+ import utils = require('../../../utils');
3
+
4
+ /**
5
+ * @description A module for uploading a new profile picture (avatar) to Facebook.
6
+ * Two-step process:
7
+ * 1. Upload: sends the image file via multipart/form-data to /profile/picture/upload/
8
+ * 2. Set: calls ProfileCometProfilePictureSetMutation to apply the photo as avatar with optional caption
9
+ * @param {Object} defaultFuncs The default functions provided by the API wrapper.
10
+ * @param {Object} api The full API object.
11
+ * @param {Object} ctx The context object containing the user's session state.
12
+ * @returns {Function} An async function that uploads an avatar image.
13
+ */
14
+ export default function (defaultFuncs: any, api: any, ctx: any) {
15
+ /**
16
+ * Step 2 — Apply the uploaded photo as avatar via ProfileCometProfilePictureSetMutation.
17
+ * @private
18
+ * @param {string} photoID The ID of the uploaded photo.
19
+ * @param {string} profileID The profile ID.
20
+ * @param {string} caption Optional caption for the avatar.
21
+ */
22
+ async function setProfilePicture(photoID: string, profileID: string, caption = '') {
23
+ const variables = {
24
+ input: {
25
+ attribution_id_v2: `ProfileCometTimelineListViewRoot.react,comet.profile.timeline.list,via_cold_start,${Date.now()},473818,190055527696468,,`,
26
+ caption: caption,
27
+ existing_photo_id: photoID,
28
+ expiration_time: null,
29
+ profile_id: profileID,
30
+ profile_pic_method: 'EXISTING',
31
+ profile_pic_source: 'TIMELINE',
32
+ scaled_crop_rect: { height: 0.56328, width: 1, x: 0, y: 0.21836 },
33
+ skip_cropping: true,
34
+ actor_id: ctx.userID,
35
+ client_mutation_id: Math.floor(Math.random() * 10 + 1).toString(),
36
+ },
37
+ isPage: false,
38
+ isProfile: true,
39
+ sectionToken: 'UNKNOWN',
40
+ collectionToken: 'UNKNOWN',
41
+ scale: 1,
42
+ '__relay_internal__pv__ProfileGeminiIsCoinFlipEnabledrelayprovider': false,
43
+ };
44
+
45
+ const form = {
46
+ av: ctx.userID,
47
+ __user: ctx.userID,
48
+ __a: '1',
49
+ fb_dtsg: ctx.fb_dtsg,
50
+ jazoest: ctx.jazoest,
51
+ lsd: ctx.lsd,
52
+ fb_api_caller_class: 'RelayModern',
53
+ fb_api_req_friendly_name: 'ProfileCometProfilePictureSetMutation',
54
+ variables: JSON.stringify(variables),
55
+ server_timestamps: 'true',
56
+ doc_id: '26101621736123633',
57
+ };
58
+
59
+ const res = await defaultFuncs.post(
60
+ 'https://www.facebook.com/api/graphql/',
61
+ ctx.jar,
62
+ form,
63
+ {},
64
+ );
65
+ if (res.data.errors) throw new Error(JSON.stringify(res.data.errors));
66
+ return res.data;
67
+ }
68
+
69
+ /**
70
+ * Uploads a new profile picture (avatar) for the logged-in user, then sets it as avatar with optional caption.
71
+ * @async
72
+ * @param {string} imagePath The local file path to the image to upload.
73
+ * @param {string} [caption=''] Optional caption for the profile picture post.
74
+ * @param {string} [profileID=ctx.userID] The profile ID to upload the avatar for. Defaults to the logged-in user.
75
+ * @param {Function} [callback] Optional callback function.
76
+ * @returns {Promise<Object>} A promise that resolves with { upload, set } data on success.
77
+ * @throws {Error} If the imagePath is missing, the file does not exist, or any step fails.
78
+ *
79
+ * @example
80
+ * await api.uploadAvatar('/path/to/photo.jpg');
81
+ * await api.uploadAvatar('/path/to/photo.jpg', 'My new avatar!');
82
+ * await api.uploadAvatar('/path/to/photo.jpg', 'Caption', '61588408996667');
83
+ */
84
+ return async function uploadAvatar(
85
+ imagePath: string,
86
+ caption: string = '',
87
+ profileID: string = ctx.userID,
88
+ callback?,
89
+ ): Promise<any> {
90
+ let resolveFunc: Function = function () { };
91
+ let rejectFunc: Function = function () { };
92
+
93
+ const returnPromise = new Promise<any>(function (resolve, reject) {
94
+ resolveFunc = resolve;
95
+ rejectFunc = reject;
96
+ });
97
+
98
+ callback =
99
+ callback ||
100
+ function (err, data) {
101
+ if (err) return rejectFunc(err);
102
+ resolveFunc(data);
103
+ };
104
+
105
+ try {
106
+ if (!imagePath) throw new Error('imagePath is required.');
107
+ if (typeof imagePath !== 'string')
108
+ throw new Error('imagePath must be a string.');
109
+ if (!fs.existsSync(imagePath))
110
+ throw new Error(`File not found: ${imagePath}`);
111
+
112
+ // Step 1: Upload the photo
113
+ const photoStream = fs.createReadStream(imagePath);
114
+
115
+ const url = new URL('https://www.facebook.com/profile/picture/upload/');
116
+ url.searchParams.append('photo_source', '57');
117
+ url.searchParams.append('profile_id', profileID);
118
+ url.searchParams.append('av', ctx.userID);
119
+ url.searchParams.append('__aaid', '0');
120
+ url.searchParams.append('__user', ctx.userID);
121
+ url.searchParams.append('__a', '1');
122
+ url.searchParams.append('fb_dtsg', ctx.fb_dtsg);
123
+ url.searchParams.append('jazoest', ctx.jazoest);
124
+ url.searchParams.append('lsd', ctx.lsd);
125
+
126
+ const uploadResponse = await utils.postFormData(
127
+ url.toString(),
128
+ ctx.jar,
129
+ { file: photoStream },
130
+ ctx.globalOptions,
131
+ ctx,
132
+ );
133
+
134
+ const uploadResult = JSON.parse(
135
+ uploadResponse.body.toString().replace(/^for \(;;\);/, ''),
136
+ );
137
+
138
+ if (uploadResult.error || uploadResult.errors) {
139
+ throw new Error(
140
+ JSON.stringify(uploadResult.error || uploadResult.errors),
141
+ );
142
+ }
143
+
144
+ const photoID =
145
+ uploadResult.payload?.fbid ||
146
+ uploadResult.payload?.photoID ||
147
+ uploadResult.payload?.photo_id ||
148
+ null;
149
+
150
+ if (!photoID) {
151
+ throw new Error('Upload succeeded but could not extract photo ID from response.');
152
+ }
153
+
154
+ // Step 2: Set as profile picture with caption
155
+ const setResult = await setProfilePicture(photoID, profileID, caption);
156
+
157
+ const result = {
158
+ success: true,
159
+ photoID: photoID,
160
+ upload: uploadResult,
161
+ set: setResult,
162
+ };
163
+
164
+ callback(null, result);
165
+ } catch (err) {
166
+ utils.error('uploadAvatar', err);
167
+ callback(err);
168
+ }
169
+
170
+ return returnPromise;
171
+ };
172
+ }
@@ -0,0 +1,167 @@
1
+ import * as fs from 'fs';
2
+ import utils = require('../../../utils');
3
+
4
+ /**
5
+ * @ChoruOfficial
6
+ * @description A module for uploading a new cover photo (ảnh bìa) to Facebook.
7
+ * Two-step process:
8
+ * 1. Upload: sends the image file via multipart/form-data to /profile/cover/comet_upload/
9
+ * 2. Confirm: calls ProfileCometCoverPhotoUpdateMutation to apply the photo as cover
10
+ * @param {Object} defaultFuncs The default functions provided by the API wrapper.
11
+ * @param {Object} api The full API object.
12
+ * @param {Object} ctx The context object containing the user's session state.
13
+ * @returns {Function} An async function that uploads a cover photo.
14
+ */
15
+ export default function (defaultFuncs: any, api: any, ctx: any) {
16
+ /**
17
+ * Step 2 — Confirm the uploaded photo as cover via ProfileCometCoverPhotoUpdateMutation.
18
+ * @private
19
+ * @param {string} photoID The ID of the uploaded photo.
20
+ * @param {string} profileID The profile ID.
21
+ * @param {{ x: number, y: number }} [focus] Optional focus point. Defaults to center {x:0.5, y:0.5}.
22
+ */
23
+ async function setCoverPhoto(
24
+ photoID: string,
25
+ profileID: string,
26
+ focus = { x: 0.5, y: 0.5 },
27
+ ) {
28
+ const variables = {
29
+ input: {
30
+ attribution_id_v2: `ProfileCometAboutTabRoot.react,comet.profile.collection.about,via_cold_start,${Date.now()},670164,,,`,
31
+ cover_photo_id: photoID,
32
+ focus: focus,
33
+ target_user_id: profileID,
34
+ actor_id: ctx.userID,
35
+ client_mutation_id: Math.floor(Math.random() * 10 + 1).toString(),
36
+ },
37
+ scale: 1,
38
+ contextualProfileContext: null,
39
+ };
40
+
41
+ const form = {
42
+ av: ctx.userID,
43
+ __user: ctx.userID,
44
+ __a: '1',
45
+ fb_dtsg: ctx.fb_dtsg,
46
+ jazoest: ctx.jazoest,
47
+ lsd: ctx.lsd,
48
+ fb_api_caller_class: 'RelayModern',
49
+ fb_api_req_friendly_name: 'ProfileCometCoverPhotoUpdateMutation',
50
+ variables: JSON.stringify(variables),
51
+ server_timestamps: 'true',
52
+ doc_id: '31388044007461211',
53
+ };
54
+
55
+ const res = await defaultFuncs.post(
56
+ 'https://www.facebook.com/api/graphql/',
57
+ ctx.jar,
58
+ form,
59
+ {},
60
+ );
61
+ if (res.data.errors) throw new Error(JSON.stringify(res.data.errors));
62
+ return res.data;
63
+ }
64
+
65
+ /**
66
+ * Uploads a new cover photo (ảnh bìa) for the logged-in user, then confirms it as the cover.
67
+ * @async
68
+ * @param {string} imagePath The local file path to the image to upload.
69
+ * @param {string} [profileID=ctx.userID] The profile ID. Defaults to the logged-in user.
70
+ * @param {{ x: number, y: number }} [focus] Optional focus point for cropping. Defaults to {x:0.5, y:0.5}.
71
+ * @param {Function} [callback] Optional callback function.
72
+ * @returns {Promise<Object>} A promise that resolves with { upload, set } data on success.
73
+ * @throws {Error} If the imagePath is missing, the file does not exist, or any step fails.
74
+ *
75
+ * @example
76
+ * await api.uploadCover('/path/to/cover.jpg');
77
+ * await api.uploadCover('/path/to/cover.jpg', '61588408996667');
78
+ * await api.uploadCover('/path/to/cover.jpg', ctx.userID, { x: 0.5, y: 0.3 });
79
+ */
80
+ return async function uploadCover(
81
+ imagePath: string,
82
+ profileID: string = ctx.userID,
83
+ focus = { x: 0.5, y: 0.5 },
84
+ callback?,
85
+ ): Promise<any> {
86
+ let resolveFunc: Function = function () { };
87
+ let rejectFunc: Function = function () { };
88
+
89
+ const returnPromise = new Promise<any>(function (resolve, reject) {
90
+ resolveFunc = resolve;
91
+ rejectFunc = reject;
92
+ });
93
+
94
+ callback =
95
+ callback ||
96
+ function (err, data) {
97
+ if (err) return rejectFunc(err);
98
+ resolveFunc(data);
99
+ };
100
+
101
+ try {
102
+ if (!imagePath) throw new Error('imagePath is required.');
103
+ if (typeof imagePath !== 'string')
104
+ throw new Error('imagePath must be a string.');
105
+ if (!fs.existsSync(imagePath))
106
+ throw new Error(`File not found: ${imagePath}`);
107
+
108
+ // Step 1: Upload the photo
109
+ const photoStream = fs.createReadStream(imagePath);
110
+
111
+ const url = new URL('https://www.facebook.com/profile/cover/comet_upload/');
112
+ url.searchParams.append('profile_id', profileID);
113
+ url.searchParams.append('av', ctx.userID);
114
+ url.searchParams.append('__aaid', '0');
115
+ url.searchParams.append('__user', ctx.userID);
116
+ url.searchParams.append('__a', '1');
117
+ url.searchParams.append('fb_dtsg', ctx.fb_dtsg);
118
+ url.searchParams.append('jazoest', ctx.jazoest);
119
+ url.searchParams.append('lsd', ctx.lsd);
120
+
121
+ const uploadResponse = await utils.postFormData(
122
+ url.toString(),
123
+ ctx.jar,
124
+ { file: photoStream },
125
+ ctx.globalOptions,
126
+ ctx,
127
+ );
128
+
129
+ const uploadResult = JSON.parse(
130
+ uploadResponse.body.toString().replace(/^for \(;;\);/, ''),
131
+ );
132
+
133
+ if (uploadResult.error || uploadResult.errors) {
134
+ throw new Error(
135
+ JSON.stringify(uploadResult.error || uploadResult.errors),
136
+ );
137
+ }
138
+
139
+ const photoID =
140
+ uploadResult.payload?.fbid ||
141
+ uploadResult.payload?.photoID ||
142
+ uploadResult.payload?.photo_id ||
143
+ null;
144
+
145
+ if (!photoID) {
146
+ throw new Error('Upload succeeded but could not extract photo ID from response.');
147
+ }
148
+
149
+ // Step 2: Confirm as cover photo
150
+ const setResult = await setCoverPhoto(photoID, profileID, focus);
151
+
152
+ const result = {
153
+ success: true,
154
+ photoID: photoID,
155
+ upload: uploadResult,
156
+ set: setResult,
157
+ };
158
+
159
+ callback(null, result);
160
+ } catch (err) {
161
+ utils.error('uploadCover', err);
162
+ callback(err);
163
+ }
164
+
165
+ return returnPromise;
166
+ };
167
+ }
@@ -699,6 +699,121 @@ export interface API {
699
699
 
700
700
  // ── Session ────────────────────────────────────────────────────
701
701
 
702
+ /**
703
+ * Searches for cities matching the given query string.
704
+ * Uses Facebook's Directory Typeahead data source.
705
+ * @param query The search query (city name or partial name).
706
+ * @param searchCategory The category to search (default: 'CURRENT_CITY').
707
+ */
708
+ searchCity(
709
+ query: string,
710
+ searchCategory?: string,
711
+ ): Promise<Array<{
712
+ fbid: string;
713
+ title: string;
714
+ value: string;
715
+ photoUri: string;
716
+ subtitle: string;
717
+ secondSubtitle: string;
718
+ }>>;
719
+
720
+ /**
721
+ * Searches for companies/workplaces matching the given query string.
722
+ * Uses Facebook's Directory Typeahead data source with WORKPLACE category.
723
+ * @param query The search query (company name or partial name).
724
+ */
725
+ searchCompany(
726
+ query: string,
727
+ ): Promise<Array<{
728
+ fbid: string;
729
+ title: string;
730
+ value: string;
731
+ photoUri: string;
732
+ subtitle: string;
733
+ secondSubtitle: string;
734
+ }>>;
735
+
736
+ /**
737
+ * Searches for job titles/positions matching the given query string.
738
+ * Uses Facebook's Directory Typeahead data source with JOB_TITLE category.
739
+ * @param query The search query (job title or partial name).
740
+ */
741
+ searchJobTitle(
742
+ query: string,
743
+ ): Promise<Array<{
744
+ fbid: string;
745
+ title: string;
746
+ value: string;
747
+ photoUri: string;
748
+ subtitle: string;
749
+ secondSubtitle: string;
750
+ }>>;
751
+
752
+ /**
753
+ * Fetches the friend list of a given user with pagination support.
754
+ * @param userID The Facebook user ID whose friends to fetch.
755
+ * @param count Number of friends per page (default: 8).
756
+ * @param cursor Pagination cursor from previous response's endCursor (default: null).
757
+ */
758
+ getUserFriends(
759
+ userID: string,
760
+ count?: number,
761
+ cursor?: string | null,
762
+ ): Promise<{
763
+ friends: Array<{
764
+ id: string;
765
+ name: string;
766
+ avatarUri: string;
767
+ profileUrl: string;
768
+ mutualFriendsText: string;
769
+ friendshipStatus: string;
770
+ gender: string;
771
+ }>;
772
+ endCursor: string | null;
773
+ hasNextPage: boolean;
774
+ }>;
775
+
776
+ /**
777
+ * Adds or updates a work experience entry on the profile.
778
+ * Use searchCompany() and searchJobTitle() to get IDs first.
779
+ * @param options Work experience details.
780
+ */
781
+ updateWorkExperience(options: {
782
+ companyName: string;
783
+ companyId?: string;
784
+ positionName?: string;
785
+ positionId?: string;
786
+ isCurrent?: boolean;
787
+ startDate?: { year?: number; month?: number; day?: number };
788
+ endDate?: { year?: number; month?: number; day?: number };
789
+ description?: string;
790
+ locationId?: string;
791
+ privacy?: string;
792
+ workExperienceID?: string | null;
793
+ }): Promise<any>;
794
+
795
+ /**
796
+ * Updates the current city on the profile.
797
+ * Use searchCity() to get the city fbid first.
798
+ * @param cityId The Facebook ID of the city (from searchCity's fbid field).
799
+ * @param privacy Privacy setting: 'EVERYONE', 'FRIENDS', or 'SELF' (default: 'EVERYONE').
800
+ */
801
+ updateCity(
802
+ cityId: string,
803
+ privacy?: string,
804
+ ): Promise<any>;
805
+
806
+ /**
807
+ * Updates the hometown on the profile.
808
+ * Use searchCity() to get the city fbid first.
809
+ * @param cityId The Facebook ID of the city (from searchCity's fbid field).
810
+ * @param privacy Privacy setting: 'EVERYONE', 'FRIENDS', or 'SELF' (default: 'EVERYONE').
811
+ */
812
+ updateHometown(
813
+ cityId: string,
814
+ privacy?: string,
815
+ ): Promise<any>;
816
+
702
817
  /**
703
818
  * Changes the Facebook account display name (two-step: preview then confirm).
704
819
  * Calls AccountsCenter GraphQL API.