diff --git a/src/models/hashtag.ts b/src/models/hashtag.ts
new file mode 100644
index 000000000..f5b615605
--- /dev/null
+++ b/src/models/hashtag.ts
@@ -0,0 +1,13 @@
+import * as mongo from 'mongodb';
+import db from '../db/mongodb';
+
+const Hashtag = db.get('hashtags');
+Hashtag.createIndex('tag', { unique: true });
+Hashtag.createIndex('mentionedUserIdsCount');
+export default Hashtag;
+
+export interface IHashtags {
+ tag: string;
+ mentionedUserIds: mongo.ObjectID[];
+ mentionedUserIdsCount: number;
+}
diff --git a/src/server/api/endpoints/hashtags/search.ts b/src/server/api/endpoints/hashtags/search.ts
new file mode 100644
index 000000000..988a786a0
--- /dev/null
+++ b/src/server/api/endpoints/hashtags/search.ts
@@ -0,0 +1,51 @@
+import $ from 'cafy';
+import Hashtag from '../../../../models/hashtag';
+import getParams from '../../get-params';
+
+export const meta = {
+ desc: {
+ ja: 'ハッシュタグを検索します。'
+ },
+
+ requireCredential: false,
+
+ params: {
+ limit: $.num.optional.range(1, 100).note({
+ default: 10,
+ desc: {
+ ja: '最大数'
+ }
+ }),
+
+ query: $.str.note({
+ desc: {
+ ja: 'クエリ'
+ }
+ }),
+
+ offset: $.num.optional.min(0).note({
+ default: 0,
+ desc: {
+ ja: 'オフセット'
+ }
+ })
+ }
+};
+
+export default (params: any) => new Promise(async (res, rej) => {
+ const [ps, psErr] = getParams(meta, params);
+ if (psErr) throw psErr;
+
+ const hashtags = await Hashtag
+ .find({
+ tag: new RegExp(ps.query.toLowerCase())
+ }, {
+ sort: {
+ count: -1
+ },
+ limit: ps.limit,
+ skip: ps.offset
+ });
+
+ res(hashtags.map(tag => tag.tag));
+});
diff --git a/src/services/note/create.ts b/src/services/note/create.ts
index aec0e7896..6629e691b 100644
--- a/src/services/note/create.ts
+++ b/src/services/note/create.ts
@@ -20,6 +20,7 @@ import UserList from '../../models/user-list';
import resolveUser from '../../remote/resolve-user';
import Meta from '../../models/meta';
import config from '../../config';
+import registerHashtag from '../register-hashtag';
type Type = 'reply' | 'renote' | 'quote' | 'mention';
@@ -64,7 +65,6 @@ export default async (user: IUser, data: {
geo?: any;
poll?: any;
viaMobile?: boolean;
- tags?: string[];
cw?: string;
visibility?: string;
visibleUsers?: IUser[];
@@ -75,7 +75,7 @@ export default async (user: IUser, data: {
if (data.visibility == null) data.visibility = 'public';
if (data.viaMobile == null) data.viaMobile = false;
- let tags = data.tags || [];
+ let tags: string[] = [];
let tokens: any[] = null;
@@ -149,6 +149,9 @@ export default async (user: IUser, data: {
res(note);
+ // ハッシュタグ登録
+ tags.map(tag => registerHashtag(user, tag));
+
//#region Increment notes count
if (isLocalUser(user)) {
Meta.update({}, {
diff --git a/src/services/register-hashtag.ts b/src/services/register-hashtag.ts
new file mode 100644
index 000000000..ca6b74783
--- /dev/null
+++ b/src/services/register-hashtag.ts
@@ -0,0 +1,28 @@
+import { IUser } from '../models/user';
+import Hashtag from '../models/hashtag';
+
+export default async function(user: IUser, tag: string) {
+ tag = tag.toLowerCase();
+
+ const index = await Hashtag.findOne({ tag });
+
+ if (index != null) {
+ // 自分が初めてこのタグを使ったなら
+ if (!index.mentionedUserIds.some(id => id.equals(user._id))) {
+ Hashtag.update({ tag }, {
+ $push: {
+ mentionedUserIds: user._id
+ },
+ $inc: {
+ mentionedUserIdsCount: 1
+ }
+ });
+ }
+ } else {
+ Hashtag.insert({
+ tag,
+ mentionedUserIds: [user._id],
+ mentionedUserIdsCount: 1
+ });
+ }
+}