strapi-plugin-xquik 0.0.0-stage → 1.0.0
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.
- package/LICENSE +21 -0
- package/README.md +150 -2
- package/dist/server/index.js +114 -0
- package/dist/server/index.mjs +114 -0
- package/dist/server/src/config.d.ts +11 -0
- package/dist/server/src/index.d.ts +23 -0
- package/dist/server/src/services/xquik.d.ts +69 -0
- package/package.json +67 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Xquik
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,151 @@
|
|
|
1
|
-
#
|
|
1
|
+
# X API & Twitter API Plugin for Strapi 5
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<img src="https://raw.githubusercontent.com/Xquik-dev/strapi-plugin-xquik/main/assets/logo.png" alt="Xquik logo" width="96" height="96">
|
|
4
|
+
|
|
5
|
+
[Xquik](https://xquik.com) is the world's fastest and cheapest X Twitter data API with the most complete X data.
|
|
6
|
+
Searches & extractions charge only for delivered, unique, filter-matching results.
|
|
7
|
+
|
|
8
|
+
Xquik is an independent third-party service. Not affiliated with X Corp. "Twitter" and "X" are trademarks of X Corp.
|
|
9
|
+
Xquik makes this plugin. Strapi doesn't make or endorse it.
|
|
10
|
+
|
|
11
|
+
This plugin adds an `xquik` service to your Strapi 5 server.
|
|
12
|
+
The service searches X posts & reads X profiles.
|
|
13
|
+
It runs on the server only. It adds no admin pages, routes or content types.
|
|
14
|
+
|
|
15
|
+
## Requirements
|
|
16
|
+
|
|
17
|
+
- Strapi 5
|
|
18
|
+
- Node.js 20 to 26
|
|
19
|
+
- An Xquik API key from your dashboard at [xquik.com](https://xquik.com)
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
npm install strapi-plugin-xquik
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Strapi 5 enables the plugin on its next start.
|
|
28
|
+
|
|
29
|
+
## Configure
|
|
30
|
+
|
|
31
|
+
Add your API key to your app's `.env` file.
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
XQUIK_API_KEY=xq_...
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The plugin reads `XQUIK_API_KEY` by default.
|
|
38
|
+
To set the key another way, set `apiKey` in `config/plugins.js`.
|
|
39
|
+
|
|
40
|
+
```js
|
|
41
|
+
module.exports = ({ env }) => ({
|
|
42
|
+
xquik: {
|
|
43
|
+
config: {
|
|
44
|
+
apiKey: env("XQUIK_API_KEY"),
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
| Option | Type | Default | Use |
|
|
51
|
+
| -------- | ------ | ------------------------ | ---------------------------------------------- |
|
|
52
|
+
| `apiKey` | string | `XQUIK_API_KEY`, or `""` | The key the plugin sends in `x-api-key`. |
|
|
53
|
+
|
|
54
|
+
The plugin reads the key on each call.
|
|
55
|
+
With no key, a call fails before it sends a request.
|
|
56
|
+
|
|
57
|
+
## Search posts
|
|
58
|
+
|
|
59
|
+
Call the service from any controller, service or lifecycle hook.
|
|
60
|
+
|
|
61
|
+
```js
|
|
62
|
+
const page = await strapi
|
|
63
|
+
.plugin("xquik")
|
|
64
|
+
.service("xquik")
|
|
65
|
+
.searchPosts({ q: "from:NASA", limit: 10 });
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`searchPosts(query)` takes these fields.
|
|
69
|
+
|
|
70
|
+
| Field | Type | Use |
|
|
71
|
+
| ----------- | ------ | ------------------------------------------------------------------ |
|
|
72
|
+
| `q` | string | Required. A search query, a post ID or a post URL. |
|
|
73
|
+
| `limit` | number | Most posts to return, from 1 to 10,000. The API default is 20. |
|
|
74
|
+
| `cursor` | string | The `next_cursor` of the previous page. |
|
|
75
|
+
| `queryType` | string | `Latest` lists the newest posts first. `Top` lists high-engagement posts first.|
|
|
76
|
+
|
|
77
|
+
It also passes every other search filter in the [search API reference](https://docs.xquik.com/api-reference/x/search-tweets).
|
|
78
|
+
Examples are `language`, `minLikes`, `sinceDate` & `untilDate`.
|
|
79
|
+
`q` takes [X search operators](https://github.com/Xquik-dev/x-twitter-advanced-search), such as `from:` & `since:`.
|
|
80
|
+
|
|
81
|
+
It returns `{ tweets, has_next_page, next_cursor }`.
|
|
82
|
+
Each post has an `id`. It also has fields such as `text`, `createdAt`, `likeCount`, `viewCount` & `author`.
|
|
83
|
+
|
|
84
|
+
To read the next page, pass `next_cursor` as `cursor` with the same filters.
|
|
85
|
+
Stop when `has_next_page` is `false`.
|
|
86
|
+
|
|
87
|
+
## Read a profile
|
|
88
|
+
|
|
89
|
+
```js
|
|
90
|
+
const profile = await strapi.plugin("xquik").service("xquik").getUser("NASA");
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`getUser(id)` takes a user ID, a username with or without `@`, or a profile URL such as `x.com/nasa`.
|
|
94
|
+
It returns the profile with `id`, `username` & `name`.
|
|
95
|
+
It also has fields such as `description`, `followers`, `following` & `verified`.
|
|
96
|
+
The [profile API reference](https://docs.xquik.com/api-reference/x/get-user) lists every field.
|
|
97
|
+
|
|
98
|
+
## Handle errors
|
|
99
|
+
|
|
100
|
+
Each failure throws an `XquikError`.
|
|
101
|
+
Its `code` names the failure. Its `status` holds the HTTP status when the API answered.
|
|
102
|
+
|
|
103
|
+
| `code` | `status` | Next step |
|
|
104
|
+
| ---------------------- | ----------- | ----------------------------------------------------------- |
|
|
105
|
+
| `missing_api_key` | `undefined` | Set `XQUIK_API_KEY` or `apiKey`. |
|
|
106
|
+
| `network_error` | `undefined` | The request failed or took over 30 seconds. Retry. |
|
|
107
|
+
| `unauthenticated` | `401` | Check your API key. |
|
|
108
|
+
| `insufficient_credits` | `402` | Add credits in your Xquik dashboard. |
|
|
109
|
+
| `rate_limit_exceeded` | `429` | Wait, then retry. |
|
|
110
|
+
| `http_error` | The status | The API sent an error without a JSON body. Retry. |
|
|
111
|
+
| `invalid_response` | The status | The response didn't match the contract. Update the plugin. |
|
|
112
|
+
|
|
113
|
+
Other API errors keep the API's own `code` & `message`.
|
|
114
|
+
The [API reference](https://docs.xquik.com/api-reference/overview) lists every code.
|
|
115
|
+
|
|
116
|
+
This controller returns search results & turns an `XquikError` into a JSON error.
|
|
117
|
+
|
|
118
|
+
```js
|
|
119
|
+
module.exports = ({ strapi }) => ({
|
|
120
|
+
async search(ctx) {
|
|
121
|
+
try {
|
|
122
|
+
ctx.body = await strapi
|
|
123
|
+
.plugin("xquik")
|
|
124
|
+
.service("xquik")
|
|
125
|
+
.searchPosts({ q: ctx.query.q, limit: 10 });
|
|
126
|
+
} catch (error) {
|
|
127
|
+
if (error.name !== "XquikError") {
|
|
128
|
+
throw error;
|
|
129
|
+
}
|
|
130
|
+
ctx.status = error.status ?? 500;
|
|
131
|
+
ctx.body = { error: { code: error.code, message: error.message } };
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Develop
|
|
138
|
+
|
|
139
|
+
```sh
|
|
140
|
+
bin/check-local
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
`bin/check-local` runs every check in order.
|
|
144
|
+
It installs, then checks format, lint, types & tests.
|
|
145
|
+
It builds & verifies the plugin, then runs `npm audit` & `npm pack --dry-run`.
|
|
146
|
+
Last, it starts a fresh Strapi 5 app with the packed plugin & calls the service over HTTP.
|
|
147
|
+
With `XQUIK_API_KEY` set, that last check calls the live API.
|
|
148
|
+
|
|
149
|
+
## License
|
|
150
|
+
|
|
151
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
|
|
3
|
+
const zod = require("zod");
|
|
4
|
+
const config = {
|
|
5
|
+
default: ({ env }) => ({
|
|
6
|
+
apiKey: env("XQUIK_API_KEY", "")
|
|
7
|
+
}),
|
|
8
|
+
validator(config2) {
|
|
9
|
+
if (typeof config2.apiKey !== "string") {
|
|
10
|
+
throw new TypeError("Set the xquik apiKey config to a string.");
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
const XQUIK_URL = "https://xquik.com";
|
|
15
|
+
const API_URL = `${XQUIK_URL}/api/v1/x`;
|
|
16
|
+
const REQUEST_TIMEOUT_MS = 3e4;
|
|
17
|
+
class XquikError extends Error {
|
|
18
|
+
name = "XquikError";
|
|
19
|
+
code;
|
|
20
|
+
status;
|
|
21
|
+
constructor(code, message, status, options) {
|
|
22
|
+
super(message, options);
|
|
23
|
+
this.code = code;
|
|
24
|
+
this.status = status;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const postPageSchema = zod.z.looseObject({
|
|
28
|
+
tweets: zod.z.array(zod.z.looseObject({ id: zod.z.string() })),
|
|
29
|
+
has_next_page: zod.z.boolean(),
|
|
30
|
+
next_cursor: zod.z.string()
|
|
31
|
+
});
|
|
32
|
+
const userProfileSchema = zod.z.looseObject({
|
|
33
|
+
id: zod.z.string(),
|
|
34
|
+
username: zod.z.string(),
|
|
35
|
+
name: zod.z.string()
|
|
36
|
+
});
|
|
37
|
+
const failureSchema = zod.z.looseObject({
|
|
38
|
+
error: zod.z.string(),
|
|
39
|
+
message: zod.z.string().optional()
|
|
40
|
+
});
|
|
41
|
+
const failureFrom = (status, body) => {
|
|
42
|
+
const failure = failureSchema.safeParse(body);
|
|
43
|
+
return failure.success ? new XquikError(
|
|
44
|
+
failure.data.error,
|
|
45
|
+
failure.data.message ?? "Xquik rejected the request. Check the error code.",
|
|
46
|
+
status
|
|
47
|
+
) : new XquikError(
|
|
48
|
+
"http_error",
|
|
49
|
+
"Xquik returned an unknown error. Retry later.",
|
|
50
|
+
status
|
|
51
|
+
);
|
|
52
|
+
};
|
|
53
|
+
const readApi = async (strapi, path, schema) => {
|
|
54
|
+
const apiKey = strapi.plugin("xquik").config("apiKey");
|
|
55
|
+
if (apiKey === "") {
|
|
56
|
+
throw new XquikError(
|
|
57
|
+
"missing_api_key",
|
|
58
|
+
`Set XQUIK_API_KEY. Get a key at ${XQUIK_URL}.`
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
const response = await fetch(`${API_URL}${path}`, {
|
|
62
|
+
headers: { accept: "application/json", "x-api-key": apiKey },
|
|
63
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
64
|
+
redirect: "error"
|
|
65
|
+
}).catch((cause) => {
|
|
66
|
+
throw new XquikError(
|
|
67
|
+
"network_error",
|
|
68
|
+
"Xquik did not answer. Check the network and retry.",
|
|
69
|
+
void 0,
|
|
70
|
+
{ cause }
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
const body = await response.json().catch(() => null);
|
|
74
|
+
if (!response.ok) {
|
|
75
|
+
throw failureFrom(response.status, body);
|
|
76
|
+
}
|
|
77
|
+
const parsed = schema.safeParse(body);
|
|
78
|
+
if (!parsed.success) {
|
|
79
|
+
throw new XquikError(
|
|
80
|
+
"invalid_response",
|
|
81
|
+
"Xquik sent an unexpected response. Update the plugin or retry.",
|
|
82
|
+
response.status
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
return parsed.data;
|
|
86
|
+
};
|
|
87
|
+
const xquikService = ({ strapi }) => ({
|
|
88
|
+
searchPosts(query) {
|
|
89
|
+
const params = new URLSearchParams();
|
|
90
|
+
for (const [name, value] of Object.entries(query)) {
|
|
91
|
+
if (value !== void 0) {
|
|
92
|
+
params.set(name, String(value));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return readApi(
|
|
96
|
+
strapi,
|
|
97
|
+
`/tweets/search?${params.toString()}`,
|
|
98
|
+
postPageSchema
|
|
99
|
+
);
|
|
100
|
+
},
|
|
101
|
+
getUser(id) {
|
|
102
|
+
return readApi(
|
|
103
|
+
strapi,
|
|
104
|
+
`/users/${encodeURIComponent(id)}`,
|
|
105
|
+
userProfileSchema
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
const index = {
|
|
110
|
+
config,
|
|
111
|
+
services: { xquik: xquikService }
|
|
112
|
+
};
|
|
113
|
+
exports.XquikError = XquikError;
|
|
114
|
+
exports.default = index;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const config = {
|
|
3
|
+
default: ({ env }) => ({
|
|
4
|
+
apiKey: env("XQUIK_API_KEY", "")
|
|
5
|
+
}),
|
|
6
|
+
validator(config2) {
|
|
7
|
+
if (typeof config2.apiKey !== "string") {
|
|
8
|
+
throw new TypeError("Set the xquik apiKey config to a string.");
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
const XQUIK_URL = "https://xquik.com";
|
|
13
|
+
const API_URL = `${XQUIK_URL}/api/v1/x`;
|
|
14
|
+
const REQUEST_TIMEOUT_MS = 3e4;
|
|
15
|
+
class XquikError extends Error {
|
|
16
|
+
name = "XquikError";
|
|
17
|
+
code;
|
|
18
|
+
status;
|
|
19
|
+
constructor(code, message, status, options) {
|
|
20
|
+
super(message, options);
|
|
21
|
+
this.code = code;
|
|
22
|
+
this.status = status;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const postPageSchema = z.looseObject({
|
|
26
|
+
tweets: z.array(z.looseObject({ id: z.string() })),
|
|
27
|
+
has_next_page: z.boolean(),
|
|
28
|
+
next_cursor: z.string()
|
|
29
|
+
});
|
|
30
|
+
const userProfileSchema = z.looseObject({
|
|
31
|
+
id: z.string(),
|
|
32
|
+
username: z.string(),
|
|
33
|
+
name: z.string()
|
|
34
|
+
});
|
|
35
|
+
const failureSchema = z.looseObject({
|
|
36
|
+
error: z.string(),
|
|
37
|
+
message: z.string().optional()
|
|
38
|
+
});
|
|
39
|
+
const failureFrom = (status, body) => {
|
|
40
|
+
const failure = failureSchema.safeParse(body);
|
|
41
|
+
return failure.success ? new XquikError(
|
|
42
|
+
failure.data.error,
|
|
43
|
+
failure.data.message ?? "Xquik rejected the request. Check the error code.",
|
|
44
|
+
status
|
|
45
|
+
) : new XquikError(
|
|
46
|
+
"http_error",
|
|
47
|
+
"Xquik returned an unknown error. Retry later.",
|
|
48
|
+
status
|
|
49
|
+
);
|
|
50
|
+
};
|
|
51
|
+
const readApi = async (strapi, path, schema) => {
|
|
52
|
+
const apiKey = strapi.plugin("xquik").config("apiKey");
|
|
53
|
+
if (apiKey === "") {
|
|
54
|
+
throw new XquikError(
|
|
55
|
+
"missing_api_key",
|
|
56
|
+
`Set XQUIK_API_KEY. Get a key at ${XQUIK_URL}.`
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
const response = await fetch(`${API_URL}${path}`, {
|
|
60
|
+
headers: { accept: "application/json", "x-api-key": apiKey },
|
|
61
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
62
|
+
redirect: "error"
|
|
63
|
+
}).catch((cause) => {
|
|
64
|
+
throw new XquikError(
|
|
65
|
+
"network_error",
|
|
66
|
+
"Xquik did not answer. Check the network and retry.",
|
|
67
|
+
void 0,
|
|
68
|
+
{ cause }
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
const body = await response.json().catch(() => null);
|
|
72
|
+
if (!response.ok) {
|
|
73
|
+
throw failureFrom(response.status, body);
|
|
74
|
+
}
|
|
75
|
+
const parsed = schema.safeParse(body);
|
|
76
|
+
if (!parsed.success) {
|
|
77
|
+
throw new XquikError(
|
|
78
|
+
"invalid_response",
|
|
79
|
+
"Xquik sent an unexpected response. Update the plugin or retry.",
|
|
80
|
+
response.status
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
return parsed.data;
|
|
84
|
+
};
|
|
85
|
+
const xquikService = ({ strapi }) => ({
|
|
86
|
+
searchPosts(query) {
|
|
87
|
+
const params = new URLSearchParams();
|
|
88
|
+
for (const [name, value] of Object.entries(query)) {
|
|
89
|
+
if (value !== void 0) {
|
|
90
|
+
params.set(name, String(value));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return readApi(
|
|
94
|
+
strapi,
|
|
95
|
+
`/tweets/search?${params.toString()}`,
|
|
96
|
+
postPageSchema
|
|
97
|
+
);
|
|
98
|
+
},
|
|
99
|
+
getUser(id) {
|
|
100
|
+
return readApi(
|
|
101
|
+
strapi,
|
|
102
|
+
`/users/${encodeURIComponent(id)}`,
|
|
103
|
+
userProfileSchema
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
const index = {
|
|
108
|
+
config,
|
|
109
|
+
services: { xquik: xquikService }
|
|
110
|
+
};
|
|
111
|
+
export {
|
|
112
|
+
XquikError,
|
|
113
|
+
index as default
|
|
114
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export { XquikError } from './services/xquik';
|
|
2
|
+
export type { Post, PostPage, SearchPostsQuery, UserProfile, } from './services/xquik';
|
|
3
|
+
declare const _default: {
|
|
4
|
+
config: {
|
|
5
|
+
default: ({ env }: {
|
|
6
|
+
env: (key: string, fallback: string) => string;
|
|
7
|
+
}) => {
|
|
8
|
+
apiKey: string;
|
|
9
|
+
};
|
|
10
|
+
validator(config: {
|
|
11
|
+
apiKey: unknown;
|
|
12
|
+
}): void;
|
|
13
|
+
};
|
|
14
|
+
services: {
|
|
15
|
+
xquik: ({ strapi }: {
|
|
16
|
+
strapi: import('./services/xquik').StrapiHandle;
|
|
17
|
+
}) => {
|
|
18
|
+
searchPosts(query: import('.').SearchPostsQuery): Promise<import('.').PostPage>;
|
|
19
|
+
getUser(id: string): Promise<import('.').UserProfile>;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
export default _default;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
export declare class XquikError extends Error {
|
|
2
|
+
readonly name = "XquikError";
|
|
3
|
+
readonly code: string;
|
|
4
|
+
readonly status: number | undefined;
|
|
5
|
+
constructor(code: string, message: string, status?: number, options?: ErrorOptions);
|
|
6
|
+
}
|
|
7
|
+
export interface UserProfile {
|
|
8
|
+
id: string;
|
|
9
|
+
username: string;
|
|
10
|
+
name: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
followers?: number;
|
|
13
|
+
following?: number;
|
|
14
|
+
verified?: boolean;
|
|
15
|
+
isBlueVerified?: boolean;
|
|
16
|
+
isVerified?: boolean;
|
|
17
|
+
profilePicture?: string;
|
|
18
|
+
profilePictureOriginal?: string;
|
|
19
|
+
coverPicture?: string;
|
|
20
|
+
location?: string;
|
|
21
|
+
url?: string;
|
|
22
|
+
createdAt?: string;
|
|
23
|
+
statusesCount?: number;
|
|
24
|
+
[field: string]: unknown;
|
|
25
|
+
}
|
|
26
|
+
export interface Post {
|
|
27
|
+
id: string;
|
|
28
|
+
text?: string;
|
|
29
|
+
createdAt?: string;
|
|
30
|
+
url?: string;
|
|
31
|
+
lang?: string;
|
|
32
|
+
likeCount?: number;
|
|
33
|
+
retweetCount?: number;
|
|
34
|
+
replyCount?: number;
|
|
35
|
+
quoteCount?: number;
|
|
36
|
+
viewCount?: number;
|
|
37
|
+
bookmarkCount?: number;
|
|
38
|
+
conversationId?: string;
|
|
39
|
+
isReply?: boolean;
|
|
40
|
+
isRetweet?: boolean;
|
|
41
|
+
isQuoteStatus?: boolean;
|
|
42
|
+
author?: UserProfile;
|
|
43
|
+
[field: string]: unknown;
|
|
44
|
+
}
|
|
45
|
+
export interface PostPage {
|
|
46
|
+
tweets: Post[];
|
|
47
|
+
has_next_page: boolean;
|
|
48
|
+
next_cursor: string;
|
|
49
|
+
[field: string]: unknown;
|
|
50
|
+
}
|
|
51
|
+
export interface SearchPostsQuery {
|
|
52
|
+
q: string;
|
|
53
|
+
limit?: number;
|
|
54
|
+
cursor?: string;
|
|
55
|
+
queryType?: string;
|
|
56
|
+
[filter: string]: string | number | boolean | undefined;
|
|
57
|
+
}
|
|
58
|
+
export interface StrapiHandle {
|
|
59
|
+
plugin(name: "xquik"): {
|
|
60
|
+
config(key: "apiKey"): string;
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
declare const xquikService: ({ strapi }: {
|
|
64
|
+
strapi: StrapiHandle;
|
|
65
|
+
}) => {
|
|
66
|
+
searchPosts(query: SearchPostsQuery): Promise<PostPage>;
|
|
67
|
+
getUser(id: string): Promise<UserProfile>;
|
|
68
|
+
};
|
|
69
|
+
export default xquikService;
|
package/package.json
CHANGED
|
@@ -1,6 +1,69 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "strapi-plugin-xquik",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Search X posts & read profiles from Strapi 5 with the Xquik X Twitter data API.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"strapi",
|
|
7
|
+
"strapi-plugin",
|
|
8
|
+
"strapi-5",
|
|
9
|
+
"xquik",
|
|
10
|
+
"x-twitter-data-api",
|
|
11
|
+
"twitter-api",
|
|
12
|
+
"x-api"
|
|
13
|
+
],
|
|
14
|
+
"homepage": "https://xquik.com",
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/Xquik-dev/strapi-plugin-xquik/issues"
|
|
17
|
+
},
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/Xquik-dev/strapi-plugin-xquik.git"
|
|
21
|
+
},
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"author": "Xquik",
|
|
24
|
+
"type": "commonjs",
|
|
25
|
+
"exports": {
|
|
26
|
+
"./package.json": "./package.json",
|
|
27
|
+
"./strapi-server": {
|
|
28
|
+
"types": "./dist/server/src/index.d.ts",
|
|
29
|
+
"source": "./server/src/index.ts",
|
|
30
|
+
"import": "./dist/server/index.mjs",
|
|
31
|
+
"require": "./dist/server/index.js",
|
|
32
|
+
"default": "./dist/server/index.js"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"dist"
|
|
37
|
+
],
|
|
38
|
+
"scripts": {
|
|
39
|
+
"prepack": "strapi-plugin build",
|
|
40
|
+
"test": "bun test"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"zod": "^4.6.7"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@strapi/sdk-plugin": "6.1.1",
|
|
47
|
+
"@types/bun": "1.4.4",
|
|
48
|
+
"oxlint": "1.90.0",
|
|
49
|
+
"typescript": "5.9.3"
|
|
50
|
+
},
|
|
51
|
+
"peerDependencies": {
|
|
52
|
+
"@strapi/strapi": "^5"
|
|
53
|
+
},
|
|
54
|
+
"overrides": {
|
|
55
|
+
"minimatch": "11.0.4"
|
|
56
|
+
},
|
|
57
|
+
"engines": {
|
|
58
|
+
"node": ">=20.0.0 <=26.x.x"
|
|
59
|
+
},
|
|
60
|
+
"publishConfig": {
|
|
61
|
+
"access": "public"
|
|
62
|
+
},
|
|
63
|
+
"strapi": {
|
|
64
|
+
"kind": "plugin",
|
|
65
|
+
"name": "xquik",
|
|
66
|
+
"displayName": "Xquik",
|
|
67
|
+
"description": "Search X posts & read profiles from Strapi 5 with the Xquik X Twitter data API."
|
|
68
|
+
}
|
|
69
|
+
}
|