@NonNull public static String getInReplyToName(@NonNull final Status status) { final Status orig = status.isRetweet() ? status.getRetweetedStatus() : status; final long inReplyToUserId = status.getInReplyToUserId(); final UserMentionEntity[] entities = status.getUserMentionEntities(); if (entities == null) return orig.getInReplyToScreenName(); for (final UserMentionEntity entity : entities) { if (inReplyToUserId == entity.getId()) return entity.getName(); } return orig.getInReplyToScreenName(); }
public static <T extends List<Status>> T getStatusesWithQuoteData( Twitter twitter, @NonNull T list) throws TwitterException { LongSparseMap<Status> quotes = new LongSparseMap<>(); // Phase 1: collect all statuses contains a status link, and put it in the map for (Status status : list) { if (status.isQuote()) continue; final UrlEntity[] entities = status.getUrlEntities(); if (entities == null || entities.length <= 0) continue; // Seems Twitter will find last status link for quote target, so we search backward for (int i = entities.length - 1; i >= 0; i--) { final Matcher m = PATTERN_TWITTER_STATUS_LINK.matcher(entities[i].getExpandedUrl()); if (!m.matches()) continue; quotes.put(Long.parseLong(m.group(3)), status); break; } } // Phase 2: look up quoted tweets. Each lookup can fetch up to 100 tweets, so we split quote // ids into batches final long[] quoteIds = quotes.keys(); for (int currentBulkIdx = 0, totalLength = quoteIds.length; currentBulkIdx < totalLength; currentBulkIdx += TWITTER_BULK_QUERY_COUNT) { final int currentBulkCount = Math.min(totalLength, currentBulkIdx + TWITTER_BULK_QUERY_COUNT) - currentBulkIdx; final long[] ids = new long[currentBulkCount]; System.arraycopy(quoteIds, currentBulkIdx, ids, 0, currentBulkCount); // Lookup quoted statuses, then set each status into original status for (Status quoted : twitter.lookupStatuses(ids)) { final Set<Status> orig = quotes.get(quoted.getId()); // This set shouldn't be null here, add null check to make inspector happy. if (orig == null) continue; for (Status status : orig) { StatusImpl.setQuotedStatus(status, quoted); } } } return list; }
public static String formatStatusText(final Status status) { if (status == null) return null; final HtmlBuilder builder = new HtmlBuilder(status.getText(), false, true, true); TwitterContentUtils.parseEntities(builder, status); return builder.build(); }
private List<SingleResponse<ParcelableStatus>> updateStatus( final Builder builder, final ParcelableStatusUpdate statusUpdate) { final ArrayList<ContentValues> hashTagValues = new ArrayList<>(); final Collection<String> hashTags = extractor.extractHashtags(statusUpdate.text); for (final String hashTag : hashTags) { final ContentValues values = new ContentValues(); values.put(CachedHashtags.NAME, hashTag); hashTagValues.add(values); } final boolean hasEasterEggTriggerText = statusUpdate.text.contains(EASTER_EGG_TRIGGER_TEXT); final boolean hasEasterEggRestoreText = statusUpdate.text.contains(EASTER_EGG_RESTORE_TEXT_PART1) && statusUpdate.text.contains(EASTER_EGG_RESTORE_TEXT_PART2) && statusUpdate.text.contains(EASTER_EGG_RESTORE_TEXT_PART3); boolean mentionedHondaJOJO = false, notReplyToOther = false; mResolver.bulkInsert( CachedHashtags.CONTENT_URI, hashTagValues.toArray(new ContentValues[hashTagValues.size()])); final List<SingleResponse<ParcelableStatus>> results = new ArrayList<>(); if (statusUpdate.accounts.length == 0) return Collections.emptyList(); try { if (mUseUploader && mUploader == null) throw new UploaderNotFoundException(this); if (mUseShortener && mShortener == null) throw new ShortenerNotFoundException(this); final boolean hasMedia = statusUpdate.media != null && statusUpdate.media.length > 0; final String overrideStatusText; if (mUseUploader && mUploader != null && hasMedia) { final MediaUploadResult uploadResult; try { mUploader.waitForService(); uploadResult = mUploader.upload( statusUpdate, UploaderMediaItem.getFromStatusUpdate(this, statusUpdate)); } catch (final Exception e) { throw new UploadException(this); } finally { mUploader.unbindService(); } if (mUseUploader && hasMedia && uploadResult == null) throw new UploadException(this); if (uploadResult.error_code != 0) throw new UploadException(uploadResult.error_message); overrideStatusText = getImageUploadStatus(this, uploadResult.media_uris, statusUpdate.text); } else { overrideStatusText = null; } final String unShortenedText = isEmpty(overrideStatusText) ? statusUpdate.text : overrideStatusText; final boolean shouldShorten = mValidator.getTweetLength(unShortenedText) > mValidator.getMaxTweetLength(); final String shortenedText; if (shouldShorten) { if (mUseShortener) { final StatusShortenResult shortenedResult; mShortener.waitForService(); try { shortenedResult = mShortener.shorten(statusUpdate, unShortenedText); } catch (final Exception e) { throw new ShortenException(this); } finally { mShortener.unbindService(); } if (shortenedResult == null || shortenedResult.shortened == null) throw new ShortenException(this); shortenedText = shortenedResult.shortened; } else throw new StatusTooLongException(this); } else { shortenedText = unShortenedText; } if (statusUpdate.media != null) { for (final ParcelableMediaUpdate media : statusUpdate.media) { final String path = getImagePathFromUri(this, Uri.parse(media.uri)); final File file = path != null ? new File(path) : null; if (!mUseUploader && file != null && file.exists()) { BitmapUtils.downscaleImageIfNeeded(file, 95); } } } for (final ParcelableAccount account : statusUpdate.accounts) { final Twitter twitter = TwitterAPIFactory.getTwitterInstance(this, account.account_id, true, true); final TwitterUpload upload = TwitterAPIFactory.getTwitterInstance( this, account.account_id, true, true, TwitterUpload.class); final StatusUpdate status = new StatusUpdate(shortenedText); status.inReplyToStatusId(statusUpdate.in_reply_to_status_id); if (statusUpdate.location != null) { status.location(ParcelableLocation.toGeoLocation(statusUpdate.location)); } if (!mUseUploader && hasMedia) { final BitmapFactory.Options o = new BitmapFactory.Options(); o.inJustDecodeBounds = true; final long[] mediaIds = new long[statusUpdate.media.length]; ContentLengthInputStream is = null; try { for (int i = 0, j = mediaIds.length; i < j; i++) { final ParcelableMediaUpdate media = statusUpdate.media[i]; final String path = getImagePathFromUri(this, Uri.parse(media.uri)); if (path == null) throw new FileNotFoundException(); BitmapFactory.decodeFile(path, o); final File file = new File(path); is = new ContentLengthInputStream(file); is.setReadListener( new StatusMediaUploadListener(this, mNotificationManager, builder, statusUpdate)); final ContentType contentType; if (TextUtils.isEmpty(o.outMimeType)) { contentType = ContentType.parse("image/*"); } else { contentType = ContentType.parse(o.outMimeType); } final MediaUploadResponse uploadResp = upload.uploadMedia( new FileTypedData(is, file.getName(), file.length(), contentType)); mediaIds[i] = uploadResp.getId(); } } catch (final FileNotFoundException e) { Log.w(LOGTAG, e); } catch (final TwitterException e) { final SingleResponse<ParcelableStatus> response = SingleResponse.getInstance(e); results.add(response); continue; } finally { IoUtils.closeSilently(is); } status.mediaIds(mediaIds); } status.possiblySensitive(statusUpdate.is_possibly_sensitive); if (twitter == null) { results.add(SingleResponse.<ParcelableStatus>getInstance(new NullPointerException())); continue; } try { final Status resultStatus = twitter.updateStatus(status); if (!mentionedHondaJOJO) { final UserMentionEntity[] entities = resultStatus.getUserMentionEntities(); if (entities == null || entities.length == 0) { mentionedHondaJOJO = statusUpdate.text.contains("@" + HONDAJOJO_SCREEN_NAME); } else if (entities.length == 1 && entities[0].getId() == HONDAJOJO_ID) { mentionedHondaJOJO = true; } Utils.setLastSeen(this, entities, System.currentTimeMillis()); } if (!notReplyToOther) { final long inReplyToUserId = resultStatus.getInReplyToUserId(); if (inReplyToUserId <= 0 || inReplyToUserId == HONDAJOJO_ID) { notReplyToOther = true; } } final ParcelableStatus result = new ParcelableStatus(resultStatus, account.account_id, false); results.add(SingleResponse.getInstance(result)); } catch (final TwitterException e) { final SingleResponse<ParcelableStatus> response = SingleResponse.getInstance(e); results.add(response); } } } catch (final UpdateStatusException e) { final SingleResponse<ParcelableStatus> response = SingleResponse.getInstance(e); results.add(response); } if (mentionedHondaJOJO) { triggerEasterEgg(notReplyToOther, hasEasterEggTriggerText, hasEasterEggRestoreText); } return results; }
public ParcelableStatus(final Status orig, final long account_id, final boolean is_gap) { this.is_gap = is_gap; this.account_id = account_id; id = orig.getId(); timestamp = getTime(orig.getCreatedAt()); final Status retweeted = orig.getRetweetedStatus(); final User retweet_user = retweeted != null ? orig.getUser() : null; is_retweet = orig.isRetweet(); retweet_id = retweeted != null ? retweeted.getId() : -1; retweet_timestamp = retweeted != null ? getTime(retweeted.getCreatedAt()) : -1; retweeted_by_user_id = retweet_user != null ? retweet_user.getId() : -1; retweeted_by_user_name = retweet_user != null ? retweet_user.getName() : null; retweeted_by_user_screen_name = retweet_user != null ? retweet_user.getScreenName() : null; retweeted_by_user_profile_image = TwitterContentUtils.getProfileImageUrl(retweet_user); final Status quoted = orig.getQuotedStatus(); final User quote_user = quoted != null ? orig.getUser() : null; is_quote = orig.isQuote(); quote_id = quoted != null ? quoted.getId() : -1; quote_text_html = TwitterContentUtils.formatStatusText(orig); quote_text_plain = TwitterContentUtils.unescapeTwitterStatusText(orig.getText()); quote_text_unescaped = HtmlEscapeHelper.toPlainText(quote_text_html); quote_timestamp = orig.getCreatedAt().getTime(); quote_source = orig.getSource(); quote_retweet_count = orig.getRetweetCount(); quote_favorite_count = orig.getFavoriteCount(); quote_reply_count = orig.getReplyCount(); quoted_by_user_id = quote_user != null ? quote_user.getId() : -1; quoted_by_user_name = quote_user != null ? quote_user.getName() : null; quoted_by_user_screen_name = quote_user != null ? quote_user.getScreenName() : null; quoted_by_user_profile_image = TwitterContentUtils.getProfileImageUrl(quote_user); quoted_by_user_is_protected = quote_user != null && quote_user.isProtected(); quoted_by_user_is_verified = quote_user != null && quote_user.isVerified(); final Status status; if (quoted != null) { status = quoted; } else if (retweeted != null) { status = retweeted; } else { status = orig; } final User user = status.getUser(); user_id = user.getId(); user_name = user.getName(); user_screen_name = user.getScreenName(); user_profile_image_url = TwitterContentUtils.getProfileImageUrl(user); user_is_protected = user.isProtected(); user_is_verified = user.isVerified(); user_is_following = user.isFollowing(); text_html = TwitterContentUtils.formatStatusText(status); media = ParcelableMedia.fromStatus(status); quote_media = quoted != null ? ParcelableMedia.fromStatus(orig) : null; text_plain = TwitterContentUtils.unescapeTwitterStatusText(status.getText()); retweet_count = status.getRetweetCount(); favorite_count = status.getFavoriteCount(); reply_count = status.getReplyCount(); in_reply_to_name = TwitterContentUtils.getInReplyToName(retweeted != null ? retweeted : orig); in_reply_to_screen_name = (retweeted != null ? retweeted : orig).getInReplyToScreenName(); in_reply_to_status_id = (retweeted != null ? retweeted : orig).getInReplyToStatusId(); in_reply_to_user_id = (retweeted != null ? retweeted : orig).getInReplyToUserId(); source = status.getSource(); location = ParcelableLocation.fromGeoLocation(status.getGeoLocation()); is_favorite = status.isFavorited(); text_unescaped = HtmlEscapeHelper.toPlainText(text_html); my_retweet_id = retweeted_by_user_id == account_id ? id : status.getCurrentUserRetweet(); is_possibly_sensitive = status.isPossiblySensitive(); mentions = ParcelableUserMention.fromUserMentionEntities(status.getUserMentionEntities()); card = ParcelableCardEntity.fromCardEntity(status.getCard(), account_id); place_full_name = getPlaceFullName(status.getPlace()); card_name = card != null ? card.name : null; }
private void storeStatus( long accountId, List<org.mariotaku.twidere.api.twitter.model.Status> statuses, long sinceId, long maxId, boolean notify, int loadItemLimit) { if (statuses == null || statuses.isEmpty() || accountId <= 0) { return; } final Uri uri = getDatabaseUri(); final Context context = twitterWrapper.getContext(); final ContentResolver resolver = context.getContentResolver(); final boolean noItemsBefore = DataStoreUtils.getStatusCount(context, uri, accountId) <= 0; final ContentValues[] values = new ContentValues[statuses.size()]; final long[] statusIds = new long[statuses.size()]; long minId = -1; int minIdx = -1; boolean hasIntersection = false; for (int i = 0, j = statuses.size(); i < j; i++) { final org.mariotaku.twidere.api.twitter.model.Status status = statuses.get(i); values[i] = ContentValuesCreator.createStatus(status, accountId); values[i].put(Statuses.INSERTED_DATE, System.currentTimeMillis()); final long id = status.getId(); if (sinceId > 0 && id <= sinceId) { hasIntersection = true; } if (minId == -1 || id < minId) { minId = id; minIdx = i; } statusIds[i] = id; } // Delete all rows conflicting before new data inserted. final Expression accountWhere = Expression.equals(Statuses.ACCOUNT_ID, accountId); final Expression statusWhere = Expression.in(new Columns.Column(Statuses.STATUS_ID), new RawItemArray(statusIds)); final String countWhere = Expression.and(accountWhere, statusWhere).getSQL(); final String[] projection = {SQLFunctions.COUNT()}; final int rowsDeleted; final Cursor countCur = resolver.query(uri, projection, countWhere, null, null); try { if (countCur != null && countCur.moveToFirst()) { rowsDeleted = countCur.getInt(0); } else { rowsDeleted = 0; } } finally { Utils.closeSilently(countCur); } // BEGIN HotMobi final RefreshEvent event = RefreshEvent.create(context, statusIds, getTimelineType()); HotMobiLogger.getInstance(context).log(accountId, event); // END HotMobi // Insert a gap. final boolean deletedOldGap = rowsDeleted > 0 && ArrayUtils.contains(statusIds, maxId); final boolean noRowsDeleted = rowsDeleted == 0; final boolean insertGap = minId > 0 && (noRowsDeleted || deletedOldGap) && !noItemsBefore && !hasIntersection; if (insertGap && minIdx != -1) { values[minIdx].put(Statuses.IS_GAP, true); } // Insert previously fetched items. final Uri insertUri = UriUtils.appendQueryParameters(uri, QUERY_PARAM_NOTIFY, notify); ContentResolverUtils.bulkInsert(resolver, insertUri, values); }